diff --git a/.clang-tidy b/.clang-tidy index db4990355..c97a138f3 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -9,7 +9,6 @@ WarningsAsErrors: > -bugprone-unused-local-non-trivial-variable, -bugprone-easily-swappable-parameters, -bugprone-forward-declararion-namespace, - -bugprone-forward-declararion-namespace, -bugprone-macro-parentheses, -bugprone-narrowing-conversions, -bugprone-branch-clone, @@ -113,7 +112,6 @@ Checks: > bugprone-*, -bugprone-easily-swappable-parameters, -bugprone-forward-declararion-namespace, - -bugprone-forward-declararion-namespace, -bugprone-macro-parentheses, -bugprone-narrowing-conversions, -bugprone-branch-clone, diff --git a/.github/actions/setup_base/action.yml b/.github/actions/setup_base/action.yml index 665d7f07d..9fcaabfbc 100644 --- a/.github/actions/setup_base/action.yml +++ b/.github/actions/setup_base/action.yml @@ -24,6 +24,7 @@ runs: glm \ glslang \ go \ + gtest \ hyprlang \ hyprcursor \ jq \ @@ -75,6 +76,15 @@ runs: cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` cmake --install build + - name: Get hyprwire-git + shell: bash + run: | + git clone https://github.com/hyprwm/hyprwire --recursive + cd hyprwire + cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build + cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` + cmake --install build + - name: Get hyprutils-git shell: bash run: | diff --git a/.github/labeler.yml b/.github/labeler.yml index a0685fcfe..6b89255ac 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -22,6 +22,10 @@ protocols: - changed-files: - any-glob-to-any-file: ["protocols/**", "src/protocols/**"] +start: + - changed-files: + - any-glob-to-any-file: "start/**" + core: - changed-files: - any-glob-to-any-file: "src/**" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index fda97f578..75b4b7c50 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,6 +1,8 @@ diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d14ac02e9..a41f41e40 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -46,11 +46,33 @@ 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: Install clang-format + run: | + pacman --noconfirm --noprogressbar -Syyu + pacman --noconfirm --noprogressbar -Sy clang + - name: clang-format check - uses: jidicula/clang-format-action@v4.16.0 - with: - exclude-regex: ^subprojects$ + run: .github/workflows/clang-format-check.sh "." "llvm" "^subprojects$" "" + + - name: Save PR head commit SHA + if: failure() && github.event_name == 'pull_request' + shell: bash + run: | + SHA="${{ github.event.pull_request.head.sha }}" + echo "SHA=$SHA" >> $GITHUB_ENV + - name: Save latest commit SHA if not PR + if: failure() && github.event_name != 'pull_request' + shell: bash + run: echo "SHA=${{ github.sha }}" >> $GITHUB_ENV + + - name: Report failure in job summary + if: failure() + run: | + DEEPLINK="${{ github.server_url }}/${{ github.repository }}/commit/${{ env.SHA }}" + echo -e "Format check failed on commit [${GITHUB_SHA:0:8}]($DEEPLINK) with files:\n$(<$GITHUB_WORKSPACE/failing-files.txt)" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/clang-format-check.sh b/.github/workflows/clang-format-check.sh new file mode 100755 index 000000000..41237aa7d --- /dev/null +++ b/.github/workflows/clang-format-check.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# +# Adapted from https://github.com/jidicula/clang-format-action + +############################################################################### +# check.sh # +############################################################################### +# USAGE: ./entrypoint.sh [] [] +# +# Checks all C/C++/Protobuf/CUDA files (.h, .H, .hpp, .hh, .h++, .hxx and .c, +# .C, .cpp, .cc, .c++, .cxx, .proto, .cu) in the provided GitHub repository path +# (arg1) for conforming to clang-format. If no path is provided or provided path +# is not a directory, all C/C++/Protobuf/CUDA files are checked. If any files +# are incorrectly formatted, the script lists them and exits with 1. +# +# Define your own formatting rules in a .clang-format file at your repository +# root. Otherwise, the provided style guide (arg2) is used as a fallback. + +# format_diff function +# Accepts a filepath argument. The filepath passed to this function must point +# to a C/C++/Protobuf/CUDA file. +format_diff() { + local filepath="$1" + + # Invoke clang-format with dry run and formatting error output + local_format="$(clang-format \ + --dry-run \ + --Werror \ + --style=file \ + --fallback-style="$FALLBACK_STYLE" \ + "${filepath}")" + + local format_status="$?" + if [[ ${format_status} -ne 0 ]]; then + # Append Markdown-bulleted monospaced filepath of failing file to + # summary file. + echo "* \`$filepath\`" >>failing-files.txt + + echo "Failed on file: $filepath" >&2 + echo "$local_format" >&2 + exit_code=1 # flip the global exit code + return "${format_status}" + fi + return 0 +} + +CHECK_PATH="$1" +FALLBACK_STYLE="$2" +EXCLUDE_REGEX="$3" +INCLUDE_REGEX="$4" + +# Set the regex to an empty string regex if nothing was provided +if [[ -z $EXCLUDE_REGEX ]]; then + EXCLUDE_REGEX="^$" +fi + +# Set the filetype regex if nothing was provided. +# Find all C/C++/Protobuf/CUDA files: +# h, H, hpp, hh, h++, hxx +# c, C, cpp, cc, c++, cxx +# ino, pde +# proto +# cu +if [[ -z $INCLUDE_REGEX ]]; then + INCLUDE_REGEX='^.*\.((((c|C)(c|pp|xx|\+\+)?$)|((h|H)h?(pp|xx|\+\+)?$))|(ino|pde|proto|cu))$' +fi + +cd "$GITHUB_WORKSPACE" || exit 2 + +if [[ ! -d $CHECK_PATH ]]; then + echo "Not a directory in the workspace, fallback to all files." >&2 + CHECK_PATH="." +fi + +# initialize exit code +exit_code=0 + +# All files improperly formatted will be printed to the output. +src_files=$(find "$CHECK_PATH" -name .git -prune -o -regextype posix-egrep -regex "$INCLUDE_REGEX" -print) + +# check formatting in each source file +IFS=$'\n' # Loop below should separate on new lines, not spaces. +for file in $src_files; do + # Only check formatting if the path doesn't match the regex + if ! [[ ${file} =~ $EXCLUDE_REGEX ]]; then + format_diff "${file}" + fi +done + +# global exit code is flipped to nonzero if any invocation of `format_diff` has +# a formatting difference. +exit "$exit_code" diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml deleted file mode 100644 index 505829e36..000000000 --- a/.github/workflows/clang-format.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: clang-format -on: pull_request_target -jobs: - clang-format: - permissions: write-all - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork - name: "Code Style" - runs-on: ubuntu-latest - 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: Create comment - if: ${{ failure() && github.event_name == 'pull_request' }} - run: | - echo 'Please fix the formatting issues by running [`clang-format`](https://wiki.hyprland.org/Contributing-and-Debugging/PR-Guidelines/#code-style).' > clang-format.patch - - - name: Post comment - if: ${{ failure() && github.event_name == 'pull_request' }} - uses: mshick/add-pr-comment@v2 - with: - message-path: | - clang-format.patch diff --git a/.github/workflows/new-pr-comment.yml b/.github/workflows/new-pr-comment.yml new file mode 100644 index 000000000..36ea19098 --- /dev/null +++ b/.github/workflows/new-pr-comment.yml @@ -0,0 +1,45 @@ +name: "New MR welcome comment" + +on: + pull_request_target: + types: + - opened + +jobs: + comment: + if: > + github.event.pull_request.user.login != 'vaxerski' && + github.event.pull_request.user.login != 'fufexan' && + github.event.pull_request.user.login != 'gulafaran' && + github.event.pull_request.user.login != 'ujint34' && + github.event.pull_request.user.login != 'paideiadilemma' && + github.event.pull_request.user.login != 'notashelf' + runs-on: ubuntu-latest + permissions: + pull-requests: write + + env: + PR_COMMENT: | + Hello and thank you for making a PR to Hyprland! + + Please check the [PR Guidelines](https://wiki.hypr.land/Contributing-and-Debugging/PR-Guidelines/) and make sure your PR follows them. + It will make the entire review process faster. :) + + If your code can be tested, please always add tests. See more [here](https://wiki.hypr.land/Contributing-and-Debugging/Tests/). + + _beep boop, I'm just a bot. A real human will review your PR soon._ + + steps: + - name: Add comment to PR + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const pr = context.payload.pull_request; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + body: process.env.PR_COMMENT, + }); diff --git a/.github/workflows/nix-ci.yml b/.github/workflows/nix-ci.yml index ae6150578..467b2dfb8 100644 --- a/.github/workflows/nix-ci.yml +++ b/.github/workflows/nix-ci.yml @@ -10,21 +10,22 @@ jobs: hyprland: if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork) - uses: ./.github/workflows/nix.yml + uses: hyprwm/actions/.github/workflows/nix.yml@main secrets: inherit with: + checkout: false command: nix build 'github:${{ github.repository }}?ref=${{ github.ref }}' -L --extra-substituters "https://hyprland.cachix.org" xdph: if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork) needs: hyprland - uses: ./.github/workflows/nix.yml + uses: hyprwm/actions/.github/workflows/nix.yml@main secrets: inherit with: + checkout: false command: nix build 'github:${{ github.repository }}?ref=${{ github.ref }}#xdg-desktop-portal-hyprland' -L --extra-substituters "https://hyprland.cachix.org" test: if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork) - needs: hyprland uses: ./.github/workflows/nix-test.yml secrets: inherit diff --git a/.github/workflows/nix-test.yml b/.github/workflows/nix-test.yml index 68357093a..666d971c1 100644 --- a/.github/workflows/nix-test.yml +++ b/.github/workflows/nix-test.yml @@ -10,28 +10,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - name: Install Nix - uses: nixbuild/nix-quick-install-action@v31 + - uses: hyprwm/actions/nix-setup@main with: - nix_conf: | - keep-env-derivations = true - keep-outputs = true - - - name: Restore and save Nix store - uses: nix-community/cache-nix-action@v6 - with: - # restore and save a cache using this key (per job) - primary-key: nix-${{ runner.os }}-${{ github.job }} - # if there's no cache hit, restore a cache by this prefix - restore-prefixes-first-match: nix-${{ runner.os }} - # collect garbage until the Nix store size (in bytes) is at most this number - # before trying to save a new cache - gc-max-store-size-linux: 5G - - - uses: cachix/cachix-action@v15 - with: - name: hyprland - authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} - name: Run test VM run: nix build 'github:${{ github.repository }}?ref=${{ github.ref }}#checks.x86_64-linux.tests' -L --extra-substituters "https://hyprland.cachix.org" diff --git a/.github/workflows/nix-update-inputs.yml b/.github/workflows/nix-update-inputs.yml index a3084b270..9e92365fb 100644 --- a/.github/workflows/nix-update-inputs.yml +++ b/.github/workflows/nix-update-inputs.yml @@ -17,23 +17,7 @@ jobs: with: token: ${{ secrets.PAT }} - - name: Install Nix - uses: nixbuild/nix-quick-install-action@v31 - with: - nix_conf: | - keep-env-derivations = true - keep-outputs = true - - - name: Restore and save Nix store - uses: nix-community/cache-nix-action@v6 - with: - # restore and save a cache using this key (per job) - primary-key: nix-${{ runner.os }}-${{ github.job }} - # if there's no cache hit, restore a cache by this prefix - restore-prefixes-first-match: nix-${{ runner.os }} - # collect garbage until the Nix store size (in bytes) is at most this number - # before trying to save a new cache - gc-max-store-size-linux: 5G + - uses: hyprwm/actions/nix-setup@main - name: Update inputs run: nix/update-inputs.sh diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml deleted file mode 100644 index b46b37953..000000000 --- a/.github/workflows/nix.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Build - -on: - workflow_call: - inputs: - command: - required: true - type: string - description: Command to run - secrets: - CACHIX_AUTH_TOKEN: - required: false - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Install Nix - uses: nixbuild/nix-quick-install-action@v31 - with: - nix_conf: | - keep-env-derivations = true - keep-outputs = true - - - name: Restore and save Nix store - uses: nix-community/cache-nix-action@v6 - with: - # restore and save a cache using this key (per job) - primary-key: nix-${{ runner.os }}-${{ github.job }} - # if there's no cache hit, restore a cache by this prefix - restore-prefixes-first-match: nix-${{ runner.os }} - # collect garbage until the Nix store size (in bytes) is at most this number - # before trying to save a new cache - gc-max-store-size-linux: 5G - - - uses: cachix/cachix-action@v15 - with: - name: hyprland - authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - - - run: ${{ inputs.command }} diff --git a/.gitignore b/.gitignore index 4ced16785..b20592bc0 100644 --- a/.gitignore +++ b/.gitignore @@ -32,16 +32,23 @@ src/render/shaders/*.inc src/render/shaders/Shaders.hpp hyprctl/hyprctl +hyprctl/hw-protocols/*.c* +hyprctl/hw-protocols/*.h* gmon.out *.out *.tar.gz +perf.data +flame.svg PKGBUILD src/version.h hyprpm/Makefile hyprctl/Makefile +example/hyprland.desktop **/.#*.* **/#*.*# + +debug-tools/flamegraph \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d47b57ff..abca4e9b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,6 @@ set(HYPRLAND_VERSION ${VER}) set(PREFIX ${CMAKE_INSTALL_PREFIX}) set(INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR}) set(BINDIR ${CMAKE_INSTALL_BINDIR}) -configure_file(hyprland.pc.in hyprland.pc @ONLY) set(CMAKE_MESSAGE_LOG_LEVEL "STATUS") @@ -25,7 +24,14 @@ message(STATUS "Gathering git info") # Make shader files includable execute_process(COMMAND ./scripts/generateShaderIncludes.sh - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE HYPR_SHADER_GEN_RESULT) +if(NOT HYPR_SHADER_GEN_RESULT EQUAL 0) + message( + FATAL_ERROR + "Failed to generate shader includes (scripts/generateShaderIncludes.sh), exit code: ${HYPR_SHADER_GEN_RESULT}" + ) +endif() find_package(PkgConfig REQUIRED) @@ -80,9 +86,11 @@ message( if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) message(STATUS "Configuring Hyprland in Debug with CMake") add_compile_definitions(HYPRLAND_DEBUG) + set(BUILD_TESTING ON) else() add_compile_options(-O3) message(STATUS "Configuring Hyprland in Release with CMake") + set(BUILD_TESTING OFF) endif() add_compile_definitions(HYPRLAND_VERSION="${HYPRLAND_VERSION}") @@ -102,6 +110,7 @@ add_compile_options( -Wno-narrowing -Wno-pointer-arith -Wno-clobbered + -frtti -fmacro-prefix-map=${CMAKE_SOURCE_DIR}/=) # disable lto as it may break plugins @@ -116,12 +125,19 @@ find_package(Threads REQUIRED) set(GLES_VERSION "GLES3") find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION}) +find_package(glslang CONFIG REQUIRED) -pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=0.9.3) -pkg_check_modules(hyprlang_dep REQUIRED IMPORTED_TARGET hyprlang>=0.3.2) -pkg_check_modules(hyprcursor_dep REQUIRED IMPORTED_TARGET hyprcursor>=0.1.7) -pkg_check_modules(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=0.10.2) -pkg_check_modules(hyprgraphics_dep REQUIRED IMPORTED_TARGET hyprgraphics>=0.1.6) +set(AQUAMARINE_MINIMUM_VERSION 0.9.3) +set(HYPRLANG_MINIMUM_VERSION 0.6.7) +set(HYPRCURSOR_MINIMUM_VERSION 0.1.7) +set(HYPRUTILS_MINIMUM_VERSION 0.11.1) +set(HYPRGRAPHICS_MINIMUM_VERSION 0.5.1) + +pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=${AQUAMARINE_MINIMUM_VERSION}) +pkg_check_modules(hyprlang_dep REQUIRED IMPORTED_TARGET hyprlang>=${HYPRLANG_MINIMUM_VERSION}) +pkg_check_modules(hyprcursor_dep REQUIRED IMPORTED_TARGET hyprcursor>=${HYPRCURSOR_MINIMUM_VERSION}) +pkg_check_modules(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=${HYPRUTILS_MINIMUM_VERSION}) +pkg_check_modules(hyprgraphics_dep REQUIRED IMPORTED_TARGET hyprgraphics>=${HYPRGRAPHICS_MINIMUM_VERSION}) string(REPLACE "." ";" AQ_VERSION_LIST ${aquamarine_dep_VERSION}) list(GET AQ_VERSION_LIST 0 AQ_VERSION_MAJOR) @@ -228,29 +244,37 @@ 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_PROTOCOLS_MINIMUM_VERSION 1.47) +set(LIBINPUT_MINIMUM_VERSION 1.28) + pkg_check_modules( deps REQUIRED - IMPORTED_TARGET - xkbcommon>=1.11.0 + IMPORTED_TARGET GLOBAL + xkbcommon>=${XKBCOMMON_MINIMUM_VERSION} uuid - wayland-server>=1.22.90 - wayland-protocols>=1.45 + wayland-server>=${WAYLAND_SERVER_MINIMUM_VERSION} + wayland-protocols>=${WAYLAND_SERVER_PROTOCOLS_MINIMUM_VERSION} cairo pango pangocairo pixman-1 xcursor libdrm - libinput>=1.28 + libinput>=${LIBINPUT_MINIMUM_VERSION} gbm gio-2.0 re2 - muparser) + muparser + lcms2) find_package(hyprwayland-scanner 0.3.10 REQUIRED) file(GLOB_RECURSE SRCFILES "src/*.cpp") +get_filename_component(FULL_MAIN_PATH ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp ABSOLUTE) +list(REMOVE_ITEM SRCFILES "${FULL_MAIN_PATH}") set(TRACY_CPP_FILES "") if(USE_TRACY) @@ -258,7 +282,12 @@ if(USE_TRACY) message(STATUS "Tracy enabled, TRACY_CPP_FILES: " ${TRACY_CPP_FILES}) endif() -add_executable(Hyprland ${SRCFILES} ${TRACY_CPP_FILES}) +add_library(hyprland_lib STATIC ${SRCFILES}) +add_executable(Hyprland src/main.cpp ${TRACY_CPP_FILES}) +target_link_libraries(Hyprland hyprland_lib) + +target_include_directories(hyprland_lib PUBLIC ${deps_INCLUDE_DIRS}) +target_include_directories(Hyprland PUBLIC ${deps_INCLUDE_DIRS}) set(USE_GPROF OFF) @@ -268,26 +297,11 @@ if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) if(WITH_ASAN) message(STATUS "Enabling ASan") - target_link_libraries(Hyprland asan) - target_compile_options(Hyprland PUBLIC -fsanitize=address) + target_link_libraries(hyprland_lib PUBLIC asan) + 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 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_compile_options(-fno-pie -fno-builtin -fno-omit-frame-pointer) add_link_options(-no-pie -fno-builtin) if(USE_GPROF) add_compile_options(-pg) @@ -295,6 +309,27 @@ 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() + check_include_file("execinfo.h" EXECINFOH) if(EXECINFOH) message(STATUS "Configuration supports execinfo") @@ -304,19 +339,19 @@ endif() include(CheckLibraryExists) check_library_exists(execinfo backtrace "" HAVE_LIBEXECINFO) if(HAVE_LIBEXECINFO) - target_link_libraries(Hyprland execinfo) + target_link_libraries(hyprland_lib PUBLIC execinfo) endif() check_include_file("sys/timerfd.h" HAS_TIMERFD) pkg_check_modules(epoll IMPORTED_TARGET epoll-shim) if(NOT HAS_TIMERFD AND epoll_FOUND) - target_link_libraries(Hyprland PkgConfig::epoll) + target_link_libraries(hyprland_lib PUBLIC PkgConfig::epoll) endif() check_include_file("sys/inotify.h" HAS_INOTIFY) pkg_check_modules(inotify IMPORTED_TARGET libinotify) if(NOT HAS_INOTIFY AND inotify_FOUND) - target_link_libraries(Hyprland PkgConfig::inotify) + target_link_libraries(hyprland_lib PUBLIC PkgConfig::inotify) endif() if(NO_XWAYLAND) @@ -324,10 +359,7 @@ if(NO_XWAYLAND) add_compile_definitions(NO_XWAYLAND) else() message(STATUS "XWAYLAND Enabled (NO_XWAYLAND not defined) checking deps...") - pkg_check_modules( - xdeps - REQUIRED - IMPORTED_TARGET + set(XWAYLAND_DEPENDENCIES xcb xcb-render xcb-xfixes @@ -335,9 +367,21 @@ else() xcb-composite xcb-res xcb-errors) - target_link_libraries(Hyprland PkgConfig::xdeps) + + pkg_check_modules( + xdeps + REQUIRED + IMPORTED_TARGET + ${XWAYLAND_DEPENDENCIES}) + + string(JOIN ", " PKGCONFIG_XWAYLAND_DEPENDENCIES ${XWAYLAND_DEPENDENCIES}) + string(PREPEND PKGCONFIG_XWAYLAND_DEPENDENCIES ", ") + + target_link_libraries(hyprland_lib PUBLIC PkgConfig::xdeps) endif() +configure_file(hyprland.pc.in hyprland.pc @ONLY) + if(NO_SYSTEMD) message(STATUS "SYSTEMD support is disabled...") else() @@ -362,31 +406,38 @@ if(CMAKE_DISABLE_PRECOMPILE_HEADERS) message(STATUS "Not using precompiled headers") else() message(STATUS "Setting precompiled headers") - target_precompile_headers(Hyprland PRIVATE + target_precompile_headers(hyprland_lib PRIVATE $<$:src/pch/pch.hpp>) endif() message(STATUS "Setting link libraries") target_link_libraries( - Hyprland - ${LIBRT} + hyprland_lib + PUBLIC PkgConfig::aquamarine_dep PkgConfig::hyprlang_dep PkgConfig::hyprutils_dep PkgConfig::hyprcursor_dep PkgConfig::hyprgraphics_dep - PkgConfig::deps) + PkgConfig::deps +) + +target_link_libraries( + Hyprland + ${LIBRT} + hyprland_lib) if(udis_dep_FOUND) - target_link_libraries(Hyprland PkgConfig::udis_dep) + target_link_libraries(hyprland_lib PUBLIC PkgConfig::udis_dep) elseif(NOT("${udis_nopc}" MATCHES "udis_nopc-NOTFOUND")) - target_link_libraries(Hyprland ${udis_nopc}) + target_link_libraries(hyprland_lib PUBLIC ${udis_nopc}) else() - target_link_libraries(Hyprland libudis86) + target_link_libraries(hyprland_lib PUBLIC libudis86) endif() # used by `make installheaders`, to ensure the headers are generated add_custom_target(generate-protocol-headers) +set(PROTOCOL_SOURCES "") function(protocolnew protoPath protoName external) if(external) @@ -400,10 +451,15 @@ function(protocolnew protoPath protoName external) COMMAND hyprwayland-scanner ${path}/${protoName}.xml ${CMAKE_SOURCE_DIR}/protocols/ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) - target_sources(Hyprland PRIVATE protocols/${protoName}.cpp + target_sources(hyprland_lib PRIVATE protocols/${protoName}.cpp protocols/${protoName}.hpp) target_sources(generate-protocol-headers PRIVATE ${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp) + + list(APPEND PROTOCOL_SOURCES "${CMAKE_SOURCE_DIR}/protocols/${protoName}.cpp") + set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE) + list(APPEND PROTOCOL_SOURCES "${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp") + set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE) endfunction() function(protocolWayland) add_custom_command( @@ -413,12 +469,21 @@ function(protocolWayland) hyprwayland-scanner --wayland-enums ${WAYLAND_SCANNER_PKGDATA_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) - target_sources(Hyprland PRIVATE protocols/wayland.cpp protocols/wayland.hpp) + target_sources(hyprland_lib PRIVATE protocols/wayland.cpp protocols/wayland.hpp) target_sources(generate-protocol-headers PRIVATE ${CMAKE_SOURCE_DIR}/protocols/wayland.hpp) + + list(APPEND PROTOCOL_SOURCES "${CMAKE_SOURCE_DIR}/protocols/wayland.hpp") + set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE) + list(APPEND PROTOCOL_SOURCES "${CMAKE_SOURCE_DIR}/protocols/wayland.cpp") + set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE) endfunction() -target_link_libraries(Hyprland OpenGL::EGL OpenGL::GL Threads::Threads) +if(TARGET OpenGL::GL) + target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GL glslang::glslang glslang::glslang-default-resource-limits glslang::SPIRV Threads::Threads) +else() + target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GLES3 glslang::glslang glslang::glslang-default-resource-limits glslang::SPIRV Threads::Threads) +endif() pkg_check_modules(hyprland_protocols_dep hyprland-protocols>=0.6.4) if(hyprland_protocols_dep_FOUND) @@ -446,8 +511,6 @@ protocolnew("protocols" "kde-server-decoration" true) protocolnew("protocols" "wlr-data-control-unstable-v1" true) protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-focus-grab-v1" true) protocolnew("protocols" "wlr-layer-shell-unstable-v1" true) -protocolnew("protocols" "xx-color-management-v4" true) -protocolnew("protocols" "frog-color-management-v1" true) protocolnew("protocols" "wayland-drm" true) protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-ctm-control-v1" true) protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-surface-v1" true) @@ -471,6 +534,7 @@ protocolnew("unstable/keyboard-shortcuts-inhibit" protocolnew("unstable/text-input" "text-input-unstable-v3" false) protocolnew("unstable/pointer-constraints" "pointer-constraints-unstable-v1" false) +protocolnew("unstable/xdg-foreign" "xdg-foreign-unstable-v2" false) protocolnew("staging/xdg-activation" "xdg-activation-v1" false) protocolnew("staging/ext-idle-notify" "ext-idle-notify-v1" false) protocolnew("staging/ext-session-lock" "ext-session-lock-v1" false) @@ -495,11 +559,14 @@ 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() # tools add_subdirectory(hyprctl) +add_subdirectory(start) if(NO_HYPRPM) message(STATUS "hyprpm is disabled") @@ -518,6 +585,11 @@ install( \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_BINDIR}/hyprland\" \ )") # session file +configure_file( + ${CMAKE_SOURCE_DIR}/example/hyprland.desktop.in + ${CMAKE_SOURCE_DIR}/example/hyprland.desktop + @ONLY +) install(FILES ${CMAKE_SOURCE_DIR}/example/hyprland.desktop DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/wayland-sessions) @@ -563,10 +635,37 @@ install( PATTERN "*.hpp" PATTERN "*.inc") -if(BUILD_TESTING OR BUILD_HYPRTESTER) - message(STATUS "Building hyprtester") +if(BUILD_TESTING OR WITH_TESTS) + message(STATUS "Building tests") + # hyprtester add_subdirectory(hyprtester) + + # GTest + find_package(GTest CONFIG REQUIRED) + include(GoogleTest) + file(GLOB_RECURSE TESTFILES "tests/*.cpp") + add_executable(hyprland_gtests ${TESTFILES}) + target_compile_options(hyprland_gtests PRIVATE --coverage) + target_link_options(hyprland_gtests PRIVATE --coverage) + target_include_directories( + hyprland_gtests + PUBLIC "./include" + PRIVATE "./src" "./src/include" "./protocols" "${CMAKE_BINARY_DIR}") + + target_link_libraries(hyprland_gtests hyprland_lib GTest::gtest_main) + + gtest_discover_tests(hyprland_gtests) + + # Enable coverage in main hyprland lib + target_compile_options(hyprland_lib PRIVATE --coverage) + target_link_options(hyprland_lib PRIVATE --coverage) + target_link_libraries(hyprland_lib PUBLIC gcov) + + # Enable coverage in hyprland exe + target_compile_options(Hyprland PRIVATE --coverage) + target_link_options(Hyprland PRIVATE --coverage) + target_link_libraries(Hyprland gcov) endif() if(BUILD_TESTING) @@ -575,12 +674,8 @@ if(BUILD_TESTING) enable_testing() add_custom_target(tests) - add_test( - NAME "Main Test" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/hyprtester - COMMAND hyprtester) + add_dependencies(tests hyprland_gtests) - add_dependencies(tests hyprtester) else() message(STATUS "Testing is disabled") endif() diff --git a/LICENSE b/LICENSE index e881cf92a..efdec21a5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2022-2025, vaxerski +Copyright (c) 2022-2026, vaxerski All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/Makefile b/Makefile index 282258ed8..c3cd8df04 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ nopch: clear: rm -rf build rm -f ./protocols/*.h ./protocols/*.c ./protocols/*.cpp ./protocols/*.hpp + rm -f ./hyprctl/hw-protocols/*.cpp ./hyprctl/hw-protocols/*.hpp all: $(MAKE) clear @@ -87,12 +88,24 @@ asan: @echo "Wayland done" patch -p1 < ./scripts/hyprlandStaticAsan.diff - cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DWITH_ASAN:STRING=True -DUSE_TRACY:STRING=False -DUSE_TRACY_GPU:STRING=False -S . -B ./build -G Ninja + cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DWITH_ASAN:STRING=True -DUSE_TRACY:STRING=False -DUSE_TRACY_GPU:STRING=False -S . -B ./build cmake --build ./build --config Debug --target all @echo "Hyprland done" ASAN_OPTIONS="detect_odr_violation=0,log_path=asan.log" HYPRLAND_NO_CRASHREPORTER=1 ./build/Hyprland -c ~/.config/hypr/hyprland.conf +format-check: + @find src hyprctl hyprpm start tests -type f \( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" \) \ + ! -path "src/render/shaders/Shaders.hpp" \ + ! -path "hyprctl/hw-protocols/*" | \ + xargs clang-format --dry-run --Werror + +format-fix: + @find src hyprctl hyprpm start tests -type f \( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" \) \ + ! -path "src/render/shaders/Shaders.hpp" \ + ! -path "hyprctl/hw-protocols/*" | \ + xargs clang-format -i + test: $(MAKE) debug ./build/hyprtester/hyprtester -c hyprtester/test.conf -b ./build/Hyprland -p hyprtester/plugin/hyprtestplugin.so diff --git a/VERSION b/VERSION index 4f9b378b4..524456c77 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.52.0 +0.54.0 diff --git a/debug-tools/flamegraph.sh b/debug-tools/flamegraph.sh new file mode 100755 index 000000000..73d54b4bf --- /dev/null +++ b/debug-tools/flamegraph.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +FLAMEGRAPH_DIR="$SCRIPT_DIR/flamegraph" + +if [ ! -d "$FLAMEGRAPH_DIR" ]; then + echo "Cloning FlameGraph tools..." + git clone https://github.com/brendangregg/FlameGraph "$FLAMEGRAPH_DIR" +fi + +if [ ! -f perf.data ]; then + echo "No perf.data found in current directory." + echo "Run Hyprland under perf first:" + echo " perf record -F 99 -g -- ./build/Hyprland" + exit 1 +fi + +echo "Generating flame graph..." +perf script | "$FLAMEGRAPH_DIR/stackcollapse-perf.pl" | "$FLAMEGRAPH_DIR/flamegraph.pl" > flame.svg +xdg-open flame.svg diff --git a/example/hyprland.conf b/example/hyprland.conf index 07b372c10..15b0e4f7b 100644 --- a/example/hyprland.conf +++ b/example/hyprland.conf @@ -27,7 +27,7 @@ monitor=,preferred,auto,auto # Set programs that you use $terminal = kitty $fileManager = dolphin -$menu = wofi --show drun +$menu = hyprlauncher ################# @@ -237,12 +237,12 @@ $mainMod = SUPER # Sets "Windows" key as main modifier # Example binds, see https://wiki.hypr.land/Configuring/Binds/ for more bind = $mainMod, Q, exec, $terminal bind = $mainMod, C, killactive, -bind = $mainMod, M, exit, +bind = $mainMod, M, exec, command -v hyprshutdown >/dev/null 2>&1 && hyprshutdown || hyprctl dispatch exit bind = $mainMod, E, exec, $fileManager bind = $mainMod, V, togglefloating, bind = $mainMod, R, exec, $menu bind = $mainMod, P, pseudo, # dwindle -bind = $mainMod, J, togglesplit, # dwindle +bind = $mainMod, J, layoutmsg, togglesplit # dwindle # Move focus with mainMod + arrow keys bind = $mainMod, left, movefocus, l @@ -329,3 +329,13 @@ windowrule { no_focus = true } + +# Hyprland-run windowrule +windowrule { + name = move-hyprland-run + + match:class = hyprland-run + + move = 20 monitor_h-120 + float = yes +} diff --git a/example/hyprland.desktop b/example/hyprland.desktop.in similarity index 75% rename from example/hyprland.desktop rename to example/hyprland.desktop.in index bb2801a94..d8e87d60e 100644 --- a/example/hyprland.desktop +++ b/example/hyprland.desktop.in @@ -1,7 +1,7 @@ [Desktop Entry] Name=Hyprland Comment=An intelligent dynamic tiling Wayland compositor -Exec=Hyprland +Exec=@PREFIX@/@CMAKE_INSTALL_BINDIR@/start-hyprland Type=Application DesktopNames=Hyprland Keywords=tiling;wayland;compositor; diff --git a/flake.lock b/flake.lock index a20ecfed7..45ecbad47 100644 --- a/flake.lock +++ b/flake.lock @@ -16,11 +16,11 @@ ] }, "locked": { - "lastModified": 1763922789, - "narHash": "sha256-XnkWjCpeXfip9tqYdL0b0zzBDjq+dgdISvEdSVGdVyA=", + "lastModified": 1776702787, + "narHash": "sha256-qc5uwEWbuubzYthmZcfCapooZGXhoYZWfTQ24TozbCQ=", "owner": "hyprwm", "repo": "aquamarine", - "rev": "a20a0e67a33b6848378a91b871b89588d3a12573", + "rev": "9a1ca6b8cb4d86a599787a55b78f2ddf809bf945", "type": "github" }, "original": { @@ -32,15 +32,15 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1761588595, - "narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=", - "owner": "edolstra", + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", + "owner": "NixOS", "repo": "flake-compat", - "rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", "type": "github" }, "original": { - "owner": "edolstra", + "owner": "NixOS", "repo": "flake-compat", "type": "github" } @@ -79,11 +79,11 @@ ] }, "locked": { - "lastModified": 1753964049, - "narHash": "sha256-lIqabfBY7z/OANxHoPeIrDJrFyYy9jAM4GQLzZ2feCM=", + "lastModified": 1776511930, + "narHash": "sha256-fCpwFiTW0rT7oKJqr3cqHMnkwypSwQKpbtUEtxdkgrM=", "owner": "hyprwm", "repo": "hyprcursor", - "rev": "44e91d467bdad8dcf8bbd2ac7cf49972540980a5", + "rev": "39435900785d0c560c6ae8777d29f28617d031ef", "type": "github" }, "original": { @@ -105,11 +105,11 @@ ] }, "locked": { - "lastModified": 1763733840, - "narHash": "sha256-JnET78yl5RvpGuDQy3rCycOCkiKoLr5DN1fPhRNNMco=", + "lastModified": 1776426399, + "narHash": "sha256-RUESLKNikIeEq9ymGJ6nmcDXiSFQpUW1IhJ245nL3xM=", "owner": "hyprwm", "repo": "hyprgraphics", - "rev": "8f1bec691b2d198c60cccabca7a94add2df4ed1a", + "rev": "68d064434787cf1ed4a2fe257c03c5f52f33cf84", "type": "github" }, "original": { @@ -144,11 +144,11 @@ ] }, "locked": { - "lastModified": 1763727565, - "narHash": "sha256-vRff/2R1U1jzPBy4OODqh2kfUzmizW/nfV2ROzTDIKo=", + "lastModified": 1776426575, + "narHash": "sha256-KI6nIfVihn/DPaeB5Et46Xg3dkNHrrEtUd5LBBVomB0=", "owner": "hyprwm", "repo": "hyprland-guiutils", - "rev": "7724d3a12a0453e7aae05f2ef39474219f05a4b4", + "rev": "a968d211048e3ed538e47b84cb3649299578f19d", "type": "github" }, "original": { @@ -167,11 +167,11 @@ ] }, "locked": { - "lastModified": 1759610243, - "narHash": "sha256-+KEVnKBe8wz+a6dTLq8YDcF3UrhQElwsYJaVaHXJtoI=", + "lastModified": 1772460177, + "narHash": "sha256-/6G/MsPvtn7bc4Y32pserBT/Z4SUUdBd4XYJpOEKVR4=", "owner": "hyprwm", "repo": "hyprland-protocols", - "rev": "bd153e76f751f150a09328dbdeb5e4fab9d23622", + "rev": "1cb6db5fd6bb8aee419f4457402fa18293ace917", "type": "github" }, "original": { @@ -193,11 +193,11 @@ ] }, "locked": { - "lastModified": 1763819661, - "narHash": "sha256-0jLarTR/BLWdGlboM86bPVP2zKJNI2jvo3JietnDkOM=", + "lastModified": 1776426736, + "narHash": "sha256-rl7i4aY+9p8LysJp7o8uRWahCkpFznCgGHXszlTw7b0=", "owner": "hyprwm", "repo": "hyprlang", - "rev": "a318deec0c12409ec39c68d2be8096b636dc2a5c", + "rev": "7833ff33b2e82d3406337b5dcf0d1cec595d83e9", "type": "github" }, "original": { @@ -238,11 +238,11 @@ ] }, "locked": { - "lastModified": 1763503177, - "narHash": "sha256-VPoiswJBBmTLVuNncvT/8FpFR+sYcAi/LgP/zTZ+5rA=", + "lastModified": 1772462885, + "narHash": "sha256-5pHXrQK9zasMnIo6yME6EOXmWGFMSnCITcfKshhKJ9I=", "owner": "hyprwm", "repo": "hyprtoolkit", - "rev": "f4e1e12755567ecf39090203b8f43eace8279630", + "rev": "9af245a69fa6b286b88ddfc340afd288e00a6998", "type": "github" }, "original": { @@ -261,11 +261,11 @@ ] }, "locked": { - "lastModified": 1763996058, - "narHash": "sha256-DsqzFZvrEV+aDmavjaD4/bk5qxeZwhGxPWBQdpFyM9Y=", + "lastModified": 1776428866, + "narHash": "sha256-XfRlBolGtjvalTHJp3XvvpYLBjkMhaZLLU0WqZ91Fcg=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "0168583075baffa083032ed13a8bea8ea12f281a", + "rev": "eedd60805cd96d4442586f2ba5fe51d549b12674", "type": "github" }, "original": { @@ -284,11 +284,11 @@ ] }, "locked": { - "lastModified": 1763640274, - "narHash": "sha256-Uan1Nl9i4TF/kyFoHnTq1bd/rsWh4GAK/9/jDqLbY5A=", + "lastModified": 1776430932, + "narHash": "sha256-Yv3RPiUvl7CAsJgwIVsqcj7akn1gLyJP1F/mocof5hA=", "owner": "hyprwm", "repo": "hyprwayland-scanner", - "rev": "f6cf414ca0e16a4d30198fd670ec86df3c89f671", + "rev": "4c2fcc06dc9722c97dbb54ba649c69b18ce83d2e", "type": "github" }, "original": { @@ -297,13 +297,39 @@ "type": "github" } }, + "hyprwire": { + "inputs": { + "hyprutils": [ + "hyprutils" + ], + "nixpkgs": [ + "nixpkgs" + ], + "systems": [ + "systems" + ] + }, + "locked": { + "lastModified": 1776728575, + "narHash": "sha256-z9eGphrArEBpl1O/GCH0wlY6z4K9vA6yWh2gAS6qytU=", + "owner": "hyprwm", + "repo": "hyprwire", + "rev": "f3a80888783702a39691b684d099e16b83ed4702", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprwire", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1763966396, - "narHash": "sha256-6eeL1YPcY1MV3DDStIDIdy/zZCDKgHdkCmsrLJFiZf0=", + "lastModified": 1776548001, + "narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5ae3b07d8d6527c42f17c876e404993199144b6a", + "rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc", "type": "github" }, "original": { @@ -322,11 +348,11 @@ ] }, "locked": { - "lastModified": 1763988335, - "narHash": "sha256-QlcnByMc8KBjpU37rbq5iP7Cp97HvjRP0ucfdh+M4Qc=", + "lastModified": 1776796298, + "narHash": "sha256-PcRvlWayisPSjd0UcRQbhG8Oqw78AcPE6x872cPRHN8=", "owner": "cachix", "repo": "git-hooks.nix", - "rev": "50b9238891e388c9fdc6a5c49e49c42533a1b5ce", + "rev": "3cfd774b0a530725a077e17354fbdb87ea1c4aad", "type": "github" }, "original": { @@ -345,6 +371,7 @@ "hyprlang": "hyprlang", "hyprutils": "hyprutils", "hyprwayland-scanner": "hyprwayland-scanner", + "hyprwire": "hyprwire", "nixpkgs": "nixpkgs", "pre-commit-hooks": "pre-commit-hooks", "systems": "systems", @@ -388,11 +415,11 @@ ] }, "locked": { - "lastModified": 1761431178, - "narHash": "sha256-xzjC1CV3+wpUQKNF+GnadnkeGUCJX+vgaWIZsnz9tzI=", + "lastModified": 1776608502, + "narHash": "sha256-UH8YoQxx4hFOm6qjMdjRQNRvSejFIR/wBZ8fW1p9sME=", "owner": "hyprwm", "repo": "xdg-desktop-portal-hyprland", - "rev": "4b8801228ff958d028f588f0c2b911dbf32297f9", + "rev": "4a293523d36dfa367e67ec304cc718ea66a8fec2", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 6799144b4..6d695bfb5 100644 --- a/flake.nix +++ b/flake.nix @@ -65,6 +65,13 @@ inputs.systems.follows = "systems"; }; + hyprwire = { + url = "github:hyprwm/hyprwire"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.systems.follows = "systems"; + inputs.hyprutils.follows = "hyprutils"; + }; + xdph = { url = "github:hyprwm/xdg-desktop-portal-hyprland"; inputs.nixpkgs.follows = "nixpkgs"; @@ -81,107 +88,122 @@ }; }; - outputs = inputs @ { - self, - nixpkgs, - systems, - ... - }: let - inherit (nixpkgs) lib; - eachSystem = lib.genAttrs (import systems); - pkgsFor = eachSystem (system: - import nixpkgs { - localSystem = system; - overlays = with self.overlays; [ - hyprland-packages - hyprland-extras - ]; - }); - pkgsCrossFor = eachSystem (system: crossSystem: - import nixpkgs { - localSystem = system; - inherit crossSystem; - overlays = with self.overlays; [ - hyprland-packages - hyprland-extras - ]; - }); - pkgsDebugFor = eachSystem (system: - import nixpkgs { - localSystem = system; - overlays = with self.overlays; [ - hyprland-debug - ]; - }); - pkgsDebugCrossFor = eachSystem (system: crossSystem: - import nixpkgs { - localSystem = system; - inherit crossSystem; - overlays = with self.overlays; [ - hyprland-debug - ]; - }); - in { - overlays = import ./nix/overlays.nix {inherit self lib inputs;}; + outputs = + inputs@{ + self, + nixpkgs, + systems, + ... + }: + let + inherit (nixpkgs) lib; + eachSystem = lib.genAttrs (import systems); + pkgsFor = eachSystem ( + system: + import nixpkgs { + localSystem = system; + overlays = with self.overlays; [ + hyprland-packages + hyprland-extras + ]; + } + ); + pkgsCrossFor = eachSystem ( + system: crossSystem: + import nixpkgs { + localSystem = system; + inherit crossSystem; + overlays = with self.overlays; [ + hyprland-packages + hyprland-extras + ]; + } + ); + pkgsDebugFor = eachSystem ( + system: + import nixpkgs { + localSystem = system; + overlays = with self.overlays; [ + hyprland-debug + ]; + } + ); + pkgsDebugCrossFor = eachSystem ( + system: crossSystem: + import nixpkgs { + localSystem = system; + inherit crossSystem; + overlays = with self.overlays; [ + hyprland-debug + ]; + } + ); + in + { + overlays = import ./nix/overlays.nix { inherit self lib inputs; }; - checks = eachSystem (system: - (lib.filterAttrs - (n: _: (lib.hasPrefix "hyprland" n) && !(lib.hasSuffix "debug" n)) - self.packages.${system}) - // { - inherit (self.packages.${system}) xdg-desktop-portal-hyprland; - pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run { - src = ./.; - hooks = { - hyprland-treewide-formatter = { - enable = true; - entry = "${self.formatter.${system}}/bin/hyprland-treewide-formatter"; - pass_filenames = false; - excludes = ["subprojects"]; - always_run = true; + checks = eachSystem ( + system: + (lib.filterAttrs ( + n: _: (lib.hasPrefix "hyprland" n) && !(lib.hasSuffix "debug" n) + ) self.packages.${system}) + // { + inherit (self.packages.${system}) xdg-desktop-portal-hyprland; + pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run { + src = ./.; + hooks = { + hyprland-treewide-formatter = { + enable = true; + entry = "${self.formatter.${system}}/bin/hyprland-treewide-formatter"; + pass_filenames = false; + excludes = [ "subprojects" ]; + always_run = true; + }; }; }; - }; - } - // (import ./nix/tests inputs pkgsFor.${system})); + } + // (import ./nix/tests inputs pkgsFor.${system}) + ); - packages = eachSystem (system: { - default = self.packages.${system}.hyprland; - inherit - (pkgsFor.${system}) - # hyprland-packages - hyprland - hyprland-unwrapped - # hyprland-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/CMakeLists.txt b/hyprctl/CMakeLists.txt index db5ef6157..7071ede9f 100644 --- a/hyprctl/CMakeLists.txt +++ b/hyprctl/CMakeLists.txt @@ -5,11 +5,32 @@ project( DESCRIPTION "Control utility for Hyprland" ) -pkg_check_modules(hyprctl_deps REQUIRED IMPORTED_TARGET hyprutils>=0.2.4 re2) +pkg_check_modules(hyprctl_deps REQUIRED IMPORTED_TARGET hyprutils>=0.2.4 hyprwire re2) -add_executable(hyprctl "main.cpp") +file(GLOB_RECURSE HYPRCTL_SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "hw-protocols/*.cpp" "include/*.hpp") + +add_executable(hyprctl ${HYPRCTL_SRCFILES}) target_link_libraries(hyprctl PUBLIC PkgConfig::hyprctl_deps) +target_include_directories(hyprctl PRIVATE "hw-protocols") + +# Hyprwire + +function(hyprprotocol protoPath protoName) + set(path ${CMAKE_CURRENT_SOURCE_DIR}/${protoPath}) + add_custom_command( + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/${protoName}-client.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/${protoName}-client.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/${protoName}-spec.hpp + COMMAND hyprwire-scanner --client ${path}/${protoName}.xml + ${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/ + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + target_sources(hyprctl PRIVATE hw-protocols/${protoName}-client.cpp + hw-protocols/${protoName}-client.hpp + hw-protocols/${protoName}-spec.hpp) +endfunction() + +hyprprotocol(hw-protocols hyprpaper_core) # binary install(TARGETS hyprctl) diff --git a/hyprctl/hw-protocols/hyprpaper_core.xml b/hyprctl/hw-protocols/hyprpaper_core.xml new file mode 100644 index 000000000..3d26a1021 --- /dev/null +++ b/hyprctl/hw-protocols/hyprpaper_core.xml @@ -0,0 +1,172 @@ + + + + BSD 3-Clause License + + Copyright (c) 2025, Hypr Development + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + + This is the core manager object for hyprpaper operations + + + + + Creates a wallpaper object + + + + + + + Emitted when a new monitor is added. + + + + + + + Emitted when a monitor is removed. + + + + + + + Destroys this object. Children remain alive until destroyed. + + + + + + Creates a status object + + + + + + + + + + + + + + + + + + + + + + + + + This is an object describing a wallpaper + + + + + Set a file path for the wallpaper. This has to be an absolute path from the fs root. + This is required. + + + + + + + Set a fit mode for the wallpaper. This is set to cover by default. + + + + + + + Set a monitor for the wallpaper. Setting this to empty (or not setting at all) will + treat this as a wildcard fallback. + + See hyprpaper_core_manager.add_monitor and hyprpaper_core_manager.remove_monitor + for tracking monitor names. + + + + + + + Applies this object's state to the wallpaper state. Will emit .success on success, + and .failed on failure. + + This object becomes inert after .succeess or .failed, the only valid operation + is to destroy it afterwards. + + + + + + Wallpaper was applied successfully. + + + + + + Wallpaper was not applied. See the error field for more information. + + + + + + + Destroys this object. + + + + + + + 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/Strings.hpp b/hyprctl/src/Strings.hpp similarity index 96% rename from hyprctl/Strings.hpp rename to hyprctl/src/Strings.hpp index 67e4f992c..549d84bb6 100644 --- a/hyprctl/Strings.hpp +++ b/hyprctl/src/Strings.hpp @@ -74,11 +74,8 @@ flags: const std::string_view HYPRPAPER_HELP = R"#(usage: hyprctl [flags] hyprpaper requests: - listactive → Lists all active images - listloaded → Lists all loaded images - preload → Preloads image - unload → Unloads image. Pass 'all' as path to unload all images - wallpaper → Issue a wallpaper to call a config wallpaper dynamically + wallpaper → Issue a wallpaper to call a config wallpaper dynamically. + Arguments are [mon],[path],[fit_mode]. Fit mode is optional. flags: See 'hyprctl --help')#"; diff --git a/hyprctl/src/helpers/Memory.hpp b/hyprctl/src/helpers/Memory.hpp new file mode 100644 index 000000000..1d3a9e07c --- /dev/null +++ b/hyprctl/src/helpers/Memory.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include +#include +#include + +using namespace Hyprutils::Memory; + +#define SP CSharedPointer +#define WP CWeakPointer +#define UP CUniquePointer diff --git a/hyprctl/src/hyprpaper/Hyprpaper.cpp b/hyprctl/src/hyprpaper/Hyprpaper.cpp new file mode 100644 index 000000000..93f4182a8 --- /dev/null +++ b/hyprctl/src/hyprpaper/Hyprpaper.cpp @@ -0,0 +1,208 @@ +#include "Hyprpaper.hpp" +#include "../helpers/Memory.hpp" + +#include +#include +#include +#include + +#include + +#include +using namespace Hyprutils::String; + +using namespace std::string_literals; + +constexpr const char* SOCKET_NAME = ".hyprpaper.sock"; +static SP g_coreImpl; + +constexpr const uint32_t PROTOCOL_VERSION_SUPPORTED = 2; + +// +static hyprpaperCoreWallpaperFitMode fitFromString(const std::string_view& sv) { + if (sv == "contain") + return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_CONTAIN; + if (sv == "fit" || sv == "stretch") + return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_STRETCH; + if (sv == "tile") + return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_TILE; + return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_COVER; +} + +static std::expected resolvePath(const std::string_view& sv) { + std::error_code ec; + auto can = std::filesystem::canonical(sv, ec); + + if (ec) + return std::unexpected(std::format("invalid path: {}", ec.message())); + + return can; +} + +static std::expected getFullPath(const std::string_view& sv) { + if (sv.empty()) + return std::unexpected("empty path"); + + if (sv[0] == '~') { + static auto HOME = getenv("HOME"); + if (!HOME || HOME[0] == '\0') + return std::unexpected("home path but no $HOME"); + + return resolvePath(std::string{HOME} + "/"s + std::string{sv.substr(1)}); + } + + return resolvePath(sv); +} + +static std::expected doWallpaper(const std::string_view& RHS) { + CVarList2 args(std::string{RHS}, 0, ','); + + const std::string MONITOR = std::string{args[0]}; + const auto& PATH_RAW = args[1]; + const auto& FIT = args[2]; + + if (PATH_RAW.empty()) + return std::unexpected("not enough args"); + + 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)"); + + const auto PATH = getFullPath(PATH_RAW); + + if (!PATH) + return std::unexpected(std::format("bad path: {}", PATH_RAW)); + + 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?!"); + + 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 wallpaper = makeShared(manager->sendGetWallpaperObject()); + + if (!wallpaper) + return std::unexpected("wire error: couldn't create wallpaper object"); + + bool canExit = false; + std::optional err; + + 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; + } + }); + wallpaper->setSuccess([&canExit]() { canExit = true; }); + + wallpaper->sendPath(PATH->c_str()); + wallpaper->sendMonitorName(MONITOR.c_str()); + if (!FIT.empty()) + wallpaper->sendFitMode(fitFromString(FIT)); + + wallpaper->sendApply(); + + while (!canExit) { + socket->dispatchEvents(true); + } + + if (err) + 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 {}; +} diff --git a/hyprctl/src/hyprpaper/Hyprpaper.hpp b/hyprctl/src/hyprpaper/Hyprpaper.hpp new file mode 100644 index 000000000..167b0a8d5 --- /dev/null +++ b/hyprctl/src/hyprpaper/Hyprpaper.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +namespace Hyprpaper { + std::expected makeHyprpaperRequest(const std::string_view& rq); +}; \ No newline at end of file diff --git a/hyprctl/main.cpp b/hyprctl/src/main.cpp similarity index 95% rename from hyprctl/main.cpp rename to hyprctl/src/main.cpp index e15a17f5f..0a33f3ed8 100644 --- a/hyprctl/main.cpp +++ b/hyprctl/src/main.cpp @@ -31,6 +31,7 @@ using namespace Hyprutils::String; using namespace Hyprutils::Memory; #include "Strings.hpp" +#include "hyprpaper/Hyprpaper.hpp" std::string instanceSignature; bool quiet = false; @@ -227,23 +228,23 @@ int request(std::string_view arg, int minArgs = 0, bool needRoll = false) { constexpr size_t BUFFER_SIZE = 8192; char buffer[BUFFER_SIZE] = {0}; - sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE); - - if (sizeWritten < 0) { - if (errno == EWOULDBLOCK) - log("Hyprland IPC didn't respond in time\n"); - log("Couldn't read (6)"); - return 6; - } - - reply += std::string(buffer, sizeWritten); - - while (sizeWritten == BUFFER_SIZE) { + // read all data until server closes the connection + // this handles partial writes on the server side under high load + while (true) { sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE); + if (sizeWritten < 0) { + if (errno == EWOULDBLOCK) + log("Hyprland IPC didn't respond in time\n"); log("Couldn't read (6)"); return 6; } + + if (sizeWritten == 0) { + // server closed connection, we're done + break; + } + reply += std::string(buffer, sizeWritten); } @@ -305,10 +306,6 @@ int requestIPC(std::string_view filename, std::string_view arg) { return 0; } -int requestHyprpaper(std::string_view arg) { - return requestIPC(".hyprpaper.sock", arg); -} - int requestHyprsunset(std::string_view arg) { return requestIPC(".hyprsunset.sock", arg); } @@ -500,9 +497,12 @@ int main(int argc, char** argv) { if (fullRequest.contains("/--batch")) batchRequest(fullRequest, json); - else if (fullRequest.contains("/hyprpaper")) - exitStatus = requestHyprpaper(fullRequest); - else if (fullRequest.contains("/hyprsunset")) + else if (fullRequest.contains("/hyprpaper")) { + auto result = Hyprpaper::makeHyprpaperRequest(fullRequest); + if (!result) + log(std::format("error: {}", result.error())); + exitStatus = !result; + } else if (fullRequest.contains("/hyprsunset")) exitStatus = requestHyprsunset(fullRequest); else if (fullRequest.contains("/switchxkblayout")) exitStatus = request(fullRequest, 2); diff --git a/hyprland.pc.in b/hyprland.pc.in index 81a0ef114..0629d2d34 100644 --- a/hyprland.pc.in +++ b/hyprland.pc.in @@ -4,4 +4,5 @@ Name: Hyprland URL: https://github.com/hyprwm/Hyprland Description: Hyprland header files Version: @HYPRLAND_VERSION@ -Cflags: -I${prefix} -I${prefix}/hyprland/protocols -I${prefix}/hyprland +Requires: aquamarine >= @AQUAMARINE_MINIMUM_VERSION@, hyprcursor >= @HYPRCURSOR_MINIMUM_VERSION@, hyprgraphics >= @HYPRGRAPHICS_MINIMUM_VERSION@, hyprlang >= @HYPRLANG_MINIMUM_VERSION@, hyprutils >= @HYPRUTILS_MINIMUM_VERSION@, libdrm, egl, cairo, xkbcommon >= @XKBCOMMON_MINIMUM_VERSION@, libinput >= @LIBINPUT_MINIMUM_VERSION@, wayland-server >= @WAYLAND_SERVER_MINIMUM_VERSION@@PKGCONFIG_XWAYLAND_DEPENDENCIES@ +Cflags: -I${prefix} -I${prefix}/hyprland/protocols -I${prefix}/hyprland -I${prefix}/hyprland/src diff --git a/hyprpm/CMakeLists.txt b/hyprpm/CMakeLists.txt index f2e0b2236..aefb71bb1 100644 --- a/hyprpm/CMakeLists.txt +++ b/hyprpm/CMakeLists.txt @@ -11,16 +11,17 @@ set(CMAKE_CXX_STANDARD 23) pkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.7.0) -find_package(glaze QUIET) +find_package(glaze 7...<8 QUIET) if (NOT glaze_FOUND) - set(GLAZE_VERSION v5.1.1) - message(STATUS "glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent") + set(GLAZE_VERSION v7.2.0) + message(STATUS "hyprpm: glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent") include(FetchContent) FetchContent_Declare( glaze 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/DataState.cpp b/hyprpm/src/core/DataState.cpp index 131146a16..64f3cfa02 100644 --- a/hyprpm/src/core/DataState.cpp +++ b/hyprpm/src/core/DataState.cpp @@ -93,6 +93,7 @@ void DataState::addNewPluginRepo(const SPluginRepository& repo) { auto DATA = toml::table{ {"repository", toml::table{ {"name", repo.name}, + {"author", repo.author}, {"hash", repo.hash}, {"url", repo.url}, {"rev", repo.rev} @@ -122,31 +123,32 @@ void DataState::addNewPluginRepo(const SPluginRepository& repo) { Debug::die("{}", failureString("Failed to write plugin state")); } -bool DataState::pluginRepoExists(const std::string& urlOrName) { +bool DataState::pluginRepoExists(const SPluginRepoIdentifier identifier) { ensureStateStoreExists(); for (const auto& stateFile : getPluginStates()) { - const auto STATE = toml::parse_file(stateFile.c_str()); - const auto NAME = STATE["repository"]["name"].value_or(""); - const auto URL = STATE["repository"]["url"].value_or(""); + const auto STATE = toml::parse_file(stateFile.c_str()); + const auto NAME = STATE["repository"]["name"].value_or(""); + const auto AUTHOR = STATE["repository"]["author"].value_or(""); + const auto URL = STATE["repository"]["url"].value_or(""); - if (URL == urlOrName || NAME == urlOrName) + if (identifier.matches(URL, NAME, AUTHOR)) return true; } return false; } -void DataState::removePluginRepo(const std::string& urlOrName) { +void DataState::removePluginRepo(const SPluginRepoIdentifier identifier) { ensureStateStoreExists(); for (const auto& stateFile : getPluginStates()) { - const auto STATE = toml::parse_file(stateFile.c_str()); - const auto NAME = STATE["repository"]["name"].value_or(""); - const auto URL = STATE["repository"]["url"].value_or(""); - - if (URL == urlOrName || NAME == urlOrName) { + const auto STATE = toml::parse_file(stateFile.c_str()); + const auto NAME = STATE["repository"]["name"].value_or(""); + const auto AUTHOR = STATE["repository"]["author"].value_or(""); + const auto URL = STATE["repository"]["url"].value_or(""); + if (identifier.matches(URL, NAME, AUTHOR)) { // unload the plugins!! for (const auto& file : std::filesystem::directory_iterator(stateFile.parent_path())) { if (!file.path().string().ends_with(".so")) @@ -181,7 +183,7 @@ void DataState::updateGlobalState(const SGlobalState& state) { // clang-format off auto DATA = toml::table{ {"state", toml::table{ - {"hash", state.headersHashCompiled}, + {"hash", state.headersAbiCompiled}, {"dont_warn_install", state.dontWarnInstall} }} }; @@ -206,8 +208,8 @@ SGlobalState DataState::getGlobalState() { auto DATA = toml::parse_file(stateFile.c_str()); SGlobalState state; - state.headersHashCompiled = DATA["state"]["hash"].value_or(""); - state.dontWarnInstall = DATA["state"]["dont_warn_install"].value_or(false); + state.headersAbiCompiled = DATA["state"]["hash"].value_or(""); + state.dontWarnInstall = DATA["state"]["dont_warn_install"].value_or(false); return state; } @@ -219,16 +221,18 @@ std::vector DataState::getAllRepositories() { for (const auto& stateFile : getPluginStates()) { const auto STATE = toml::parse_file(stateFile.c_str()); - const auto NAME = STATE["repository"]["name"].value_or(""); - const auto URL = STATE["repository"]["url"].value_or(""); - const auto REV = STATE["repository"]["rev"].value_or(""); - const auto HASH = STATE["repository"]["hash"].value_or(""); + const auto NAME = STATE["repository"]["name"].value_or(""); + const auto AUTHOR = STATE["repository"]["author"].value_or(""); + const auto URL = STATE["repository"]["url"].value_or(""); + const auto REV = STATE["repository"]["rev"].value_or(""); + const auto HASH = STATE["repository"]["hash"].value_or(""); SPluginRepository repo; - repo.hash = HASH; - repo.name = NAME; - repo.url = URL; - repo.rev = REV; + repo.hash = HASH; + repo.name = NAME; + repo.author = AUTHOR; + repo.url = URL; + repo.rev = REV; for (const auto& [key, val] : STATE) { if (key == "repository") @@ -247,7 +251,7 @@ std::vector DataState::getAllRepositories() { return repos; } -bool DataState::setPluginEnabled(const std::string& name, bool enabled) { +bool DataState::setPluginEnabled(const SPluginRepoIdentifier identifier, bool enabled) { ensureStateStoreExists(); for (const auto& stateFile : getPluginStates()) { @@ -256,8 +260,17 @@ bool DataState::setPluginEnabled(const std::string& name, bool enabled) { if (key == "repository") continue; - if (key.str() != name) - continue; + switch (identifier.type) { + case IDENTIFIER_NAME: + if (key.str() != identifier.name) + continue; + break; + case IDENTIFIER_AUTHOR_NAME: + if (STATE["repository"]["author"] != identifier.author || key.str() != identifier.name) + continue; + break; + default: return false; + } const auto FAILED = STATE[key]["failed"].value_or(false); diff --git a/hyprpm/src/core/DataState.hpp b/hyprpm/src/core/DataState.hpp index c35ded064..d9872b907 100644 --- a/hyprpm/src/core/DataState.hpp +++ b/hyprpm/src/core/DataState.hpp @@ -5,8 +5,8 @@ #include "Plugin.hpp" struct SGlobalState { - std::string headersHashCompiled = ""; - bool dontWarnInstall = false; + std::string headersAbiCompiled = ""; + bool dontWarnInstall = false; }; namespace DataState { @@ -15,11 +15,11 @@ namespace DataState { std::vector getPluginStates(); void ensureStateStoreExists(); void addNewPluginRepo(const SPluginRepository& repo); - void removePluginRepo(const std::string& urlOrName); - bool pluginRepoExists(const std::string& urlOrName); + void removePluginRepo(const SPluginRepoIdentifier identifier); + bool pluginRepoExists(const SPluginRepoIdentifier identifier); void updateGlobalState(const SGlobalState& state); void purgeAllCache(); SGlobalState getGlobalState(); - bool setPluginEnabled(const std::string& name, bool enabled); + bool setPluginEnabled(const SPluginRepoIdentifier identifier, bool enabled); std::vector getAllRepositories(); -}; \ No newline at end of file +}; diff --git a/hyprpm/src/core/Plugin.cpp b/hyprpm/src/core/Plugin.cpp new file mode 100644 index 000000000..3aaa85925 --- /dev/null +++ b/hyprpm/src/core/Plugin.cpp @@ -0,0 +1,48 @@ +#include "Plugin.hpp" + +SPluginRepoIdentifier SPluginRepoIdentifier::fromUrl(const std::string& url) { + return SPluginRepoIdentifier{.type = IDENTIFIER_URL, .url = url}; +} + +SPluginRepoIdentifier SPluginRepoIdentifier::fromName(const std::string& name) { + return SPluginRepoIdentifier{.type = IDENTIFIER_NAME, .name = name}; +} + +SPluginRepoIdentifier SPluginRepoIdentifier::fromAuthorName(const std::string& author, const std::string& name) { + return SPluginRepoIdentifier{.type = IDENTIFIER_AUTHOR_NAME, .name = name, .author = author}; +} + +SPluginRepoIdentifier SPluginRepoIdentifier::fromString(const std::string& string) { + if (string.find(':') != std::string::npos) { + return SPluginRepoIdentifier{.type = IDENTIFIER_URL, .url = string}; + } else { + auto slashPos = string.find('/'); + if (slashPos != std::string::npos) { + std::string author = string.substr(0, slashPos); + std::string name = string.substr(slashPos + 1, string.size() - slashPos - 1); + return SPluginRepoIdentifier{.type = IDENTIFIER_AUTHOR_NAME, .name = name, .author = author}; + } else { + return SPluginRepoIdentifier{.type = IDENTIFIER_NAME, .name = string}; + } + } +} + +std::string SPluginRepoIdentifier::toString() const { + switch (type) { + case IDENTIFIER_NAME: return name; + case IDENTIFIER_AUTHOR_NAME: return author + '/' + name; + case IDENTIFIER_URL: return url; + } + + return ""; +} + +bool SPluginRepoIdentifier::matches(const std::string& url, const std::string& name, const std::string& author) const { + switch (type) { + case IDENTIFIER_URL: return this->url == url; + case IDENTIFIER_NAME: return this->name == name; + case IDENTIFIER_AUTHOR_NAME: return this->author == author && this->name == name; + } + + return false; +} diff --git a/hyprpm/src/core/Plugin.hpp b/hyprpm/src/core/Plugin.hpp index e66031c98..a8c740848 100644 --- a/hyprpm/src/core/Plugin.hpp +++ b/hyprpm/src/core/Plugin.hpp @@ -14,6 +14,27 @@ struct SPluginRepository { std::string url; std::string rev; std::string name; + std::string author; std::vector plugins; std::string hash; -}; \ No newline at end of file +}; + +enum ePluginRepoIdentifierType { + IDENTIFIER_URL, + IDENTIFIER_NAME, + IDENTIFIER_AUTHOR_NAME +}; + +struct SPluginRepoIdentifier { + ePluginRepoIdentifierType type; + std::string url = ""; + std::string name = ""; + std::string author = ""; + + static SPluginRepoIdentifier fromString(const std::string& string); + static SPluginRepoIdentifier fromUrl(const std::string& Url); + static SPluginRepoIdentifier fromName(const std::string& name); + static SPluginRepoIdentifier fromAuthorName(const std::string& author, const std::string& name); + std::string toString() const; + bool matches(const std::string& url, const std::string& name, const std::string& author) const; +}; diff --git a/hyprpm/src/core/PluginManager.cpp b/hyprpm/src/core/PluginManager.cpp index 9dea8bf40..6621a49fd 100644 --- a/hyprpm/src/core/PluginManager.cpp +++ b/hyprpm/src/core/PluginManager.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -78,40 +79,33 @@ SHyprlandVersion CPluginManager::getHyprlandVersion(bool running) { else onceInstalled = true; - const auto HLVERCALL = running ? NHyprlandSocket::send("/version") : execAndGet("Hyprland --version"); - if (m_bVerbose) - std::println("{}", verboseString("{} version returned: {}", running ? "running" : "installed", HLVERCALL)); + const auto HLVERCALL = running ? NHyprlandSocket::send("j/version") : execAndGet("Hyprland --version-json"); - if (!HLVERCALL.contains("Tag:")) { - std::println(stderr, "\n{}", failureString("You don't seem to be running Hyprland.")); + auto jsonQuery = glz::read_json(HLVERCALL); + + if (!jsonQuery) { + std::println("{}", failureString("failed to get the current hyprland version. Are you running hyprland?")); return SHyprlandVersion{}; } - std::string hlcommit = HLVERCALL.substr(HLVERCALL.find("at commit") + 10); - hlcommit = hlcommit.substr(0, hlcommit.find_first_of(' ')); + auto hlbranch = (*jsonQuery)["branch"].get_string(); + auto hlcommit = (*jsonQuery)["commit"].get_string(); + auto abiHash = (*jsonQuery)["abiHash"].get_string(); + auto hldate = (*jsonQuery)["commit_date"].get_string(); + auto hlcommits = (*jsonQuery)["commits"].get_string(); - std::string hlbranch = HLVERCALL.substr(HLVERCALL.find("from branch") + 12); - hlbranch = hlbranch.substr(0, hlbranch.find(" at commit ")); + 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"}; }); - std::string hldate = HLVERCALL.substr(HLVERCALL.find("Date: ") + 6); - hldate = hldate.substr(0, hldate.find('\n')); - - std::string hlcommits; - - if (HLVERCALL.contains("commits:")) { - hlcommits = HLVERCALL.substr(HLVERCALL.find("commits:") + 9); - hlcommits = hlcommits.substr(0, hlcommits.find(' ')); - } - - int commits = 0; + size_t commits = 0; try { - commits = std::stoi(hlcommits); + commits = std::stoull(hlcommits); } catch (...) { ; } if (m_bVerbose) - std::println("{}", verboseString("parsed commit {} at branch {} on {}, commits {}", hlcommit, hlbranch, hldate, commits)); + std::println("{}", verboseString("parsed commit {} at branch {} on {}, commits {}, nix: {}", hlcommit, hlbranch, hldate, commits, isNix)); - auto ver = SHyprlandVersion{hlbranch, hlcommit, hldate, commits}; + auto ver = SHyprlandVersion{hlbranch, hlcommit, hldate, abiHash, commits, isNix}; if (running) verRunning = ver; @@ -137,16 +131,24 @@ 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 (!hasDeps()) { - std::println(stderr, "\n{}", - failureString("Could not clone the plugin repository. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio, pkg-config, git, g++, gcc")); + if (!validArg(url) || !validArg(rev)) { + std::println(stderr, "\n{}", failureString("url or rev invalid")); return false; } - if (DataState::pluginRepoExists(url)) { + 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")); + return false; + } + + if (DataState::pluginRepoExists(SPluginRepoIdentifier::fromUrl(url))) { std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. Repository already installed.")); return false; } @@ -161,7 +163,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& DataState::updateGlobalState(GLOBALSTATE); } - if (GLOBALSTATE.headersHashCompiled.empty()) { + if (GLOBALSTATE.headersAbiCompiled.empty()) { std::println("\n{}", failureString("Cannot find headers in the global state. Try running hyprpm update first.")); return false; } @@ -205,7 +207,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& progress.printMessageAbove(infoString("Cloning {}", url)); - std::string ret = execAndGet(std::format("cd {} && git clone --recursive {} {}", getTempRoot(), url, USERNAME)); + std::string ret = execAndGet(std::format("cd {} && git clone --recursive '{}' {}", getTempRoot(), url, USERNAME)); if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) { std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. shell returned:\n{}", ret)); @@ -312,8 +314,14 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& progress.printMessageAbove(infoString("Building {}", p.name)); for (auto const& bs : p.buildSteps) { - const std::string& cmd = std::format("cd {} && PKG_CONFIG_PATH=\"{}/share/pkgconfig\" {}", m_szWorkingPluginDirectory, DataState::getHeadersPath(), bs); - out += " -> " + cmd + "\n" + execAndGet(cmd) + "\n"; + const auto CMD_RAW = nixDevelopIfNeeded(std::format("cd {} && PKG_CONFIG_PATH=\"{}\" {}", m_szWorkingPluginDirectory, getPkgConfigPath(), bs), HLVER); + + if (!CMD_RAW) { + progress.printMessageAbove(failureString("Failed to build {}: {}", p.name, CMD_RAW.error())); + break; + } + + out += " -> " + *CMD_RAW + "\n" + execAndGet(*CMD_RAW) + "\n"; } if (m_bVerbose) @@ -343,10 +351,13 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& std::string repohash = execAndGet("cd " + m_szWorkingPluginDirectory + " && git rev-parse HEAD"); if (repohash.length() > 0) repohash.pop_back(); - repo.name = pManifest->m_repository.name.empty() ? url.substr(url.find_last_of('/') + 1) : pManifest->m_repository.name; - repo.url = url; - repo.rev = rev; - repo.hash = repohash; + auto lastSlash = url.find_last_of('/'); + auto secondLastSlash = url.find_last_of('/', lastSlash - 1); + repo.name = pManifest->m_repository.name.empty() ? url.substr(lastSlash + 1) : pManifest->m_repository.name; + repo.author = url.substr(secondLastSlash + 1, lastSlash - secondLastSlash - 1); + repo.url = url; + repo.rev = rev; + repo.hash = repohash; for (auto const& p : pManifest->m_plugins) { repo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, false, p.failed}); } @@ -366,13 +377,13 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& return true; } -bool CPluginManager::removePluginRepo(const std::string& urlOrName) { - if (!DataState::pluginRepoExists(urlOrName)) { +bool CPluginManager::removePluginRepo(const SPluginRepoIdentifier identifier) { + if (!DataState::pluginRepoExists(identifier)) { std::println(stderr, "\n{}", failureString("Could not remove the repository. Repository is not installed.")); return false; } - std::cout << Colors::YELLOW << "!" << Colors::RESET << Colors::RED << " removing a plugin repository: " << Colors::RESET << urlOrName << "\n " + std::cout << Colors::YELLOW << "!" << Colors::RESET << Colors::RED << " removing a plugin repository: " << Colors::RESET << identifier.toString() << "\n " << "Are you sure? [Y/n] "; std::fflush(stdout); std::string input; @@ -383,7 +394,7 @@ bool CPluginManager::removePluginRepo(const std::string& urlOrName) { return false; } - DataState::removePluginRepo(urlOrName); + DataState::removePluginRepo(identifier); return true; } @@ -395,7 +406,7 @@ eHeadersErrors CPluginManager::headersValid() { return HEADERS_MISSING; // find headers commit - const std::string& cmd = std::format("PKG_CONFIG_PATH=\"{}/share/pkgconfig\" pkgconf --cflags --keep-system-cflags hyprland", DataState::getHeadersPath()); + const std::string& cmd = std::format("PKG_CONFIG_PATH=\"{}\" pkgconf --cflags --keep-system-cflags hyprland", getPkgConfigPath()); auto headers = execAndGet(cmd); if (!headers.contains("-I/")) @@ -444,17 +455,22 @@ eHeadersErrors CPluginManager::headersValid() { if (hash != HLVER.hash) return HEADERS_MISMATCHED; + // check ABI hash too + const auto GLOBALSTATE = DataState::getGlobalState(); + + if (GLOBALSTATE.headersAbiCompiled != HLVER.abiHash) + return HEADERS_ABI_MISMATCH; + return HEADERS_OK; } bool CPluginManager::updateHeaders(bool force) { - DataState::ensureStateStoreExists(); const auto HLVER = getHyprlandVersion(false); if (!hasDeps()) { - std::println("\n{}", failureString("Could not update. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio, pkg-config, git, g++, gcc")); + std::println("\n{}", failureString("Could not update. Dependencies not satisfied. Hyprpm requires: cmake, cpio, pkg-config, git, g++, gcc")); return false; } @@ -496,11 +512,11 @@ bool CPluginManager::updateHeaders(bool force) { progress.printMessageAbove(verboseString("will shallow since: {}", SHALLOW_DATE)); std::string ret = - execAndGet(std::format("cd {} && git clone --recursive {} hyprland-{}{}", getTempRoot(), HL_URL, USERNAME, (bShallow ? " --shallow-since='" + SHALLOW_DATE + "'" : ""))); + execAndGet(std::format("cd {} && git clone --recursive '{}' hyprland-{}{}", getTempRoot(), HL_URL, USERNAME, (bShallow ? " --shallow-since='" + SHALLOW_DATE + "'" : ""))); if (!std::filesystem::exists(WORKINGDIR)) { progress.printMessageAbove(failureString("Clone failed. Retrying without shallow.")); - ret = execAndGet(std::format("cd {} && git clone --recursive {} hyprland-{}", getTempRoot(), HL_URL, USERNAME)); + ret = execAndGet(std::format("cd {} && git clone --recursive '{}' hyprland-{}", getTempRoot(), HL_URL, USERNAME)); } if (!std::filesystem::exists(WORKINGDIR + "/.git")) { @@ -543,8 +559,17 @@ bool CPluginManager::updateHeaders(bool force) { if (m_bVerbose) progress.printMessageAbove(verboseString("setting PREFIX for cmake to {}", DataState::getHeadersPath())); - ret = execAndGet(std::format("cd {} && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=\"{}\" -S . -B ./build -G Ninja", WORKINGDIR, - DataState::getHeadersPath())); + const auto CONFIGURE_CMD = + nixDevelopIfNeeded(std::format("cd {} && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=\"{}\" -S . -B ./build", WORKINGDIR, + DataState::getHeadersPath()), + HLVER); + + if (!CONFIGURE_CMD) { + std::println(stderr, "\n{}", failureString("Could not configure hyprland: {}", CONFIGURE_CMD.error())); + return false; + } + + ret = execAndGet(*CONFIGURE_CMD); if (m_bVerbose) progress.printMessageAbove(verboseString("cmake returned: {}", ret)); @@ -589,14 +614,15 @@ bool CPluginManager::updateHeaders(bool force) { std::filesystem::remove_all(WORKINGDIR); auto HEADERSVALID = headersValid(); - if (HEADERSVALID == HEADERS_OK) { + + if (HEADERSVALID == HEADERS_OK || HEADERSVALID == HEADERS_MISMATCHED || HEADERSVALID == HEADERS_ABI_MISMATCH) { progress.printMessageAbove(successString("installed headers")); progress.m_iSteps = 5; progress.m_szCurrentMessage = "Done!"; progress.print(); - auto GLOBALSTATE = DataState::getGlobalState(); - GLOBALSTATE.headersHashCompiled = HLVER.hash; + auto GLOBALSTATE = DataState::getGlobalState(); + GLOBALSTATE.headersAbiCompiled = HLVER.abiHash; DataState::updateGlobalState(GLOBALSTATE); std::print("\n"); @@ -631,7 +657,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { const auto HLVER = getHyprlandVersion(false); CProgressBar progress; - progress.m_iMaxSteps = REPOS.size() * 2 + 2; + progress.m_iMaxSteps = (REPOS.size() * 2) + 2; progress.m_iSteps = 0; progress.m_szCurrentMessage = "Updating repositories"; progress.print(); @@ -652,7 +678,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { progress.printMessageAbove(infoString("Cloning {}", repo.url)); - std::string ret = execAndGet(std::format("cd {} && git clone --recursive {} {}", getTempRoot(), repo.url, USERNAME)); + std::string ret = execAndGet(std::format("cd {} && git clone --recursive '{}' {}", getTempRoot(), repo.url, USERNAME)); if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) { std::println("{}", failureString("could not clone repo: shell returned: {}", ret)); @@ -662,7 +688,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { if (!repo.rev.empty()) { progress.printMessageAbove(infoString("Plugin has revision set, resetting: {}", repo.rev)); - std::string ret = execAndGet("git -C " + m_szWorkingPluginDirectory + " reset --hard --recurse-submodules " + repo.rev); + std::string ret = execAndGet("git -C " + m_szWorkingPluginDirectory + " reset --hard --recurse-submodules \'" + repo.rev + "\'"); if (ret.compare(0, 6, "fatal:") == 0) { std::println(stderr, "\n{}", failureString("could not check out revision {}: shell returned:\n{}", repo.rev, ret)); @@ -741,8 +767,14 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { progress.printMessageAbove(infoString("Building {}", p.name)); for (auto const& bs : p.buildSteps) { - const std::string& cmd = std::format("cd {} && PKG_CONFIG_PATH=\"{}/share/pkgconfig\" {}", m_szWorkingPluginDirectory, DataState::getHeadersPath(), bs); - out += " -> " + cmd + "\n" + execAndGet(cmd) + "\n"; + const auto CMD_RAW = nixDevelopIfNeeded(std::format("cd {} && PKG_CONFIG_PATH=\"{}\" {}", m_szWorkingPluginDirectory, getPkgConfigPath(), bs), HLVER); + + if (!CMD_RAW) { + progress.printMessageAbove(failureString("Failed to build {}: {}", p.name, CMD_RAW.error())); + break; + } + + out += " -> " + *CMD_RAW + "\n" + execAndGet(*CMD_RAW) + "\n"; } if (m_bVerbose) @@ -772,10 +804,10 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { repohash.pop_back(); newrepo.hash = repohash; for (auto const& p : pManifest->m_plugins) { - const auto OLDPLUGINIT = std::find_if(repo.plugins.begin(), repo.plugins.end(), [&](const auto& other) { return other.name == p.name; }); - newrepo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false}); + const auto OLDPLUGINIT = std::ranges::find_if(repo.plugins, [&](const auto& other) { return other.name == p.name; }); + newrepo.plugins.emplace_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false}); } - DataState::removePluginRepo(newrepo.name); + DataState::removePluginRepo(SPluginRepoIdentifier::fromName(newrepo.name)); DataState::addNewPluginRepo(newrepo); std::filesystem::remove_all(m_szWorkingPluginDirectory); @@ -787,8 +819,8 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { progress.m_szCurrentMessage = "Updating global state..."; progress.print(); - auto GLOBALSTATE = DataState::getGlobalState(); - GLOBALSTATE.headersHashCompiled = HLVER.hash; + auto GLOBALSTATE = DataState::getGlobalState(); + GLOBALSTATE.headersAbiCompiled = HLVER.abiHash; DataState::updateGlobalState(GLOBALSTATE); progress.m_iSteps++; @@ -800,17 +832,23 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { return true; } -bool CPluginManager::enablePlugin(const std::string& name) { - bool ret = DataState::setPluginEnabled(name, true); +bool CPluginManager::enablePlugin(const SPluginRepoIdentifier identifier) { + bool ret = false; + + switch (identifier.type) { + case IDENTIFIER_NAME: + case IDENTIFIER_AUTHOR_NAME: ret = DataState::setPluginEnabled(identifier, true); break; + default: return false; + } if (ret) - std::println("{}", successString("Enabled {}", name)); + std::println("{}", successString("Enabled {}", identifier.name)); return ret; } -bool CPluginManager::disablePlugin(const std::string& name) { - bool ret = DataState::setPluginEnabled(name, false); +bool CPluginManager::disablePlugin(const SPluginRepoIdentifier identifier) { + bool ret = DataState::setPluginEnabled(identifier, false); if (ret) - std::println("{}", successString("Disabled {}", name)); + std::println("{}", successString("Disabled {}", identifier.name)); return ret; } @@ -828,7 +866,7 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState(bool forceReload) } const auto HYPRPMPATH = DataState::getDataStatePath(); - const auto json = glz::read_json(NHyprlandSocket::send("j/plugins list")); + const auto json = glz::read_json(NHyprlandSocket::send("j/plugins list")); if (!json) { std::println(stderr, "PluginManager: couldn't parse plugin list output"); return LOADSTATE_FAIL; @@ -893,7 +931,7 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState(bool forceReload) if (!p.enabled) continue; - if (!forceReload && std::find_if(loadedPlugins.begin(), loadedPlugins.end(), [&](const auto& other) { return other == p.name; }) != loadedPlugins.end()) + if (!forceReload && std::ranges::find_if(loadedPlugins, [&](const auto& other) { return other == p.name; }) != loadedPlugins.end()) continue; if (!loadUnloadPlugin(HYPRPMPATH / repoForName(p.name) / p.filename, true)) { @@ -913,9 +951,9 @@ bool CPluginManager::loadUnloadPlugin(const std::string& path, bool load) { auto state = DataState::getGlobalState(); auto HLVER = getHyprlandVersion(true); - if (state.headersHashCompiled != HLVER.hash) { + if (state.headersAbiCompiled != HLVER.abiHash) { if (load) - std::println("{}", infoString("Running Hyprland version ({}) differs from plugin state ({}), please restart Hyprland.", HLVER.hash, state.headersHashCompiled)); + std::println("{}", infoString("Running Hyprland version ({}) differs from plugin state ({}), please restart Hyprland.", HLVER.hash, state.headersAbiCompiled)); return false; } @@ -931,7 +969,7 @@ void CPluginManager::listAllPlugins() { const auto REPOS = DataState::getAllRepositories(); for (auto const& r : REPOS) { - std::println("{}", infoString("Repository {}:", r.name)); + std::println("{}", infoString("Repository {} (by {}):", r.name, r.author)); for (auto const& p : r.plugins) { std::println(" │ Plugin {}", p.name); @@ -956,6 +994,7 @@ std::string CPluginManager::headerError(const eHeadersErrors err) { case HEADERS_MISMATCHED: return failureString("Headers version mismatch. Please run hyprpm update to fix those.\n"); case HEADERS_NOT_HYPRLAND: return failureString("It doesn't seem you are running on hyprland.\n"); case HEADERS_MISSING: return failureString("Headers missing. Please run hyprpm update to fix those.\n"); + case HEADERS_ABI_MISMATCH: return failureString("ABI is mismatched. Please run hyprpm update to fix that.\n"); case HEADERS_DUPLICATED: { return failureString("Headers duplicated!!! This is a very bad sign.\n" "This could be due to e.g. installing hyprland manually while a system package of hyprland is also installed.\n" @@ -980,8 +1019,11 @@ std::string CPluginManager::headerErrorShort(const eHeadersErrors err) { } bool CPluginManager::hasDeps() { + if (!m_bNoNix && getHyprlandVersion().isNix) + return true; // dep check not needed if we are on nix + bool hasAllDeps = true; - std::vector deps = {"meson", "cpio", "cmake", "pkg-config", "g++", "gcc", "git"}; + std::vector deps = {"cpio", "cmake", "pkg-config", "g++", "gcc", "git"}; for (auto const& d : deps) { if (!execAndGet("command -v " + d).contains("/")) { @@ -992,3 +1034,92 @@ bool CPluginManager::hasDeps() { return hasAllDeps; } + +const std::string& CPluginManager::getPkgConfigPath() { + static const auto str = std::format("{}/share/pkgconfig:$PKG_CONFIG_PATH", DataState::getHeadersPath()); + return str; +} + +static std::expected getNixDevelopFromPath(const std::string& argv0) { + std::string fullStorePath; + + if (argv0.starts_with("/")) { + // we can use this directly + fullStorePath = argv0; + } else { + // use hyprpm, find in path + auto exe = NSys::findInPath("hyprpm"); + if (!exe) + return std::unexpected("hyprpm not found in PATH"); + + fullStorePath = *exe; + } + + if (fullStorePath.empty() || !fullStorePath.ends_with("/bin/hyprpm")) + return std::unexpected("couldn't get a real path for hyprpm (1)"); + + // canonicalize to get the real nix-store path + std::error_code ec; + fullStorePath = std::filesystem::canonical(fullStorePath, ec); + + if (ec || fullStorePath.empty() || !fullStorePath.starts_with("/nix")) + return std::unexpected("couldn't get a real path for hyprpm"); + + fullStorePath = fullStorePath.substr(0, fullStorePath.length() - std::string_view{"/bin/hyprpm"}.length()); + + auto deriver = trim(execAndGet(std::format("echo \"$(nix-store --query --deriver '{}')\"", fullStorePath))); + + if (deriver.starts_with("unknown")) + return std::unexpected("couldn't nix deriver"); + + return deriver; +} + +static std::expected getNixDevelopFromProfile() { + const auto NIX_PROFILE_STR = execAndGet("nix profile list --json"); + + auto rawJson = glz::read_json(NIX_PROFILE_STR); + + if (!rawJson) + return std::unexpected("failed to parse nix profile list --json"); + + auto& json = *rawJson; + + if (!json.contains("elements") || !json["elements"].is_object()) + return std::unexpected("nix profile list --json returned a wonky json"); + + if (!json["elements"].contains("hyprland") && !json["elements"].contains("Hyprland")) + return std::unexpected("nix profile list --json doesn't contain Hyprland (did you uninstall?)"); + + auto& hyprlandJson = json["elements"].contains("hyprland") ? json["elements"]["hyprland"] : json["elements"]["Hyprland"]; + + if (!hyprlandJson.contains("originalUrl")) + return std::unexpected("nix profile list --json's hyprland doesn't contain originalUrl?"); + + return hyprlandJson["originalUrl"].get_string(); +} + +std::expected CPluginManager::nixDevelopIfNeeded(const std::string& cmd, const SHyprlandVersion& ver) { + if (m_bNoNix || !ver.isNix) + return cmd; + + // Escape single quotes + std::string newCmd = cmd; + replaceInString(newCmd, "'", "\\'"); + + auto NIX_DEVELOP = getNixDevelopFromPath(m_szArgv0); + + if (NIX_DEVELOP) + return std::format("nix develop '{}' --command bash -c $'{}'", *NIX_DEVELOP, newCmd); + else if (m_bVerbose) + std::println("{}", verboseString("Failed nix from path: {}", NIX_DEVELOP.error())); + + NIX_DEVELOP = getNixDevelopFromProfile(); + + if (NIX_DEVELOP) + return std::format("nix develop '{}' --command bash -c $'{}'", *NIX_DEVELOP, newCmd); + else if (m_bVerbose) + std::println("{}", verboseString("Failed nix from profile: {}", NIX_DEVELOP.error())); + + return std::unexpected("hyprland is nix, but hyprpm failed to obtain a nix develop shell for build cmd"); +} diff --git a/hyprpm/src/core/PluginManager.hpp b/hyprpm/src/core/PluginManager.hpp index e0ed1203c..25878f54f 100644 --- a/hyprpm/src/core/PluginManager.hpp +++ b/hyprpm/src/core/PluginManager.hpp @@ -2,8 +2,10 @@ #include #include +#include #include -#include +#include +#include "Plugin.hpp" enum eHeadersErrors { HEADERS_OK = 0, @@ -11,6 +13,7 @@ enum eHeadersErrors { HEADERS_MISSING, HEADERS_CORRUPTED, HEADERS_MISMATCHED, + HEADERS_ABI_MISMATCH, HEADERS_DUPLICATED }; @@ -36,7 +39,9 @@ struct SHyprlandVersion { std::string branch; std::string hash; std::string date; + std::string abiHash; int commits = 0; + bool isNix = false; }; class CPluginManager { @@ -44,7 +49,7 @@ class CPluginManager { CPluginManager(); bool addNewPluginRepo(const std::string& url, const std::string& rev); - bool removePluginRepo(const std::string& urlOrName); + bool removePluginRepo(const SPluginRepoIdentifier identifier); eHeadersErrors headersValid(); bool updateHeaders(bool force = false); @@ -52,8 +57,8 @@ class CPluginManager { void listAllPlugins(); - bool enablePlugin(const std::string& name); - bool disablePlugin(const std::string& name); + bool enablePlugin(const SPluginRepoIdentifier identifier); + bool disablePlugin(const SPluginRepoIdentifier identifier); ePluginLoadStateReturn ensurePluginsLoadState(bool forceReload = false); bool loadUnloadPlugin(const std::string& path, bool load); @@ -61,20 +66,26 @@ class CPluginManager { void notify(const eNotifyIcons icon, uint32_t color, int durationMs, const std::string& message); + const std::string& getPkgConfigPath(); + bool hasDeps(); bool m_bVerbose = false; bool m_bNoShallow = false; - std::string m_szCustomHlUrl, m_szUsername; + bool m_bNoNix = false; + std::string m_szCustomHlUrl, m_szUsername, m_szArgv0; // will delete recursively if exists!! bool createSafeDirectory(const std::string& path); private: - std::string headerError(const eHeadersErrors err); - std::string headerErrorShort(const eHeadersErrors err); + std::string headerError(const eHeadersErrors err); + std::string headerErrorShort(const eHeadersErrors err); + bool validArg(const std::string& s); - std::string m_szWorkingPluginDirectory; + std::expected nixDevelopIfNeeded(const std::string& cmd, const SHyprlandVersion& ver); + + std::string m_szWorkingPluginDirectory; }; inline std::unique_ptr g_pPluginManager; diff --git a/hyprpm/src/helpers/Sys.cpp b/hyprpm/src/helpers/Sys.cpp index c18d4748c..e9dd4c85f 100644 --- a/hyprpm/src/helpers/Sys.cpp +++ b/hyprpm/src/helpers/Sys.cpp @@ -35,9 +35,13 @@ static std::string validSubinsAsStr() { } static bool executableExistsInPath(const std::string& exe) { + return NSys::findInPath(exe).has_value(); +} + +std::optional NSys::findInPath(const std::string& exe) { const char* PATHENV = std::getenv("PATH"); if (!PATHENV) - return false; + return std::nullopt; CVarList paths(PATHENV, 0, ':', true); std::error_code ec; @@ -52,10 +56,10 @@ static bool executableExistsInPath(const std::string& exe) { if (ec) continue; if ((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none) - return true; + return candidate.string(); } - return false; + return std::nullopt; } static std::string subin() { diff --git a/hyprpm/src/helpers/Sys.hpp b/hyprpm/src/helpers/Sys.hpp index b44eb7580..03d5a0de1 100644 --- a/hyprpm/src/helpers/Sys.hpp +++ b/hyprpm/src/helpers/Sys.hpp @@ -1,11 +1,13 @@ #pragma once #include +#include namespace NSys { - bool isSuperuser(); - int getUID(); - int getEUID(); + bool isSuperuser(); + int getUID(); + int getEUID(); + std::optional findInPath(const std::string& exe); // NOLINTNEXTLINE namespace root { @@ -20,4 +22,4 @@ namespace NSys { // Do not use this unless absolutely necessary! std::string runAsSuperuserUnsafe(const std::string& cmd); }; -}; \ No newline at end of file +}; diff --git a/hyprpm/src/main.cpp b/hyprpm/src/main.cpp index 777d1d46e..f5e14bbb5 100644 --- a/hyprpm/src/main.cpp +++ b/hyprpm/src/main.cpp @@ -13,25 +13,26 @@ using namespace Hyprutils::Utils; constexpr std::string_view HELP = R"#(┏ hyprpm, a Hyprland Plugin Manager ┃ -┣ add [url] [git rev] → Install a new plugin repository from git. Git revision -┃ is optional, when set, commit locks are ignored. -┣ remove [url/name] → Remove an installed plugin repository. -┣ enable [name] → Enable a plugin. -┣ disable [name] → Disable a plugin. -┣ update → Check and update all plugins if needed. -┣ reload → Reload hyprpm state. Ensure all enabled plugins are loaded. -┣ list → List all installed plugins. -┣ purge-cache → Remove the entire hyprpm cache, built plugins, hyprpm settings and headers. +┣ add [git rev] → Install a new plugin repository from git. Git revision +┃ is optional, when set, commit locks are ignored. +┣ remove → Remove an installed plugin repository. +┣ enable → Enable a plugin. +┣ disable → Disable a plugin. +┣ update → Check and update all plugins if needed. +┣ reload → Reload hyprpm state. Ensure all enabled plugins are loaded. +┣ list → List all installed plugins. +┣ purge-cache → Remove the entire hyprpm cache, built plugins, hyprpm settings and headers. ┃ ┣ Flags: ┃ -┣ --notify | -n → Send a hyprland notification confirming successful plugin load. -┃ Warnings/Errors trigger notifications regardless of this flag. -┣ --help | -h → Show this menu. -┣ --verbose | -v → Enable too much logging. -┣ --force | -f → Force an operation ignoring checks (e.g. update -f). -┣ --no-shallow | -s → Disable shallow cloning of Hyprland sources. -┣ --hl-url | → Pass a custom hyprland source url. +┣ --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. +┣ --verbose | -v → Enable too much logging. +┣ --force | -f → Force an operation ignoring checks (e.g. update -f). +┣ --no-shallow | -s → Disable shallow cloning of Hyprland sources. +┣ --hl-url | → Pass a custom hyprland source url. ┗ )#"; @@ -47,7 +48,7 @@ int main(int argc, char** argv, char** envp) { } std::vector command; - bool notify = false, verbose = false, force = false, noShallow = false; + bool notify = false, verbose = false, force = false, noShallow = false, noNix = false; std::string customHlUrl; for (int i = 1; i < argc; ++i) { @@ -63,6 +64,8 @@ int main(int argc, char** argv, char** envp) { g_pPluginManager->notify(ICON_INFO, 0, 10000, "[hyprpm] -n flag is deprecated, see hyprpm --help."); } else if (ARGS[i] == "--verbose" || ARGS[i] == "-v") { verbose = true; + } else if (ARGS[i] == "--no-nix") { + noNix = true; } else if (ARGS[i] == "--no-shallow" || ARGS[i] == "-s") { noShallow = true; } else if (ARGS[i] == "--hl-url") { @@ -91,7 +94,9 @@ int main(int argc, char** argv, char** envp) { g_pPluginManager = std::make_unique(); g_pPluginManager->m_bVerbose = verbose; g_pPluginManager->m_bNoShallow = noShallow; + g_pPluginManager->m_bNoNix = noNix; g_pPluginManager->m_szCustomHlUrl = customHlUrl; + g_pPluginManager->m_szArgv0 = argv[0]; if (command[0] == "add") { if (command.size() < 2) { @@ -106,7 +111,7 @@ int main(int argc, char** argv, char** envp) { const auto HLVER = g_pPluginManager->getHyprlandVersion(); auto GLOBALSTATE = DataState::getGlobalState(); - if (GLOBALSTATE.headersHashCompiled != HLVER.hash) { + if (GLOBALSTATE.headersAbiCompiled != HLVER.abiHash) { std::println(stderr, "{}", failureString("Headers outdated, please run hyprpm update.")); return 1; } @@ -126,7 +131,7 @@ int main(int argc, char** argv, char** envp) { NSys::root::cacheSudo(); CScopeGuard x([] { NSys::root::dropSudo(); }); - return g_pPluginManager->removePluginRepo(command[1]) ? 0 : 1; + return g_pPluginManager->removePluginRepo(SPluginRepoIdentifier::fromString(command[1])) ? 0 : 1; } else if (command[0] == "update") { NSys::root::cacheSudo(); CScopeGuard x([] { NSys::root::dropSudo(); }); @@ -137,7 +142,7 @@ int main(int argc, char** argv, char** envp) { if (headers) { const auto HLVER = g_pPluginManager->getHyprlandVersion(false); auto GLOBALSTATE = DataState::getGlobalState(); - const auto COMPILEDOUTDATED = HLVER.hash != GLOBALSTATE.headersHashCompiled; + const auto COMPILEDOUTDATED = HLVER.abiHash != GLOBALSTATE.headersAbiCompiled; bool ret1 = g_pPluginManager->updatePlugins(!headersValid || force || COMPILEDOUTDATED); @@ -160,7 +165,7 @@ int main(int argc, char** argv, char** envp) { return 1; } - if (!g_pPluginManager->enablePlugin(command[1])) { + if (!g_pPluginManager->enablePlugin(SPluginRepoIdentifier::fromString(command[1]))) { std::println(stderr, "{}", failureString("Couldn't enable plugin (missing?)")); return 1; } @@ -181,7 +186,7 @@ int main(int argc, char** argv, char** envp) { return 1; } - if (!g_pPluginManager->disablePlugin(command[1])) { + if (!g_pPluginManager->disablePlugin(SPluginRepoIdentifier::fromString(command[1]))) { std::println(stderr, "{}", failureString("Couldn't disable plugin (missing?)")); return 1; } diff --git a/hyprtester/CMakeLists.txt b/hyprtester/CMakeLists.txt index 0b445b0ac..599eee585 100644 --- a/hyprtester/CMakeLists.txt +++ b/hyprtester/CMakeLists.txt @@ -48,7 +48,7 @@ pkg_check_modules( IMPORTED_TARGET hyprutils>=0.8.0 wayland-client - wayland-protocols + wayland-protocols>=1.47 ) pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir) @@ -96,6 +96,9 @@ endfunction() protocolnew("staging/pointer-warp" "pointer-warp-v1" false) protocolnew("stable/xdg-shell" "xdg-shell" false) +protocolnew("unstable/keyboard-shortcuts-inhibit" "keyboard-shortcuts-inhibit-unstable-v1" false) clientNew("pointer-warp" PROTOS "pointer-warp-v1" "xdg-shell") clientNew("pointer-scroll" PROTOS "xdg-shell") +clientNew("child-window" PROTOS "xdg-shell") +clientNew("shortcut-inhibitor" PROTOS "xdg-shell" "keyboard-shortcuts-inhibit-unstable-v1") diff --git a/hyprtester/clients/child-window.cpp b/hyprtester/clients/child-window.cpp new file mode 100644 index 000000000..5f66be6b8 --- /dev/null +++ b/hyprtester/clients/child-window.cpp @@ -0,0 +1,336 @@ +#include +#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; + + // shm/buffer stuff + CSharedPointer shmPool; + CSharedPointer shmBuf; + CSharedPointer shmBuf2; + int shmFd = 0; + size_t shmBufSize = 0; + bool xrgb8888_support = false; + + // surface/toplevel stuff + CSharedPointer surf; + CSharedPointer xdgSurf; + CSharedPointer xdgToplevel; + Vector2D geom; + + // pointer + CSharedPointer pointer; + uint32_t enterSerial = 0; +}; + +bool debug, shouldExit, started; + +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)); + } + }); + 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) { + 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-pointer-warp"; + 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 * 2) < 0) { + close(state.shmFd); + return false; + } + + state.shmPool = makeShared(state.wlShm->sendCreatePool(state.shmFd, size * 2)); + 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 * 2); + 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("child-test parent"); + state.xdgToplevel->sendSetAppId("child-test-parent"); + + 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; +} + +struct SChildWindow { + CSharedPointer surface; + CSharedPointer xSurface; + CSharedPointer toplevel; +}; + +static void parseRequest(SWlState& state, std::string str, SChildWindow& window) { + if (str.starts_with("exit")) { + shouldExit = true; + return; + } + + size_t index = str.find_first_of('\n'); + str = str.substr(0, index); + + if (str == "toplevel") { + window.surface = makeShared(state.wlCompositor->sendCreateSurface()); + window.xSurface = makeShared(state.xdgShell->sendGetXdgSurface(window.surface->resource())); + + window.xSurface->setConfigure([&](CCXdgSurface* p, uint32_t serial) { + if (!state.shmBuf) + debugLog("xdgSurf configure but no buf made yet?"); + + window.xSurface->sendSetWindowGeometry(0, 0, state.geom.x, state.geom.y); + window.surface->sendAttach(state.shmBuf2.get(), 0, 0); + window.surface->sendCommit(); + + window.xSurface->sendAckConfigure(serial); + }); + + window.toplevel = makeShared(window.xSurface->sendGetToplevel()); + + window.toplevel->setConfigure([&](CCXdgToplevel* p, int32_t w, int32_t h, wl_array* arr) { + size_t stride = 1280 * 4; + size_t size = 720 * stride; + + auto buf = makeShared(state.shmPool->sendCreateBuffer(size, state.geom.x, state.geom.y, stride, WL_SHM_FORMAT_XRGB8888)); + if (!buf->resource()) + clientLog("Failed to create child buffer"); + + if (state.shmBuf2) { + state.shmBuf2->sendDestroy(); + state.shmBuf2.reset(); + } + state.shmBuf2 = buf; + }); + + window.toplevel->sendSetTitle("child-test child"); + window.toplevel->sendSetAppId("child-test-child"); + window.toplevel->sendSetParent(state.xdgToplevel.get()); + + window.surface->sendAttach(nullptr, 0, 0); + window.surface->sendCommit(); + clientLog("child started"); + return; + } +} + +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; + SChildWindow window; + + // 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()}, window); + } + } + + wl_display* display = state.display; + state = {}; + window = {}; + + wl_display_disconnect(display); + return 0; +} diff --git a/hyprtester/clients/pointer-scroll.cpp b/hyprtester/clients/pointer-scroll.cpp index 140e4700f..59120961e 100644 --- a/hyprtester/clients/pointer-scroll.cpp +++ b/hyprtester/clients/pointer-scroll.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include diff --git a/hyprtester/clients/pointer-warp.cpp b/hyprtester/clients/pointer-warp.cpp index 2d3624d52..a57f99ae3 100644 --- a/hyprtester/clients/pointer-warp.cpp +++ b/hyprtester/clients/pointer-warp.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include diff --git a/hyprtester/clients/shortcut-inhibitor.cpp b/hyprtester/clients/shortcut-inhibitor.cpp new file mode 100644 index 000000000..0c6b43419 --- /dev/null +++ b/hyprtester/clients/shortcut-inhibitor.cpp @@ -0,0 +1,297 @@ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +using Hyprutils::Math::Vector2D; +using namespace Hyprutils::Memory; + +struct SWlState { + wl_display* display; + CSharedPointer registry; + + // protocols + CSharedPointer wlCompositor; + CSharedPointer wlSeat; + CSharedPointer wlShm; + CSharedPointer xdgShell; + CSharedPointer inhibitManager; + + // shm/buffer stuff + CSharedPointer shmPool; + CSharedPointer shmBuf; + int shmFd; + size_t shmBufSize; + bool xrgb8888_support = false; + + // surface/toplevel stuff + CSharedPointer surf; + CSharedPointer xdgSurf; + CSharedPointer xdgToplevel; + Vector2D geom; + + // pointer + CSharedPointer pointer; + uint32_t enterSerial; + + // shortcut inhibiting + CSharedPointer inhibitor; +}; + +static bool debug, started, shouldExit; + +template +//NOLINTNEXTLINE +static void clientLog(std::format_string fmt, Args&&... args) { + std::string text = std::vformat(fmt.get(), std::make_format_args(args...)); + std::println("{}", text); + std::fflush(stdout); +} + +template +//NOLINTNEXTLINE +static void debugLog(std::format_string fmt, Args&&... args) { + std::string text = std::vformat(fmt.get(), std::make_format_args(args...)); + if (!debug) + return; + std::println("{}", text); + std::fflush(stdout); +} + +static bool bindRegistry(SWlState& state) { + state.registry = makeShared((wl_proxy*)wl_display_get_registry(state.display)); + + state.registry->setGlobal([&](CCWlRegistry* r, uint32_t id, const char* name, uint32_t version) { + const std::string NAME = name; + if (NAME == "wl_compositor") { + debugLog(" > binding to global: {} (version {}) with id {}", name, version, id); + state.wlCompositor = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_compositor_interface, 6)); + } else if (NAME == "wl_shm") { + debugLog(" > binding to global: {} (version {}) with id {}", name, version, id); + state.wlShm = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_shm_interface, 1)); + } else if (NAME == "wl_seat") { + debugLog(" > binding to global: {} (version {}) with id {}", name, version, id); + state.wlSeat = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_seat_interface, 9)); + } else if (NAME == "xdg_wm_base") { + debugLog(" > binding to global: {} (version {}) with id {}", name, version, id); + state.xdgShell = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &xdg_wm_base_interface, 1)); + } else if (NAME == "zwp_keyboard_shortcuts_inhibit_manager_v1") { + debugLog(" > binding to global: {} (version {}) with id {}", name, version, id); + state.inhibitManager = makeShared( + (wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1)); + } + }); + state.registry->setGlobalRemove([](CCWlRegistry* r, uint32_t id) { debugLog("Global {} removed", id); }); + + wl_display_roundtrip(state.display); + + if (!state.wlCompositor || !state.wlShm || !state.wlSeat || !state.xdgShell || !state.inhibitManager) { + clientLog("Failed to get protocols from Hyprland"); + return false; + } + + return true; +} + +static bool createShm(SWlState& state, Vector2D geom) { + if (!state.xrgb8888_support) + return false; + + size_t stride = geom.x * 4; + size_t size = geom.y * stride; + if (!state.shmPool) { + const char* name = "/wl-shm-shortcut-inhibitor"; + state.shmFd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (state.shmFd < 0) + return false; + + if (shm_unlink(name) < 0 || ftruncate(state.shmFd, size) < 0) { + close(state.shmFd); + return false; + } + + state.shmPool = makeShared(state.wlShm->sendCreatePool(state.shmFd, size)); + if (!state.shmPool->resource()) { + close(state.shmFd); + state.shmFd = -1; + state.shmPool.reset(); + return false; + } + state.shmBufSize = size; + } else if (size > state.shmBufSize) { + if (ftruncate(state.shmFd, size) < 0) { + close(state.shmFd); + state.shmFd = -1; + state.shmPool.reset(); + return false; + } + + state.shmPool->sendResize(size); + state.shmBufSize = size; + } + + auto buf = makeShared(state.shmPool->sendCreateBuffer(0, geom.x, geom.y, stride, WL_SHM_FORMAT_XRGB8888)); + if (!buf->resource()) + return false; + + if (state.shmBuf) { + state.shmBuf->sendDestroy(); + state.shmBuf.reset(); + } + + state.shmBuf = buf; + + return true; +} + +static bool setupToplevel(SWlState& state) { + state.wlShm->setFormat([&](CCWlShm* p, uint32_t format) { + if (format == WL_SHM_FORMAT_XRGB8888) + state.xrgb8888_support = true; + }); + + state.xdgShell->setPing([&](CCXdgWmBase* p, uint32_t serial) { state.xdgShell->sendPong(serial); }); + + state.surf = makeShared(state.wlCompositor->sendCreateSurface()); + if (!state.surf->resource()) + return false; + + state.xdgSurf = makeShared(state.xdgShell->sendGetXdgSurface(state.surf->resource())); + if (!state.xdgSurf->resource()) + return false; + + state.xdgToplevel = makeShared(state.xdgSurf->sendGetToplevel()); + if (!state.xdgToplevel->resource()) + return false; + + state.xdgToplevel->setClose([&](CCXdgToplevel* p) { exit(0); }); + + state.xdgToplevel->setConfigure([&](CCXdgToplevel* p, int32_t w, int32_t h, wl_array* arr) { + state.geom = {1280, 720}; + + if (!createShm(state, state.geom)) + exit(-1); + }); + + state.xdgSurf->setConfigure([&](CCXdgSurface* p, uint32_t serial) { + if (!state.shmBuf) + debugLog("xdgSurf configure but no buf made yet?"); + + state.xdgSurf->sendSetWindowGeometry(0, 0, state.geom.x, state.geom.y); + state.surf->sendAttach(state.shmBuf.get(), 0, 0); + state.surf->sendCommit(); + + state.xdgSurf->sendAckConfigure(serial); + + if (!started) { + started = true; + clientLog("started"); + } + }); + + state.xdgToplevel->sendSetTitle("shortcut-inhibitor test client"); + state.xdgToplevel->sendSetAppId("shortcut-inhibitor"); + + state.surf->sendAttach(nullptr, 0, 0); + state.surf->sendCommit(); + + return true; +} + +static bool setupSeat(SWlState& state) { + state.pointer = makeShared(state.wlSeat->sendGetPointer()); + if (!state.pointer->resource()) + return false; + + state.pointer->setEnter([&](CCWlPointer* p, uint32_t serial, wl_proxy* surf, wl_fixed_t x, wl_fixed_t y) { + debugLog("Got pointer enter event, serial {}, x {}, y {}", serial, x, y); + state.enterSerial = serial; + }); + + state.pointer->setLeave([&](CCWlPointer* p, uint32_t serial, wl_proxy* surf) { debugLog("Got pointer leave event, serial {}", serial); }); + + state.pointer->setMotion([&](CCWlPointer* p, uint32_t serial, wl_fixed_t x, wl_fixed_t y) { debugLog("Got pointer motion event, serial {}, x {}, y {}", serial, x, y); }); + + return true; +} + +static void parseRequest(SWlState& state, std::string req) { + if (req.starts_with("on")) { + state.inhibitor = makeShared(state.inhibitManager->sendInhibitShortcuts(state.surf->resource(), state.wlSeat->resource())); + + state.inhibitor->setActive([&](CCZwpKeyboardShortcutsInhibitorV1* inhibitorV1) { clientLog("inhibiting"); }); + state.inhibitor->setInactive([&](CCZwpKeyboardShortcutsInhibitorV1* inhibitorV1) { clientLog("inhibit disabled by compositor"); }); + } else if (req.starts_with("off")) { + state.inhibitor->sendDestroy(); + state.inhibitor.reset(); + shouldExit = true; + clientLog("inhibit disabled by request"); + } +} + +int main(int argc, char** argv) { + if (argc != 1 && argc != 2) + clientLog("Only the \"--debug\" switch is allowed, it turns on debug logs."); + + if (argc == 2 && std::string{argv[1]} == "--debug") + debug = true; + + SWlState state; + + // WAYLAND_DISPLAY env should be set to the correct one + state.display = wl_display_connect(nullptr); + if (!state.display) { + clientLog("Failed to connect to wayland display"); + return -1; + } + + if (!bindRegistry(state) || !setupSeat(state) || !setupToplevel(state)) + return -1; + + std::array readBuf; + readBuf.fill(0); + + wl_display_flush(state.display); + + struct pollfd fds[2] = {{.fd = wl_display_get_fd(state.display), .events = POLLIN | POLLOUT}, {.fd = STDIN_FILENO, .events = POLLIN}}; + while (!shouldExit && poll(fds, 2, 0) != -1) { + if (fds[0].revents & POLLIN) { + wl_display_flush(state.display); + + if (wl_display_prepare_read(state.display) == 0) { + wl_display_read_events(state.display); + wl_display_dispatch_pending(state.display); + } else + wl_display_dispatch(state.display); + + int ret = 0; + do { + ret = wl_display_dispatch_pending(state.display); + wl_display_flush(state.display); + } while (ret > 0); + } + + if (fds[1].revents & POLLIN) { + ssize_t bytesRead = read(fds[1].fd, readBuf.data(), 1023); + if (bytesRead == -1) + continue; + readBuf[bytesRead] = 0; + + parseRequest(state, std::string{readBuf.data()}); + } + } + + wl_display* display = state.display; + state = {}; + + wl_display_disconnect(display); + return 0; +} diff --git a/hyprtester/plugin/src/main.cpp b/hyprtester/plugin/src/main.cpp index cbc06723c..35d2eea4c 100644 --- a/hyprtester/plugin/src/main.cpp +++ b/hyprtester/plugin/src/main.cpp @@ -4,17 +4,18 @@ #include #define private public -#include -#include -#include -#include +#include +#include #include #include #include #include +#include #include +#include #include #include +#include #undef private #include @@ -33,7 +34,7 @@ static SDispatchResult test(std::string in) { bool success = true; std::string errors = ""; - if (g_pConfigManager->m_configValueNumber != CONFIG_OPTIONS.size() + 1 /* autogenerated is special */) { + if (Config::Legacy::mgr()->m_configValueNumber != Config::Supplementary::CONFIG_OPTIONS.size() + 1 /* autogenerated is special */) { errors += "config value number mismatches descriptions size\n"; success = false; } @@ -53,8 +54,9 @@ static SDispatchResult snapMove(std::string in) { Vector2D pos = PLASTWINDOW->m_realPosition->goal(); Vector2D size = PLASTWINDOW->m_realSize->goal(); - g_pLayoutManager->getCurrentLayout()->performSnap(pos, size, PLASTWINDOW, MBIND_MOVE, -1, size); - *PLASTWINDOW->m_realPosition = pos.round(); + g_layoutManager->performSnap(pos, size, PLASTWINDOW->layoutTarget(), MBIND_MOVE, -1, size); + + PLASTWINDOW->layoutTarget()->setPositionGlobal(CBox{pos, size}); return {}; } @@ -66,6 +68,8 @@ class CTestKeyboard : public IKeyboard { keeb->m_self = keeb; keeb->m_isVirtual = isVirtual; keeb->m_shareStates = !isVirtual; + keeb->m_hlName = "test-keyboard"; + keeb->m_deviceName = "test-keyboard"; return keeb; } @@ -213,7 +217,7 @@ static SDispatchResult scroll(std::string in) { by = std::stod(in); } catch (...) { return SDispatchResult{.success = false, .error = "invalid input"}; } - Debug::log(LOG, "tester: scrolling by {}", by); + Log::logger->log(Log::DEBUG, "tester: scrolling by {}", by); g_mouse->m_pointerEvents.axis.emit(IPointer::SAxisEvent{ .delta = by, @@ -224,8 +228,30 @@ static SDispatchResult scroll(std::string in) { return {}; } +static SDispatchResult click(std::string in) { + CVarList2 data(std::move(in)); + + uint32_t button; + bool pressed; + try { + button = std::stoul(std::string{data[0]}); + pressed = std::stoul(std::string{data[1]}) == 1; + } catch (...) { return {.success = false, .error = "invalid input"}; } + + Log::logger->log(Log::DEBUG, "tester: mouse button {} state {}", button, pressed); + + g_mouse->m_pointerEvents.button.emit(IPointer::SButtonEvent{ + .timeMs = sc(Time::millis(Time::steadyNow())), + .button = button, + .state = pressed ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED, + .mouse = true, + }); + + return {}; +} + static SDispatchResult keybind(std::string in) { - CVarList data(in); + CVarList2 data(std::move(in)); // 0 = release, 1 = press bool press; // See src/devices/IKeyboard.hpp : eKeyboardModifiers for modifier bitmasks @@ -234,9 +260,9 @@ static SDispatchResult keybind(std::string in) { // keycode uint32_t key; try { - press = std::stoul(data[0]) == 1; - modifier = std::stoul(data[1]); - key = std::stoul(data[2]) - 8; // xkb offset + press = std::stoul(std::string{data[0]}) == 1; + modifier = std::stoul(std::string{data[1]}); + key = std::stoul(std::string{data[2]}) - 8; // xkb offset } catch (...) { return {.success = false, .error = "invalid input"}; } uint32_t modifierMask = 0; @@ -248,32 +274,85 @@ static SDispatchResult keybind(std::string in) { return {}; } -static Desktop::Rule::CWindowRuleEffectContainer::storageType ruleIDX = 0; +static Desktop::Rule::CWindowRuleEffectContainer::storageType windowRuleIDX = 0; // -static SDispatchResult addRule(std::string in) { - ruleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule"); +static SDispatchResult addWindowRule(std::string in) { + windowRuleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule"); - if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != ruleIDX) + if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != windowRuleIDX) return {.success = false, .error = "re-registering returned a different id?"}; return {}; } -static SDispatchResult checkRule(std::string in) { +static SDispatchResult checkWindowRule(std::string in) { const auto PLASTWINDOW = Desktop::focusState()->window(); if (!PLASTWINDOW) return {.success = false, .error = "No window"}; - if (!PLASTWINDOW->m_ruleApplicator->m_otherProps.props.contains(ruleIDX)) + if (!PLASTWINDOW->m_ruleApplicator->m_otherProps.props.contains(windowRuleIDX)) return {.success = false, .error = "No rule"}; - if (PLASTWINDOW->m_ruleApplicator->m_otherProps.props[ruleIDX]->effect != "effect") + if (PLASTWINDOW->m_ruleApplicator->m_otherProps.props[windowRuleIDX]->effect != "effect") return {.success = false, .error = "Effect isn't \"effect\""}; return {}; } +static Desktop::Rule::CLayerRuleEffectContainer::storageType layerRuleIDX = 0; + +static SDispatchResult addLayerRule(std::string in) { + layerRuleIDX = Desktop::Rule::layerEffects()->registerEffect("plugin_rule"); + + if (Desktop::Rule::layerEffects()->registerEffect("plugin_rule") != layerRuleIDX) + return {.success = false, .error = "re-registering returned a different id?"}; + return {}; +} + +static SDispatchResult checkLayerRule(std::string in) { + if (g_pCompositor->m_layers.size() != 3) + return {.success = false, .error = "Layers under test not here"}; + + for (const auto& layer : g_pCompositor->m_layers) { + if (layer->m_namespace == "rule-layer") { + + if (!layer->m_ruleApplicator->m_otherProps.props.contains(layerRuleIDX)) + return {.success = false, .error = "No rule"}; + + if (layer->m_ruleApplicator->m_otherProps.props[layerRuleIDX]->effect != "effect") + return {.success = false, .error = "Effect isn't \"effect\""}; + + } else if (layer->m_namespace == "norule-layer") { + + if (layer->m_ruleApplicator->m_otherProps.props.contains(layerRuleIDX)) + return {.success = false, .error = "Rule even though it shouldn't"}; + + } else + return {.success = false, .error = "Unrecognized layer"}; + } + + return {}; +} + +static SDispatchResult floatingFocusOnFullscreen(std::string in) { + const auto PLASTWINDOW = Desktop::focusState()->window(); + + if (!PLASTWINDOW) + return {.success = false, .error = "No window"}; + + if (!PLASTWINDOW->m_isFloating) + return {.success = false, .error = "Window must be floating"}; + + if (PLASTWINDOW->m_alpha != 1.f) + return {.success = false, .error = "floating window doesnt restore it opacity when focused on fullscreen workspace"}; + + if (!PLASTWINDOW->m_createdOverFullscreen) + return {.success = false, .error = "floating window doesnt get flagged as createdOverFullscreen"}; + + return {}; +} + APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { PHANDLE = handle; @@ -283,9 +362,13 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:alt", ::pressAlt); HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:gesture", ::simulateGesture); HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:scroll", ::scroll); + HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:click", ::click); HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:keybind", ::keybind); - HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_rule", ::addRule); - HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_rule", ::checkRule); + HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_window_rule", ::addWindowRule); + HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_window_rule", ::checkWindowRule); + HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_layer_rule", ::addLayerRule); + HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_layer_rule", ::checkLayerRule); + HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:floating_focus_on_fullscreen", ::floatingFocusOnFullscreen); // init mouse g_mouse = CTestMouse::create(false); diff --git a/hyprtester/src/shared.hpp b/hyprtester/src/shared.hpp index 43944c3c2..941788fde 100644 --- a/hyprtester/src/shared.hpp +++ b/hyprtester/src/shared.hpp @@ -18,6 +18,17 @@ namespace Colors { constexpr const char* RESET = "\x1b[0m"; }; +#define EXPECT_MAX_DELTA(expr, desired, delta) \ + if (const auto RESULT = expr; std::abs(RESULT - (desired)) > delta) { \ + NLog::log("{}Failed: {}{}, expected max delta of {}, got delta {} ({} - {}). Source: {}@{}.", Colors::RED, Colors::RESET, #expr, delta, (RESULT - (desired)), RESULT, \ + desired, __FILE__, __LINE__); \ + ret = 1; \ + TESTS_FAILED++; \ + } else { \ + NLog::log("{}Passed: {}{}. Got {}", Colors::GREEN, Colors::RESET, #expr, (RESULT - (desired))); \ + TESTS_PASSED++; \ + } + #define EXPECT(expr, val) \ if (const auto RESULT = expr; RESULT != (val)) { \ NLog::log("{}Failed: {}{}, expected {}, got {}. Source: {}@{}.", Colors::RED, Colors::RESET, #expr, val, RESULT, __FILE__, __LINE__); \ @@ -28,6 +39,16 @@ namespace Colors { TESTS_PASSED++; \ } +#define EXPECT_NOT(expr, val) \ + if (const auto RESULT = expr; RESULT == (val)) { \ + NLog::log("{}Failed: {}{}, expected not {}, got {}. Source: {}@{}.", Colors::RED, Colors::RESET, #expr, val, RESULT, __FILE__, __LINE__); \ + ret = 1; \ + TESTS_FAILED++; \ + } else { \ + NLog::log("{}Passed: {}{}. Got {}", Colors::GREEN, Colors::RESET, #expr, val); \ + TESTS_PASSED++; \ + } + #define EXPECT_VECTOR2D(expr, val) \ do { \ const auto& RESULT = expr; \ diff --git a/hyprtester/src/tests/clients/child-window.cpp b/hyprtester/src/tests/clients/child-window.cpp new file mode 100644 index 000000000..a5680d4f0 --- /dev/null +++ b/hyprtester/src/tests/clients/child-window.cpp @@ -0,0 +1,151 @@ +#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 waitForWindow(SP proc, int windowsBefore) { + int counter = 0; + while (Tests::processAlive(proc->pid()) && Tests::windowCount() == windowsBefore) { + counter++; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + if (counter > 50) + return false; + } + + NLog::log("{}Waited {} milliseconds for window to open", Colors::YELLOW, counter * 100); + return Tests::processAlive(proc->pid()); +} + +static bool startClient(SClient& client) { + NLog::log("{}Attempting to start child-window client", Colors::YELLOW); + + client.proc = makeShared(binaryDir + "/child-window", std::vector{}); + + client.proc->addEnv("WAYLAND_DISPLAY", WLDISPLAY); + + int procInPipeFd[2], procOutPipeFd[2]; + if (pipe(procInPipeFd) != 0 || pipe(procOutPipeFd) != 0) { + NLog::log("{}Unable to open pipe to client", Colors::RED); + return false; + } + + client.writeFd = CFileDescriptor(procInPipeFd[1]); + client.proc->setStdinFD(procInPipeFd[0]); + + client.readFd = CFileDescriptor(procOutPipeFd[0]); + client.proc->setStdoutFD(procOutPipeFd[1]); + + if (!client.proc->runAsync()) { + NLog::log("{}Failed to run client", Colors::RED); + return false; + } + + close(procInPipeFd[0]); + close(procOutPipeFd[1]); + + if (!waitForWindow(client.proc, Tests::windowCount())) { + NLog::log("{}Window took too long to open", Colors::RED); + return false; + } + + NLog::log("{}Started child-window client", Colors::YELLOW); + return true; +} + +static void stopClient(SClient& client) { + std::string cmd = "exit\n"; + write(client.writeFd.get(), cmd.c_str(), cmd.length()); + + kill(client.proc->pid(), SIGKILL); + client.proc.reset(); +} + +static bool createChild(SClient& client) { + std::string cmd = "toplevel\n"; + if ((size_t)write(client.writeFd.get(), cmd.c_str(), cmd.length()) != cmd.length()) + return false; + + if (!waitForWindow(client.proc, Tests::windowCount())) + NLog::log("{}Child window took too long to open", Colors::RED); + + if (getFromSocket("/dispatch focuswindow class:child-test-child") != "ok") { + NLog::log("{}Failed to focus child window", Colors::RED); + return false; + } + + return true; +} + +static bool test() { + SClient client; + + if (!startClient(client)) + return false; + OK(getFromSocket("/dispatch setfloating class:child-test-parent")); + OK(getFromSocket("/dispatch pin class:child-test-parent")); + + createChild(client); + EXPECT(Tests::windowCount(), 2) + EXPECT_COUNT_STRING(getFromSocket("/clients"), "pinned: 1", 2); + + stopClient(client); + NLog::log("{}Reloading config", Colors::YELLOW); + OK(getFromSocket("/reload")); + 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); diff --git a/hyprtester/src/tests/clients/pointer-scroll.cpp b/hyprtester/src/tests/clients/pointer-scroll.cpp index 2ea93a14c..b5fb68fbc 100644 --- a/hyprtester/src/tests/clients/pointer-scroll.cpp +++ b/hyprtester/src/tests/clients/pointer-scroll.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -42,6 +43,7 @@ static bool startClient(SClient& client) { client.readFd = CFileDescriptor(pipeFds2[0]); client.proc->setStdoutFD(pipeFds2[1]); + const int COUNT_BEFORE = Tests::windowCount(); client.proc->runAsync(); close(pipeFds1[0]); @@ -62,7 +64,16 @@ static bool startClient(SClient& client) { } // wait for window to appear - std::this_thread::sleep_for(std::chrono::milliseconds(5000)); + int counter = 0; + while (Tests::processAlive(client.proc->pid()) && Tests::windowCount() == COUNT_BEFORE) { + counter++; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + if (counter > 50) { + NLog::log("{}pointer-scroll client took too long to open", Colors::RED); + return false; + } + } if (getFromSocket(std::format("/dispatch setprop pid:{} no_anim 1", client.proc->pid())) != "ok") { NLog::log("{}Failed to disable animations for client window", Colors::RED, ret); diff --git a/hyprtester/src/tests/clients/pointer-warp.cpp b/hyprtester/src/tests/clients/pointer-warp.cpp index bb03afd28..be992566d 100644 --- a/hyprtester/src/tests/clients/pointer-warp.cpp +++ b/hyprtester/src/tests/clients/pointer-warp.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -42,6 +43,7 @@ static bool startClient(SClient& client) { client.readFd = CFileDescriptor(pipeFds2[0]); client.proc->setStdoutFD(pipeFds2[1]); + const int COUNT_BEFORE = Tests::windowCount(); client.proc->runAsync(); close(pipeFds1[0]); @@ -62,7 +64,16 @@ static bool startClient(SClient& client) { } // wait for window to appear - std::this_thread::sleep_for(std::chrono::milliseconds(5000)); + int counter = 0; + while (Tests::processAlive(client.proc->pid()) && Tests::windowCount() == COUNT_BEFORE) { + counter++; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + if (counter > 50) { + NLog::log("{}pointer-warp client took too long to open", Colors::RED); + return false; + } + } if (getFromSocket(std::format("/dispatch setprop pid:{} no_anim 1", client.proc->pid())) != "ok") { NLog::log("{}Failed to disable animations for client window", Colors::RED, ret); diff --git a/hyprtester/src/tests/clients/shortcut-inhibitor.cpp b/hyprtester/src/tests/clients/shortcut-inhibitor.cpp new file mode 100644 index 000000000..91c3376c5 --- /dev/null +++ b/hyprtester/src/tests/clients/shortcut-inhibitor.cpp @@ -0,0 +1,180 @@ +#include "../../shared.hpp" +#include "../../hyprctlCompat.hpp" +#include "../shared.hpp" +#include "tests.hpp" +#include "build.hpp" + +#include +#include + +#include +#include +#include +#include + +using namespace Hyprutils::OS; +using namespace Hyprutils::Memory; + +#define SP CSharedPointer + +struct SClient { + SP proc; + std::array readBuf; + CFileDescriptor readFd, writeFd; + struct pollfd fds; +}; + +static int ret = 0; + +static bool startClient(SClient& client) { + Tests::killAllWindows(); + client.proc = makeShared(binaryDir + "/shortcut-inhibitor", std::vector{}); + + client.proc->addEnv("WAYLAND_DISPLAY", WLDISPLAY); + + int pipeFds1[2], pipeFds2[2]; + if (pipe(pipeFds1) != 0 || pipe(pipeFds2) != 0) { + NLog::log("{}Unable to open pipe to client", Colors::RED); + return false; + } + + client.writeFd = CFileDescriptor(pipeFds1[1]); + client.proc->setStdinFD(pipeFds1[0]); + + client.readFd = CFileDescriptor(pipeFds2[0]); + client.proc->setStdoutFD(pipeFds2[1]); + + const int COUNT_BEFORE = Tests::windowCount(); + client.proc->runAsync(); + + close(pipeFds1[0]); + close(pipeFds2[1]); + + client.fds = {.fd = client.readFd.get(), .events = POLLIN}; + if (poll(&client.fds, 1, 1000) != 1 || !(client.fds.revents & POLLIN)) { + NLog::log("{}shortcut-inhibitor client failed poll", Colors::RED); + return false; + } + + client.readBuf.fill(0); + if (read(client.readFd.get(), client.readBuf.data(), client.readBuf.size() - 1) == -1) { + NLog::log("{}shortcut-inhibitor client read failed", Colors::RED); + return false; + } + + std::string ret = std::string{client.readBuf.data()}; + if (ret.find("started") == std::string::npos) { + NLog::log("{}Failed to start shortcut-inhibitor client, read {}", Colors::RED, ret); + return false; + } + + // wait for window to appear + int counter = 0; + while (Tests::processAlive(client.proc->pid()) && Tests::windowCount() == COUNT_BEFORE) { + counter++; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + if (counter > 50) { + NLog::log("{}shortcut-inhibitor client took too long to open", Colors::RED); + return false; + } + } + + if (!Tests::processAlive(client.proc->pid())) { + NLog::log("{}shortcut-inhibitor client not alive", Colors::RED); + return false; + } + + if (getFromSocket(std::format("/dispatch focuswindow pid:{}", client.proc->pid())) != "ok") { + NLog::log("{}Failed to focus shortcut-inhibitor client", Colors::RED, ret); + return false; + } + + std::string command = "on\n"; + if (write(client.writeFd.get(), command.c_str(), command.length()) == -1) { + NLog::log("{}shortcut-inhibitor client write failed", Colors::RED); + return false; + } + + client.readBuf.fill(0); + if (read(client.readFd.get(), client.readBuf.data(), client.readBuf.size() - 1) == -1) + return false; + + ret = std::string{client.readBuf.data()}; + if (ret.find("inhibiting") == std::string::npos) { + NLog::log("{}shortcut-inhibitor client didn't return inhibiting", Colors::RED); + return false; + } + + NLog::log("{}Started shortcut-inhibitor client", Colors::YELLOW); + + return true; +} + +static void stopClient(SClient& client) { + std::string cmd = "off\n"; + write(client.writeFd.get(), cmd.c_str(), cmd.length()); + + kill(client.proc->pid(), SIGKILL); + client.proc.reset(); +} + +static std::string flagFile = "/tmp/hyprtester-keybinds.txt"; + +static bool checkFlag() { + bool exists = std::filesystem::exists(flagFile); + std::filesystem::remove(flagFile); + return exists; +} + +static bool attemptCheckFlag(int attempts, int intervalMs) { + for (int i = 0; i < attempts; i++) { + if (checkFlag()) + return true; + + std::this_thread::sleep_for(std::chrono::milliseconds(intervalMs)); + } + + return false; +} + +static bool test() { + SClient client; + if (!startClient(client)) + return false; + + NLog::log("{}Testing keybinds", Colors::GREEN); + //basic keybind test + EXPECT(checkFlag(), false); + EXPECT(getFromSocket("/keyword bind SUPER,Y,exec,touch " + flagFile), "ok"); + OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29")); + EXPECT(attemptCheckFlag(20, 50), false); + OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29")); + EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok"); + + //keybind bypass flag test + EXPECT(checkFlag(), false); + EXPECT(getFromSocket("/keyword bindp SUPER,Y,exec,touch " + flagFile), "ok"); + OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29")); + EXPECT(attemptCheckFlag(20, 50), true); + OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29")); + EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok"); + + NLog::log("{}Testing gestures", Colors::GREEN); + //basic gesture test + OK(getFromSocket("/dispatch plugin:test:gesture right,3")); + EXPECT_NOT_CONTAINS(getFromSocket("/activewindow"), "floating: 1"); + + //gesture bypass flag test + OK(getFromSocket("/dispatch plugin:test:gesture right,2")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "floating: 1"); + + stopClient(client); + + NLog::log("{}Reloading the config", Colors::YELLOW); + OK(getFromSocket("/reload")); + + return !ret; +} + +REGISTER_CLIENT_TEST_FN(test); diff --git a/hyprtester/src/tests/main/colors.cpp b/hyprtester/src/tests/main/colors.cpp index deb4254ce..5fa9eee6d 100644 --- a/hyprtester/src/tests/main/colors.cpp +++ b/hyprtester/src/tests/main/colors.cpp @@ -3,23 +3,33 @@ #include "../../hyprctlCompat.hpp" #include "../shared.hpp" +#include +#include + static int ret = 0; static bool test() { NLog::log("{}Testing hyprctl monitors", Colors::GREEN); std::string monitorsSpec = getFromSocket("j/monitors"); - EXPECT_CONTAINS(monitorsSpec, R"("colorManagementPreset": "srgb")"); + EXPECT_CONTAINS(monitorsSpec, R"("colorManagementPreset")"); EXPECT_CONTAINS(getFromSocket("/keyword monitor HEADLESS-2,1920x1080x60.00000,0x0,1.0,bitdepth,10,cm,wide"), "ok") + + // monitor settings are applied after a frame is pushed. + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + monitorsSpec = getFromSocket("j/monitors"); EXPECT_CONTAINS(monitorsSpec, R"("colorManagementPreset": "wide")"); EXPECT_CONTAINS(getFromSocket("/keyword monitor HEADLESS-2,1920x1080x60.00000,0x0,1.0,bitdepth,10,cm,srgb,sdrbrightness,1.2,sdrsaturation,0.98"), "ok") monitorsSpec = getFromSocket("j/monitors"); - EXPECT_CONTAINS(monitorsSpec, R"("colorManagementPreset": "srgb")"); - EXPECT_CONTAINS(monitorsSpec, R"("sdrBrightness": 1.20)"); - EXPECT_CONTAINS(monitorsSpec, R"("sdrSaturation": 0.98)"); + + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + EXPECT_CONTAINS(monitorsSpec, "colorManagementPreset"); + EXPECT_CONTAINS(monitorsSpec, "sdrBrightness"); + EXPECT_CONTAINS(monitorsSpec, "sdrSaturation"); return !ret; } diff --git a/hyprtester/src/tests/main/dwindle.cpp b/hyprtester/src/tests/main/dwindle.cpp index 8f17c8152..cd8528547 100644 --- a/hyprtester/src/tests/main/dwindle.cpp +++ b/hyprtester/src/tests/main/dwindle.cpp @@ -25,7 +25,7 @@ static void testFloatClamp() { { auto str = getFromSocket("/clients"); - EXPECT_CONTAINS(str, "at: 698,158"); + EXPECT_CONTAINS(str, "at:"); EXPECT_CONTAINS(str, "size: 1200,900"); } @@ -34,6 +34,219 @@ 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: 22,547"); + EXPECT_CONTAINS(str, "size: 931,511"); + } + + OK(getFromSocket("/dispatch movewindow r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 967,547"); + EXPECT_CONTAINS(str, "size: 931,511"); + } + + // 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 void testForceSplitOnMoveToWorkspace() { + OK(getFromSocket("/dispatch workspace 2")); + EXPECT(!!Tests::spawnKitty("kitty"), true); + + OK(getFromSocket("/dispatch workspace 1")); + EXPECT(!!Tests::spawnKitty("kitty"), true); + std::string posBefore = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:"); + + OK(getFromSocket("/keyword dwindle:force_split 2")); + OK(getFromSocket("/dispatch movecursortocorner 3")); // top left + OK(getFromSocket("/dispatch movetoworkspace 2")); + + // Should be moved to the right, so the position should change + std::string activeWindow = getFromSocket("/activewindow"); + EXPECT(activeWindow.contains(posBefore), false); + + // clean up + OK(getFromSocket("/reload")); + Tests::killAllWindows(); + Tests::waitUntilWindowsN(0); } static bool test() { @@ -43,6 +256,18 @@ 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(); + + NLog::log("{}Testing force_split on move to workspace", Colors::GREEN); + testForceSplitOnMoveToWorkspace(); + // 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 fd42cf062..a410494ab 100644 --- a/hyprtester/src/tests/main/exec.cpp +++ b/hyprtester/src/tests/main/exec.cpp @@ -2,6 +2,7 @@ #include "../../shared.hpp" #include "../../hyprctlCompat.hpp" #include +#include #include #include #include @@ -15,40 +16,46 @@ using namespace Hyprutils::Memory; #define UP CUniquePointer #define SP CSharedPointer -static bool test() { +const static auto SLEEP_DURATIONS = std::array{1, 10}; + +static bool test() { NLog::log("{}Testing process spawning", Colors::GREEN); - // Note: POSIX sleep does not support fractional seconds, so - // can't sleep for less than 1 second. - OK(getFromSocket("/dispatch exec sleep 1")); + for (const auto duration : SLEEP_DURATIONS) { + // Note: POSIX sleep does not support fractional seconds, so + // can't sleep for less than 1 second. + OK(getFromSocket(std::format("/dispatch exec sleep {}", duration))); - // Ensure that sleep is our child - const std::string sleepPidS = Tests::execAndGet("pgrep sleep"); - pid_t sleepPid; - try { - sleepPid = std::stoull(sleepPidS); - } catch (...) { - NLog::log("{}Sleep was not spawned or several sleeps are running: pgrep returned '{}'", Colors::RED, sleepPidS); - return false; + // Ensure that sleep is our child + const std::string sleepPidS = Tests::execAndGet("pgrep sleep"); + pid_t sleepPid; + try { + sleepPid = std::stoull(sleepPidS); + } catch (...) { + NLog::log("{}Sleep was not spawned or several sleeps are running: pgrep returned '{}'", Colors::RED, sleepPidS); + continue; + } + + const std::string sleepParentComm = Tests::execAndGet("cat \"/proc/$(ps -o ppid:1= -p " + sleepPidS + ")/comm\""); + NLog::log("{}Expecting that sleep's parent is Hyprland", Colors::YELLOW); + EXPECT_CONTAINS(sleepParentComm, "Hyprland"); + + std::this_thread::sleep_for(std::chrono::seconds(duration)); + + // Ensure that sleep did not become a zombie + EXPECT(Tests::processAlive(sleepPid), false); + + // kill all + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + NLog::log("{}Expecting 0 windows", Colors::YELLOW); + EXPECT(Tests::windowCount(), 0); + + return !ret; } - const std::string sleepParentComm = Tests::execAndGet("cat \"/proc/$(ps -o ppid:1= -p " + sleepPidS + ")/comm\""); - NLog::log("{}Expecting that sleep's parent is Hyprland", Colors::YELLOW); - EXPECT_CONTAINS(sleepParentComm, "Hyprland"); - - std::this_thread::sleep_for(std::chrono::seconds(1)); - - // Ensure that sleep did not become a zombie - EXPECT(Tests::processAlive(sleepPid), false); - - // kill all - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - - NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); - - return !ret; + return false; } REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/follow_mouse_shrink.cpp b/hyprtester/src/tests/main/follow_mouse_shrink.cpp new file mode 100644 index 000000000..61b1e5c12 --- /dev/null +++ b/hyprtester/src/tests/main/follow_mouse_shrink.cpp @@ -0,0 +1,131 @@ +#include +#include +#include + +#include "../../shared.hpp" +#include "../../hyprctlCompat.hpp" +#include "../shared.hpp" +#include "tests.hpp" + +static int ret = 0; + +static bool spawnKitty(const std::string& class_) { + NLog::log("{}Spawning {}", Colors::YELLOW, class_); + if (!Tests::spawnKitty(class_)) { + NLog::log("{}Error: {} did not spawn", Colors::RED, class_); + return false; + } + return true; +} + +static bool isActiveWindow(const std::string& class_, char fullscreen = '0', bool log = true) { + std::string activeWin = getFromSocket("/activewindow"); + auto winClass = Tests::getWindowAttribute(activeWin, "class:"); + auto winFullscreen = Tests::getWindowAttribute(activeWin, "fullscreen:").back(); + if (winClass.substr(strlen("class: ")) == class_ && winFullscreen == fullscreen) + return true; + else { + if (log) + NLog::log("{}Wrong active window: expected class {} fullscreen '{}', found class {}, fullscreen '{}'", Colors::RED, class_, fullscreen, winClass, winFullscreen); + return false; + } +} + +static bool waitForActiveWindow(const std::string& class_, char fullscreen = '0', bool logLastCheck = true, int maxTries = 50) { + int cnt = 0; + while (!isActiveWindow(class_, fullscreen, false)) { + ++cnt; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + if (cnt > maxTries) { + return isActiveWindow(class_, fullscreen, logLastCheck); + } + } + return true; +} + +static bool test() { + NLog::log("{}Testing follow_mouse_shrink", Colors::GREEN); + + getFromSocket("/dispatch workspace name:follow_mouse_shrink"); + + // follow_mouse 2 so cursor position determines focus (mode 1's delta threshold + // is unreliable with movecursor/simulateMouseMovement). float_switch_override_focus 2 + // enables focus switching between floating windows. + OK(getFromSocket("/keyword input:follow_mouse 2")); + OK(getFromSocket("/keyword input:float_switch_override_focus 2")); + + // Spawn two floating windows with a 20px gap + // fms_a: position (100,100), size 400x400 -> hitbox [100,499] x [100,499] + if (!spawnKitty("fms_a")) + return false; + OK(getFromSocket("/dispatch setfloating")); + OK(getFromSocket("/dispatch resizewindowpixel exact 400 400,activewindow")); + OK(getFromSocket("/dispatch movewindowpixel exact 100 100,activewindow")); + + // fms_b: position (520,100), size 400x400 -> hitbox [520,919] x [100,499] + if (!spawnKitty("fms_b")) + return false; + OK(getFromSocket("/dispatch setfloating")); + OK(getFromSocket("/dispatch resizewindowpixel exact 400 400,activewindow")); + OK(getFromSocket("/dispatch movewindowpixel exact 520 100,activewindow")); + + // --- Test 1: Baseline shrink=0, edge focus works --- + NLog::log("{}Test 1: shrink=0, cursor at B's left edge focuses B", Colors::GREEN); + OK(getFromSocket("/keyword input:follow_mouse_shrink 0")); + // Focus A explicitly, then move cursor inside A so follow_mouse tracks it + OK(getFromSocket("/dispatch focuswindow class:fms_a")); + EXPECT(waitForActiveWindow("fms_a"), true); + OK(getFromSocket("/dispatch movecursor 300 300")); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + // Move to just inside B's left edge + OK(getFromSocket("/dispatch movecursor 521 300")); + EXPECT(waitForActiveWindow("fms_b"), true); + + // --- Test 2: Shrink=20, cursor in dead zone does NOT change focus --- + NLog::log("{}Test 2: shrink=20, cursor in B's dead zone stays on A", Colors::GREEN); + OK(getFromSocket("/keyword input:follow_mouse_shrink 20")); + // Focus A explicitly + OK(getFromSocket("/dispatch focuswindow class:fms_a")); + EXPECT(waitForActiveWindow("fms_a"), true); + OK(getFromSocket("/dispatch movecursor 300 300")); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + // Move to 530,300 -- 10px inside B, within 20px shrink zone (B's shrunk hitbox starts at 540) + OK(getFromSocket("/dispatch movecursor 530 300")); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + EXPECT(isActiveWindow("fms_a"), true); + + // --- Test 3: Shrink=20, cursor well inside inactive window DOES focus it --- + NLog::log("{}Test 3: shrink=20, cursor at B's center focuses B", Colors::GREEN); + // Still focused on A from test 2 + OK(getFromSocket("/dispatch movecursor 720 300")); + EXPECT(waitForActiveWindow("fms_b"), true); + + // --- Test 4: Focused window's hitbox is NOT shrunk --- + NLog::log("{}Test 4a: focused window hitbox is not shrunk", Colors::GREEN); + // Focus A explicitly, move cursor inside A, then move near A's right edge + OK(getFromSocket("/dispatch focuswindow class:fms_a")); + EXPECT(waitForActiveWindow("fms_a"), true); + OK(getFromSocket("/dispatch movecursor 300 300")); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + OK(getFromSocket("/dispatch movecursor 490 300")); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + EXPECT(isActiveWindow("fms_a"), true); + + NLog::log("{}Test 4b: inactive window hitbox IS shrunk at same position", Colors::GREEN); + // Focus B explicitly, then move to (490,300). Now A is inactive with shrunk box ending at 479, so 490 is outside A. + OK(getFromSocket("/dispatch focuswindow class:fms_b")); + EXPECT(waitForActiveWindow("fms_b"), true); + OK(getFromSocket("/dispatch movecursor 720 300")); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + OK(getFromSocket("/dispatch movecursor 490 300")); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + EXPECT(isActiveWindow("fms_b"), true); + + // Cleanup + OK(getFromSocket("/reload")); + Tests::killAllWindows(); + + return ret == 0; +} + +REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/gestures.cpp b/hyprtester/src/tests/main/gestures.cpp index 9b31cdb6d..07cc4ca96 100644 --- a/hyprtester/src/tests/main/gestures.cpp +++ b/hyprtester/src/tests/main/gestures.cpp @@ -160,6 +160,33 @@ static bool test() { // The cursor should have moved because of the gesture EXPECT(cursorPos1 != cursorPos2, true); + // Test that `workspace previous` works correctly after a workspace gesture. + { + OK(getFromSocket("/keyword gestures:workspace_swipe_invert 0")); + OK(getFromSocket("/keyword gestures:workspace_swipe_create_new 1")); + OK(getFromSocket("/dispatch workspace 3")); + + // Come to workspace 5 from workspace 3: 5 will remember that. + OK(getFromSocket("/dispatch workspace 5")); + Tests::spawnKitty(); // Keep workspace 5 open + + // Swipe from 1 to 5: 5 shall remember that. + OK(getFromSocket("/dispatch workspace 1")); + OK(getFromSocket("/dispatch plugin:test:alt 1")); + OK(getFromSocket("/dispatch plugin:test:gesture right,3")); + OK(getFromSocket("/dispatch plugin:test:alt 0")); + EXPECT_CONTAINS(getFromSocket("/activeworkspace"), "ID 5 (5)"); + + // Must return to 1 rather than 3 + OK(getFromSocket("/dispatch workspace previous")); + EXPECT_CONTAINS(getFromSocket("/activeworkspace"), "ID 1 (1)"); + + OK(getFromSocket("/dispatch workspace previous")); + EXPECT_CONTAINS(getFromSocket("/activeworkspace"), "ID 5 (5)"); + + OK(getFromSocket("/dispatch workspace 1")); + } + // kill all NLog::log("{}Killing all windows", Colors::YELLOW); Tests::killAllWindows(); diff --git a/hyprtester/src/tests/main/groups.cpp b/hyprtester/src/tests/main/groups.cpp index 3cf15851a..86ffc7f75 100644 --- a/hyprtester/src/tests/main/groups.cpp +++ b/hyprtester/src/tests/main/groups.cpp @@ -25,6 +25,65 @@ static bool test() { NLog::log("{}Dispatching workspace `groups`", Colors::YELLOW); getFromSocket("/dispatch workspace name:groups"); + NLog::log("{}Testing movewindoworgroup from group to group", Colors::YELLOW); + auto kittyA = Tests::spawnKitty("kittyA"); + if (!kittyA) { + NLog::log("{}Error: kitty did not spawn", Colors::RED); + return false; + } + + // check kitty properties. One kitty should take the entire screen, minus the gaps. + NLog::log("{}Check kittyA dimensions", Colors::YELLOW); + { + auto str = getFromSocket("/clients"); + EXPECT_COUNT_STRING(str, "at: 22,22", 1); + EXPECT_COUNT_STRING(str, "size: 1876,1036", 1); + EXPECT_COUNT_STRING(str, "fullscreen: 0", 1); + } + + auto kittyB = Tests::spawnKitty("kittyB"); + if (!kittyB) { + NLog::log("{}Error: kitty did not spawn", Colors::RED); + return false; + } + + OK(getFromSocket("/dispatch focuswindow class:kittyB")); + OK(getFromSocket("/dispatch togglegroup")); + OK(getFromSocket("/dispatch focuswindow class:kittyA")); + OK(getFromSocket("/dispatch togglegroup")); + + NLog::log("{}Check kittyB dimensions", Colors::YELLOW); + { + auto str = getFromSocket("/activewindow"); + EXPECT_COUNT_STRING(str, "size: 931,1015", 1); + EXPECT_COUNT_STRING(str, "fullscreen: 0", 1); + } + + auto kittyC = Tests::spawnKitty("kittyC"); + if (!kittyC) { + NLog::log("{}Error: kitty did not spawn", Colors::RED); + return false; + } + + NLog::log("{}Check kittyC dimensions", Colors::YELLOW); + { + auto str = getFromSocket("/activewindow"); + EXPECT_COUNT_STRING(str, "size: 931,1015", 1); + EXPECT_COUNT_STRING(str, "fullscreen: 0", 1); + } + + OK(getFromSocket("/dispatch movewindoworgroup r")); + NLog::log("{}Check that dimensions remain the same after move", Colors::YELLOW); + { + auto str = getFromSocket("/activewindow"); + EXPECT_COUNT_STRING(str, "size: 931,1015", 1); + EXPECT_COUNT_STRING(str, "fullscreen: 0", 1); + } + + // kill all + NLog::log("{}Kill windows", Colors::YELLOW); + Tests::killAllWindows(); + NLog::log("{}Spawning kittyProcA", Colors::YELLOW); auto kittyProcA = Tests::spawnKitty(); if (!kittyProcA) { @@ -127,6 +186,34 @@ static bool test() { ret = 1; } + // test movegroupwindow: focus should follow the moved window + NLog::log("{}Test movegroupwindow focus follows window", Colors::YELLOW); + try { + auto str = getFromSocket("/activewindow"); + auto activeBeforeMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16); + OK(getFromSocket("/dispatch movegroupwindow f")); + str = getFromSocket("/activewindow"); + auto activeAfterMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16); + EXPECT(activeAfterMove, activeBeforeMove); + } catch (...) { + NLog::log("{}Fail at getting prop", Colors::RED); + ret = 1; + } + + // and backwards + NLog::log("{}Test movegroupwindow backwards", Colors::YELLOW); + try { + auto str = getFromSocket("/activewindow"); + auto activeBeforeMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16); + OK(getFromSocket("/dispatch movegroupwindow b")); + str = getFromSocket("/activewindow"); + auto activeAfterMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16); + EXPECT(activeAfterMove, activeBeforeMove); + } catch (...) { + NLog::log("{}Fail at getting prop", Colors::RED); + ret = 1; + } + NLog::log("{}Disable autogrouping", Colors::YELLOW); OK(getFromSocket("/keyword group:auto_group false")); @@ -173,6 +260,182 @@ 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); + + // Tests for grouping/merging logic + NLog::log("{}Testing locked groups w/ invade", Colors::GREEN); + + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); + + // Test normal, unlocked groups + { + auto winA = Tests::spawnKitty("unlocked"); + if (!winA) { + NLog::log("{}Error: unlocked kitty did not spawn", Colors::RED); + return false; + } + OK(getFromSocket("/dispatch togglegroup")); + + auto winB = Tests::spawnKitty("top"); + if (!winB) { + NLog::log("{}Error: top kitty did not spawn", Colors::RED); + return false; + } + + // Verify it DID merge into a group + { + auto str = getFromSocket("/clients"); + EXPECT_COUNT_STRING(str, "at: 22,22", 2); + } + } + + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); + + // Test locked groups + { + auto lockedWin = Tests::spawnKitty("locked"); + if (!lockedWin) { + NLog::log("{}Error: locked kitty did not spawn", Colors::RED); + return false; + } + OK(getFromSocket("/dispatch togglegroup")); + OK(getFromSocket(std::format("/dispatch focuswindow pid:{}", lockedWin->pid()))); + OK(getFromSocket("/dispatch lockactivegroup lock")); + + auto winB = Tests::spawnKitty("top"); + if (!winB) { + NLog::log("{}Error: top kitty did not spawn", Colors::RED); + return false; + } + + // Verify it did NOT merge into the locked group + { + auto str = getFromSocket("/clients"); + EXPECT_COUNT_STRING(str, "at: 22,22", 1); + } + } + + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); + + // Test locked groups WITH invade rule + { + OK(getFromSocket("/keyword windowrule[locked-im]:match:class ^locked|invade$")); + OK(getFromSocket("/keyword windowrule[locked-im]:group set always lock invade")); + + auto lockedWin = Tests::spawnKitty("locked"); + if (!lockedWin) { + NLog::log("{}Error: locked kitty did not spawn", Colors::RED); + return false; + } + + auto invadingWin = Tests::spawnKitty("invade"); + if (!invadingWin) { + NLog::log("{}Error: invading kitty did not spawn", Colors::RED); + return false; + } + + // Verify it DID merge into the locked group + auto str = getFromSocket("/clients"); + EXPECT_COUNT_STRING(str, "at: 22,22", 2); + } + + 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 e8759d28c..4f3fba752 100644 --- a/hyprtester/src/tests/main/hyprctl.cpp +++ b/hyprtester/src/tests/main/hyprctl.cpp @@ -77,6 +77,16 @@ static bool testGetprop() { EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "100 50"); EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [100,50]})"); + // expr-based min/max _size + getFromSocket("/dispatch setfloating class:kitty"); // need to set floating for tests below + getFromSocket("/dispatch setprop class:kitty max_size 90+10 25*2"); // set max to the same as min above, forcing window to 100*50 + EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size"), "100 50"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size -j"), R"({"max_size": [100,50]})"); + getFromSocket("/dispatch setprop class:kitty min_size window_w*0.5 window_h-10"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "50 40"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [50,40]})"); + getFromSocket("/dispatch settiled class:kitty"); // go back to tiled for consistency + // opacity EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity"), "1"); EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity -j"), R"({"opacity": 1})"); @@ -163,6 +173,13 @@ static bool testGetprop() { return true; } +static void testSubmap() { + NLog::log("{}Testing hyprctl submap", Colors::GREEN); + + EXPECT(getCommandStdOut("hyprctl submap"), "default\n"); + EXPECT(getCommandStdOut("hyprctl submap -j | jq -r \".\""), "default"); +} + static bool test() { NLog::log("{}Testing hyprctl", Colors::GREEN); @@ -176,6 +193,7 @@ static bool test() { testGetprop(); testDevicesActiveLayoutIndex(); + testSubmap(); getFromSocket("/reload"); return !ret; diff --git a/hyprtester/src/tests/main/keybinds.cpp b/hyprtester/src/tests/main/keybinds.cpp index a2fe2f37a..dcde9839d 100644 --- a/hyprtester/src/tests/main/keybinds.cpp +++ b/hyprtester/src/tests/main/keybinds.cpp @@ -86,8 +86,7 @@ static void testBind() { // press keybind OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29")); // await flag - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - EXPECT(checkFlag(), true); + EXPECT(attemptCheckFlag(20, 50), true); // release keybind OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29")); EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok"); @@ -99,8 +98,7 @@ static void testBindKey() { // press keybind OK(getFromSocket("/dispatch plugin:test:keybind 1,0,29")); // await flag - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - EXPECT(checkFlag(), true); + EXPECT(attemptCheckFlag(20, 50), true); // release keybind OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29")); EXPECT(getFromSocket("/keyword unbind ,Y"), "ok"); @@ -116,7 +114,7 @@ static void testLongPress() { std::this_thread::sleep_for(std::chrono::milliseconds(50)); EXPECT(checkFlag(), false); // await repeat delay - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); EXPECT(checkFlag(), true); // release keybind OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29")); @@ -133,7 +131,7 @@ static void testKeyLongPress() { std::this_thread::sleep_for(std::chrono::milliseconds(50)); EXPECT(checkFlag(), false); // await repeat delay - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); EXPECT(checkFlag(), true); // release keybind OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29")); @@ -152,7 +150,7 @@ static void testLongPressRelease() { // release keybind OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29")); // await repeat delay - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); EXPECT(checkFlag(), false); EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok"); } @@ -169,7 +167,7 @@ static void testLongPressOnlyKeyRelease() { // release key, keep modifier OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29")); // await repeat delay - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); EXPECT(checkFlag(), false); OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29")); EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok"); @@ -182,13 +180,13 @@ static void testRepeat() { // press keybind OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29")); // await flag - std::this_thread::sleep_for(std::chrono::milliseconds(50)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); EXPECT(checkFlag(), true); // await repeat delay - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); EXPECT(checkFlag(), true); // check that it continues repeating - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); EXPECT(checkFlag(), true); // release keybind OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29")); @@ -205,10 +203,10 @@ static void testKeyRepeat() { std::this_thread::sleep_for(std::chrono::milliseconds(50)); EXPECT(checkFlag(), true); // await repeat delay - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); EXPECT(checkFlag(), true); // check that it continues repeating - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); EXPECT(checkFlag(), true); // release keybind OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29")); @@ -216,7 +214,17 @@ static void testKeyRepeat() { } static void testRepeatRelease() { - EXPECT(checkFlag(), false); + // wait until flag becomes false (CI timing can vary) + bool ok = false; + for (int i = 0; i < 20; ++i) { + if (!checkFlag()) { + ok = true; + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + EXPECT(ok, true); EXPECT(getFromSocket("/keyword binde SUPER,Y,exec,touch " + flagFile), "ok"); EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok"); // press keybind @@ -227,10 +235,12 @@ static void testRepeatRelease() { // release keybind OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29")); // await repeat delay - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + clearFlag(); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); EXPECT(checkFlag(), false); // check that it is not repeating - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); EXPECT(checkFlag(), false); EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok"); } @@ -242,15 +252,17 @@ static void testRepeatOnlyKeyRelease() { // press keybind OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29")); // await flag - std::this_thread::sleep_for(std::chrono::milliseconds(50)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); EXPECT(checkFlag(), true); // release key, keep modifier OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29")); // await repeat delay - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + clearFlag(); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); EXPECT(checkFlag(), false); // check that it is not repeating - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); EXPECT(checkFlag(), false); OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29")); EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok"); @@ -315,9 +327,9 @@ static void testShortcutLongPress() { // press keybind OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29")); // await repeat delay - std::this_thread::sleep_for(std::chrono::milliseconds(150)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29")); - std::this_thread::sleep_for(std::chrono::milliseconds(150)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); const std::string output = readKittyOutput(); int yCount = Tests::countOccurrences(output, "y"); // sometimes 1, sometimes 2, not sure why @@ -347,7 +359,7 @@ static void testShortcutLongPressKeyRelease() { // release key, keep modifier OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29")); // await repeat delay - std::this_thread::sleep_for(std::chrono::milliseconds(150)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); const std::string output = readKittyOutput(); // disabled: doesn't work on CI // EXPECT_COUNT_STRING(output, "y", 1); @@ -454,6 +466,35 @@ static void testSubmap() { Tests::killAllWindows(); } +static void testBindsAfterScroll() { + NLog::log("{}Testing binds after scroll", Colors::GREEN); + + clearFlag(); + OK(getFromSocket("/keyword binds Alt_R,w,exec,touch " + flagFile)); + + // press keybind before scroll + OK(getFromSocket("/dispatch plugin:test:keybind 1,0,108")); // Alt_R press + OK(getFromSocket("/dispatch plugin:test:keybind 1,4,25")); // w press + EXPECT(attemptCheckFlag(20, 50), true); + OK(getFromSocket("/dispatch plugin:test:keybind 0,4,25")); // w release + OK(getFromSocket("/dispatch plugin:test:keybind 0,0,108")); // Alt_R release + + // scroll + OK(getFromSocket("/dispatch plugin:test:scroll 120")); + OK(getFromSocket("/dispatch plugin:test:scroll -120")); + OK(getFromSocket("/dispatch plugin:test:scroll 120")); + + // press keybind after scroll + OK(getFromSocket("/dispatch plugin:test:keybind 1,0,108")); // Alt_R press + OK(getFromSocket("/dispatch plugin:test:keybind 1,4,25")); // w press + EXPECT(attemptCheckFlag(20, 50), true); + OK(getFromSocket("/dispatch plugin:test:keybind 0,4,25")); // w release + OK(getFromSocket("/dispatch plugin:test:keybind 0,0,108")); // Alt_R release + + clearFlag(); + OK(getFromSocket("/keyword unbind Alt_R,w")); +} + static void testSubmapUniversal() { NLog::log("{}Testing submap universal", Colors::GREEN); @@ -482,9 +523,39 @@ static void testSubmapUniversal() { EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok"); } +static void testPerDeviceKeybind() { + NLog::log("{}Testing per-device binds", Colors::GREEN); + + // Inclusive + EXPECT(checkFlag(), false); + EXPECT(getFromSocket("/keyword bindk SUPER,Y,test-keyboard-1,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"); + + // Exclusive + EXPECT(checkFlag(), false); + EXPECT(getFromSocket("/keyword bindk SUPER,Y,!test-keyboard-1,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"); + + // With description + EXPECT(checkFlag(), false); + EXPECT(getFromSocket("/keyword binddk SUPER,Y,test-keyboard-1,test description,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"); +} + static bool test() { NLog::log("{}Testing keybinds", Colors::GREEN); + clearFlag(); + testBind(); testBindKey(); testLongPress(); @@ -503,6 +574,8 @@ static bool test() { testShortcutRepeatKeyRelease(); testSubmap(); testSubmapUniversal(); + testBindsAfterScroll(); + testPerDeviceKeybind(); clearFlag(); return !ret; diff --git a/hyprtester/src/tests/main/layer.cpp b/hyprtester/src/tests/main/layer.cpp new file mode 100644 index 000000000..73e30ba67 --- /dev/null +++ b/hyprtester/src/tests/main/layer.cpp @@ -0,0 +1,53 @@ +#include "../../Log.hpp" +#include "../shared.hpp" +#include "tests.hpp" +#include "../../shared.hpp" +#include "../../hyprctlCompat.hpp" +#include +#include + +static int ret = 0; + +using namespace Hyprutils::OS; +using namespace Hyprutils::Memory; + +static bool spawnLayer(const std::string& namespace_) { + NLog::log("{}Spawning kitty layer {}", Colors::YELLOW, namespace_); + if (!Tests::spawnLayerKitty(namespace_)) { + NLog::log("{}Error: {} layer did not spawn", Colors::RED, namespace_); + return false; + } + return true; +} + +static bool test() { + NLog::log("{}Testing plugin layerrules", Colors::GREEN); + + if (!spawnLayer("rule-layer")) + return false; + + OK(getFromSocket("/dispatch plugin:test:add_layer_rule")); + OK(getFromSocket("/reload")); + + OK(getFromSocket("/keyword layerrule match:namespace rule-layer, plugin_rule effect")); + + if (!spawnLayer("rule-layer")) + return false; + + if (!spawnLayer("norule-layer")) + return false; + + OK(getFromSocket("/dispatch plugin:test:check_layer_rule")); + + OK(getFromSocket("/reload")); + + NLog::log("{}Killing all layers", Colors::YELLOW); + Tests::killAllLayers(); + + NLog::log("{}Expecting 0 layers", Colors::YELLOW); + EXPECT(Tests::layerCount(), 0); + + return !ret; +} + +REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/layout.cpp b/hyprtester/src/tests/main/layout.cpp new file mode 100644 index 000000000..208bee30c --- /dev/null +++ b/hyprtester/src/tests/main/layout.cpp @@ -0,0 +1,191 @@ +#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 testFocusMRUAfterClose() { + NLog::log("{}Testing focus after close (MRU order)", Colors::GREEN); + + OK(getFromSocket("/reload")); + OK(getFromSocket("/keyword dwindle:default_split_ratio 1.25")); + OK(getFromSocket("/keyword input:focus_on_close 2")); + + EXPECT(!!Tests::spawnKitty("kitty_A"), true); + EXPECT(!!Tests::spawnKitty("kitty_B"), true); + EXPECT(!!Tests::spawnKitty("kitty_C"), true); + + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + OK(getFromSocket("/dispatch focuswindow class:kitty_B")); + OK(getFromSocket("/dispatch focuswindow class:kitty_C")); + + OK(getFromSocket("/dispatch killactive")); + Tests::waitUntilWindowsN(2); + + { + auto str = getFromSocket("/activewindow"); + EXPECT(str.contains("class: kitty_B"), true); + } + + OK(getFromSocket("/dispatch killactive")); + Tests::waitUntilWindowsN(1); + + { + auto str = getFromSocket("/activewindow"); + EXPECT(str.contains("class: kitty_A"), true); + } + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); + return true; +} + +static bool testFocusPreservedLayoutChange() { + NLog::log("{}Testing focus is preserved on layout change", Colors::GREEN); + + OK(getFromSocket("/keyword general:layout master")); + + EXPECT(!!Tests::spawnKitty("kitty_A"), true); + EXPECT(!!Tests::spawnKitty("kitty_B"), true); + EXPECT(!!Tests::spawnKitty("kitty_C"), true); + EXPECT(!!Tests::spawnKitty("kitty_D"), true); + + OK(getFromSocket("/dispatch focuswindow class:kitty_C")); + + OK(getFromSocket("/keyword general:layout monocle")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT(str.contains("class: kitty_C"), true); + } + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); + return true; +} + +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(); + testFocusMRUAfterClose(); + testFocusPreservedLayoutChange(); + + // 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 9cd20e83a..9aaa4cf0f 100644 --- a/hyprtester/src/tests/main/master.cpp +++ b/hyprtester/src/tests/main/master.cpp @@ -3,7 +3,53 @@ #include "../../hyprctlCompat.hpp" #include "tests.hpp" -static int ret = 0; +static int ret = 0; + +// reqs 1 master 3 slaves +static void testOrientations() { + OK(getFromSocket("/keyword master:orientation top")); + + // top + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1876"); + } + + // cycle = top, right, bottom, center, left + + // right + OK(getFromSocket("/dispatch layoutmsg orientationnext")); + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 873,22"); + EXPECT_CONTAINS(str, "size: 1025,1036"); + } + + // bottom + OK(getFromSocket("/dispatch layoutmsg orientationnext")); + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,495"); + EXPECT_CONTAINS(str, "size: 1876"); + } + + // center + OK(getFromSocket("/dispatch layoutmsg orientationnext")); + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 450,22"); + EXPECT_CONTAINS(str, "size: 1020,1036"); + } + + // left + OK(getFromSocket("/dispatch layoutmsg orientationnext")); + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1025,1036"); + } +} static void focusMasterPrevious() { // setup @@ -44,11 +90,74 @@ static void focusMasterPrevious() { OK(getFromSocket("/dispatch layoutmsg focusmaster previous")); EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master"); + testOrientations(); + // clean up NLog::log("{}Killing all windows", Colors::YELLOW); Tests::killAllWindows(); } +static void testFsBehavior() { + // Master will re-send data to fullscreen / maximized windows, which can interfere with misc:on_focus_under_fullscreen + // check that it doesn't. + + for (auto const& win : {"master", "slave1", "slave2"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + OK(getFromSocket("/dispatch focuswindow class:master")); + OK(getFromSocket("/dispatch fullscreen 1")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1876,1036"); + EXPECT_CONTAINS(str, "class: master"); + } + + OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 1")); + + Tests::spawnKitty("new_master"); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1876,1036"); + EXPECT_CONTAINS(str, "class: new_master"); + EXPECT_CONTAINS(str, "fullscreen: 1"); + } + + OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0")); + + Tests::spawnKitty("ignored"); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1876,1036"); + EXPECT_CONTAINS(str, "class: new_master"); + EXPECT_CONTAINS(str, "fullscreen: 1"); + } + + OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2")); + + Tests::spawnKitty("vaxwashere"); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: vaxwashere"); + EXPECT_CONTAINS(str, "fullscreen: 0"); + } + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + static bool test() { NLog::log("{}Testing Master layout", Colors::GREEN); @@ -60,6 +169,9 @@ static bool test() { NLog::log("{}Testing `focusmaster previous` layoutmsg", Colors::GREEN); focusMasterPrevious(); + NLog::log("{}Testing fs behavior", Colors::GREEN); + testFsBehavior(); + // clean up NLog::log("Cleaning up", Colors::YELLOW); OK(getFromSocket("/dispatch workspace 1")); diff --git a/hyprtester/src/tests/main/misc.cpp b/hyprtester/src/tests/main/misc.cpp index 3187694ba..0c7c9ec13 100644 --- a/hyprtester/src/tests/main/misc.cpp +++ b/hyprtester/src/tests/main/misc.cpp @@ -18,6 +18,121 @@ using namespace Hyprutils::Memory; #define UP CUniquePointer #define SP CSharedPointer +// Uncomment once test vm can run hyprland-dialog +// static void testAnrDialogs() { +// NLog::log("{}Testing ANR dialogs", Colors::YELLOW); +// +// OK(getFromSocket("/keyword misc:enable_anr_dialog true")); +// OK(getFromSocket("/keyword misc:anr_missed_pings 1")); +// +// NLog::log("{}ANR dialog: regular workspaces", Colors::YELLOW); +// { +// OK(getFromSocket("/dispatch workspace 2")); +// +// auto kitty = Tests::spawnKitty("bad_kitty"); +// +// if (!kitty) { +// ret = 1; +// return; +// } +// +// { +// auto str = getFromSocket("/activewindow"); +// EXPECT_CONTAINS(str, "workspace: 2"); +// } +// +// OK(getFromSocket("/dispatch workspace 1")); +// +// ::kill(kitty->pid(), SIGSTOP); +// Tests::waitUntilWindowsN(2); +// +// { +// auto str = getFromSocket("/activeworkspace"); +// EXPECT_CONTAINS(str, "windows: 0"); +// } +// +// { +// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog")) +// auto str = getFromSocket("/activewindow"); +// EXPECT_CONTAINS(str, "workspace: 2"); +// } +// } +// +// Tests::killAllWindows(); +// +// NLog::log("{}ANR dialog: named workspaces", Colors::YELLOW); +// { +// OK(getFromSocket("/dispatch workspace name:yummy")); +// +// auto kitty = Tests::spawnKitty("bad_kitty"); +// +// if (!kitty) { +// ret = 1; +// return; +// } +// +// { +// auto str = getFromSocket("/activewindow"); +// EXPECT_CONTAINS(str, "yummy"); +// } +// +// OK(getFromSocket("/dispatch workspace 1")); +// +// ::kill(kitty->pid(), SIGSTOP); +// Tests::waitUntilWindowsN(2); +// +// { +// auto str = getFromSocket("/activeworkspace"); +// EXPECT_CONTAINS(str, "windows: 0"); +// } +// +// { +// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog")) +// auto str = getFromSocket("/activewindow"); +// EXPECT_CONTAINS(str, "yummy"); +// } +// } +// +// Tests::killAllWindows(); +// +// NLog::log("{}ANR dialog: special workspaces", Colors::YELLOW); +// { +// OK(getFromSocket("/dispatch workspace special:apple")); +// +// auto kitty = Tests::spawnKitty("bad_kitty"); +// +// if (!kitty) { +// ret = 1; +// return; +// } +// +// { +// auto str = getFromSocket("/activewindow"); +// EXPECT_CONTAINS(str, "special:apple"); +// } +// +// OK(getFromSocket("/dispatch togglespecialworkspace apple")); +// OK(getFromSocket("/dispatch workspace 1")); +// +// ::kill(kitty->pid(), SIGSTOP); +// Tests::waitUntilWindowsN(2); +// +// { +// auto str = getFromSocket("/activeworkspace"); +// EXPECT_CONTAINS(str, "windows: 0"); +// } +// +// { +// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog")) +// auto str = getFromSocket("/activewindow"); +// EXPECT_CONTAINS(str, "special:apple"); +// } +// } +// +// OK(getFromSocket("/reload")); +// Tests::killAllWindows(); +// } + static bool test() { NLog::log("{}Testing config: misc:", Colors::GREEN); @@ -62,6 +177,7 @@ static bool test() { { auto str = getFromSocket("/activewindow"); EXPECT_CONTAINS(str, "fullscreen: 2"); + EXPECT_CONTAINS(str, "fullscreenClient: 2"); EXPECT_CONTAINS(str, "kitty_A"); } @@ -70,6 +186,7 @@ static bool test() { { auto str = getFromSocket("/activewindow"); EXPECT_CONTAINS(str, "fullscreen: 2"); + EXPECT_CONTAINS(str, "fullscreenClient: 2"); EXPECT_CONTAINS(str, "kitty_A"); } @@ -79,6 +196,7 @@ static bool test() { // should be ignored as per focus_under_fullscreen 0 auto str = getFromSocket("/activewindow"); EXPECT_CONTAINS(str, "fullscreen: 2"); + EXPECT_CONTAINS(str, "fullscreenClient: 2"); EXPECT_CONTAINS(str, "kitty_A"); } @@ -89,6 +207,7 @@ static bool test() { { auto str = getFromSocket("/activewindow"); EXPECT_CONTAINS(str, "fullscreen: 2"); + EXPECT_CONTAINS(str, "fullscreenClient: 2"); EXPECT_CONTAINS(str, "kitty_C"); } @@ -99,6 +218,7 @@ static bool test() { { auto str = getFromSocket("/activewindow"); EXPECT_CONTAINS(str, "fullscreen: 0"); + EXPECT_CONTAINS(str, "fullscreenClient: 0"); EXPECT_CONTAINS(str, "kitty_D"); } @@ -118,6 +238,7 @@ static bool test() { { auto str = getFromSocket("/activewindow"); EXPECT_CONTAINS(str, "fullscreen: 2"); + EXPECT_CONTAINS(str, "fullscreenClient: 2"); } OK(getFromSocket("/dispatch killwindow activewindow")); @@ -126,6 +247,7 @@ static bool test() { { auto str = getFromSocket("/activewindow"); EXPECT_CONTAINS(str, "fullscreen: 0"); + EXPECT_CONTAINS(str, "fullscreenClient: 0"); } Tests::spawnKitty("kitty_B"); @@ -138,6 +260,7 @@ static bool test() { { auto str = getFromSocket("/activewindow"); EXPECT_CONTAINS(str, "fullscreen: 2"); + EXPECT_CONTAINS(str, "fullscreenClient: 2"); } Tests::killAllWindows(); @@ -153,6 +276,7 @@ static bool test() { { auto str = getFromSocket("/activewindow"); EXPECT_CONTAINS(str, "fullscreen: 2"); + EXPECT_CONTAINS(str, "fullscreenClient: 2"); } OK(getFromSocket("/dispatch fullscreen 0 unset")); @@ -160,6 +284,7 @@ static bool test() { { auto str = getFromSocket("/activewindow"); EXPECT_CONTAINS(str, "fullscreen: 0"); + EXPECT_CONTAINS(str, "fullscreenClient: 0"); } OK(getFromSocket("/dispatch fullscreen 1 toggle")); @@ -167,6 +292,7 @@ static bool test() { { auto str = getFromSocket("/activewindow"); EXPECT_CONTAINS(str, "fullscreen: 1"); + EXPECT_CONTAINS(str, "fullscreenClient: 1"); } OK(getFromSocket("/dispatch fullscreen 1 toggle")); @@ -174,6 +300,23 @@ static bool test() { { auto str = getFromSocket("/activewindow"); EXPECT_CONTAINS(str, "fullscreen: 0"); + EXPECT_CONTAINS(str, "fullscreenClient: 0"); + } + + OK(getFromSocket("/dispatch fullscreenstate 3 3 set")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "fullscreen: 3"); + EXPECT_CONTAINS(str, "fullscreenClient: 3"); + } + + OK(getFromSocket("/dispatch fullscreenstate 3 3 set")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "fullscreen: 3"); + EXPECT_CONTAINS(str, "fullscreenClient: 3"); } OK(getFromSocket("/dispatch fullscreenstate 2 2 set")); @@ -181,6 +324,7 @@ static bool test() { { auto str = getFromSocket("/activewindow"); EXPECT_CONTAINS(str, "fullscreen: 2"); + EXPECT_CONTAINS(str, "fullscreenClient: 2"); } OK(getFromSocket("/dispatch fullscreenstate 2 2 set")); @@ -188,6 +332,7 @@ static bool test() { { auto str = getFromSocket("/activewindow"); EXPECT_CONTAINS(str, "fullscreen: 2"); + EXPECT_CONTAINS(str, "fullscreenClient: 2"); } OK(getFromSocket("/dispatch fullscreenstate 2 2 toggle")); @@ -195,6 +340,7 @@ static bool test() { { auto str = getFromSocket("/activewindow"); EXPECT_CONTAINS(str, "fullscreen: 0"); + EXPECT_CONTAINS(str, "fullscreenClient: 0"); } OK(getFromSocket("/dispatch fullscreenstate 2 2 toggle")); @@ -202,6 +348,7 @@ static bool test() { { auto str = getFromSocket("/activewindow"); EXPECT_CONTAINS(str, "fullscreen: 2"); + EXPECT_CONTAINS(str, "fullscreenClient: 2"); } // Ensure that the process autostarted in the config does not diff --git a/hyprtester/src/tests/main/moveintoorcreategroup.cpp b/hyprtester/src/tests/main/moveintoorcreategroup.cpp new file mode 100644 index 000000000..ecb491bc2 --- /dev/null +++ b/hyprtester/src/tests/main/moveintoorcreategroup.cpp @@ -0,0 +1,111 @@ +#include "tests.hpp" +#include "../../shared.hpp" +#include "../../hyprctlCompat.hpp" +#include +#include +#include +#include +#include +#include +#include +#include "../shared.hpp" + +static int ret = 0; + +using namespace Hyprutils::OS; +using namespace Hyprutils::Memory; + +#define UP CUniquePointer +#define SP CSharedPointer + +static bool test() { + NLog::log("{}Testing moveintoorcreategroup", Colors::GREEN); + + NLog::log("{}Dispatching workspace `moveintoorcreategroup`", Colors::YELLOW); + getFromSocket("/dispatch workspace name:moveintoorcreategroup"); + + OK(getFromSocket("/keyword group:auto_group false")); + + NLog::log("{}Spawning kittyA", Colors::YELLOW); + auto kittyA = Tests::spawnKitty("kitty_A"); + if (!kittyA) { + NLog::log("{}Error: kittyA did not spawn", Colors::RED); + return false; + } + + NLog::log("{}Spawning kittyB", Colors::YELLOW); + auto kittyB = Tests::spawnKitty("kitty_B"); + if (!kittyB) { + NLog::log("{}Error: kittyB did not spawn", Colors::RED); + return false; + } + + NLog::log("{}Expecting 2 windows", Colors::YELLOW); + EXPECT(Tests::windowCount(), 2); + + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "grouped: 0"); + } + + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + + NLog::log("{}Move kittyA into group with kittyB (creates group)", Colors::YELLOW); + OK(getFromSocket("/dispatch moveintoorcreategroup r")); + + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "grouped:"); + } + + NLog::log("{}Verify active window is kitty_A (the moved window)", Colors::YELLOW); + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "kitty_A"); + } + + NLog::log("{}Kill windows", Colors::YELLOW); + Tests::killAllWindows(); + + NLog::log("{}Expecting 0 windows", Colors::YELLOW); + EXPECT(Tests::windowCount(), 0); + + NLog::log("{}Testing moveintoorcreategroup into existing group", Colors::YELLOW); + + NLog::log("{}Spawning kittyC", Colors::YELLOW); + auto kittyC = Tests::spawnKitty("kitty_C"); + NLog::log("{}Spawning kittyD", Colors::YELLOW); + auto kittyD = Tests::spawnKitty("kitty_D"); + NLog::log("{}Spawning kittyE", Colors::YELLOW); + auto kittyE = Tests::spawnKitty("kitty_E"); + + NLog::log("{}Expecting 3 windows", Colors::YELLOW); + EXPECT(Tests::windowCount(), 3); + + OK(getFromSocket("/dispatch focuswindow class:kitty_D")); + OK(getFromSocket("/dispatch togglegroup")); + + OK(getFromSocket("/dispatch focuswindow class:kitty_E")); + + NLog::log("{}Move kittyE into existing group with kittyD", Colors::YELLOW); + OK(getFromSocket("/dispatch moveintoorcreategroup l")); + + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "grouped:"); + } + + NLog::log("{}Verify active window is kitty_E (the moved window)", Colors::YELLOW); + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "kitty_E"); + } + + NLog::log("{}Kill windows", Colors::YELLOW); + Tests::killAllWindows(); + OK(getFromSocket("/keyword group:auto_group true")); + + return !ret; +} + +REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/scroll.cpp b/hyprtester/src/tests/main/scroll.cpp new file mode 100644 index 000000000..3662f9658 --- /dev/null +++ b/hyprtester/src/tests/main/scroll.cpp @@ -0,0 +1,258 @@ +#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 testWindowRule() { + NLog::log("{}Testing Scrolling Width", Colors::GREEN); + + // inject a new rule. + OK(getFromSocket("/keyword windowrule[scrolling-width]:match:class kitty_scroll")); + OK(getFromSocket("/keyword windowrule[scrolling-width]:scrolling_width 0.1")); + + if (!Tests::spawnKitty("kitty_scroll")) { + NLog::log("{}Failed to spawn kitty with win class `kitty_scroll`", Colors::RED); + return false; + } + + if (!Tests::spawnKitty("kitty_scroll")) { + NLog::log("{}Failed to spawn kitty with win class `kitty_scroll`", Colors::RED); + return false; + } + + EXPECT(Tests::windowCount(), 2); + + // not the greatest test, but as long as res and gaps don't change, we good. + EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 174,1036"); + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); + return true; +} + +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(); + + testWindowRule(); + + // 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 3265bf72c..32673d280 100644 --- a/hyprtester/src/tests/main/window.cpp +++ b/hyprtester/src/tests/main/window.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -6,6 +7,7 @@ #include #include #include +#include #include "../../shared.hpp" #include "../../hyprctlCompat.hpp" @@ -23,15 +25,26 @@ static bool spawnKitty(const std::string& class_, const std::vector return true; } -static std::string getWindowAttribute(const std::string& winInfo, const std::string& attr) { - auto pos = winInfo.find(attr); - if (pos == std::string::npos) { - NLog::log("{}Wrong window attribute", Colors::RED); - ret = 1; - return "Wrong window attribute"; +/// 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 ""; } - auto pos2 = winInfo.find('\n', pos); - return winInfo.substr(pos, pos2 - pos); + (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 getWindowAddress(const std::string& winInfo) { @@ -68,10 +81,10 @@ static void testSwapWindow() { // Test swapwindow by direction { getFromSocket("/dispatch focuswindow class:kitty_A"); - auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:"); - NLog::log("{}Testing kitty_A {}, swapwindow with direction 'l'", Colors::YELLOW, pos); + auto pos = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:"); + NLog::log("{}Testing kitty_A {}, swapwindow with direction 'r'", Colors::YELLOW, pos); - OK(getFromSocket("/dispatch swapwindow l")); + OK(getFromSocket("/dispatch swapwindow r")); OK(getFromSocket("/dispatch focuswindow class:kitty_B")); EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", pos)); @@ -80,7 +93,7 @@ static void testSwapWindow() { // Test swapwindow by class { getFromSocket("/dispatch focuswindow class:kitty_A"); - auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:"); + auto pos = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:"); NLog::log("{}Testing kitty_A {}, swapwindow with class:kitty_B", Colors::YELLOW, pos); OK(getFromSocket("/dispatch swapwindow class:kitty_B")); @@ -94,7 +107,7 @@ static void testSwapWindow() { getFromSocket("/dispatch focuswindow class:kitty_B"); auto addr = getWindowAddress(getFromSocket("/activewindow")); getFromSocket("/dispatch focuswindow class:kitty_A"); - auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:"); + auto pos = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:"); NLog::log("{}Testing kitty_A {}, swapwindow with address:0x{}(kitty_B)", Colors::YELLOW, pos, addr); OK(getFromSocket(std::format("/dispatch swapwindow address:0x{}", addr))); @@ -117,7 +130,7 @@ static void testSwapWindow() { { getFromSocket("/dispatch focuswindow class:kitty_B"); auto addr = getWindowAddress(getFromSocket("/activewindow")); - auto ws = getWindowAttribute(getFromSocket("/activewindow"), "workspace:"); + auto ws = Tests::getWindowAttribute(getFromSocket("/activewindow"), "workspace:"); NLog::log("{}Sending address:0x{}(kitty_B) to workspace \"swapwindow2\"", Colors::YELLOW, addr); OK(getFromSocket("/dispatch movetoworkspacesilent name:swapwindow2")); @@ -196,10 +209,10 @@ static void testGroupRules() { Tests::killAllWindows(); } -static bool isActiveWindow(const std::string& class_, char fullscreen, bool log = true) { +static bool isActiveWindow(const std::string& class_, char fullscreen = '0', bool log = true) { std::string activeWin = getFromSocket("/activewindow"); - auto winClass = getWindowAttribute(activeWin, "class:"); - auto winFullscreen = getWindowAttribute(activeWin, "fullscreen:").back(); + auto winClass = Tests::getWindowAttribute(activeWin, "class:"); + auto winFullscreen = Tests::getWindowAttribute(activeWin, "fullscreen:").back(); if (winClass.substr(strlen("class: ")) == class_ && winFullscreen == fullscreen) return true; else { @@ -209,13 +222,13 @@ static bool isActiveWindow(const std::string& class_, char fullscreen, bool log } } -static bool waitForActiveWindow(const std::string& class_, char fullscreen, int maxTries = 50) { +static bool waitForActiveWindow(const std::string& class_, char fullscreen = '0', bool logLastCheck = true, int maxTries = 50) { int cnt = 0; while (!isActiveWindow(class_, fullscreen, false)) { ++cnt; std::this_thread::sleep_for(std::chrono::milliseconds(100)); if (cnt > maxTries) { - return isActiveWindow(class_, fullscreen, true); + return isActiveWindow(class_, fullscreen, logLastCheck); } } return true; @@ -231,24 +244,6 @@ static bool testWindowFocusOnFullscreenConflict() { OK(getFromSocket("/keyword misc:focus_on_activate true")); - auto spawnKittyActivating = [] -> std::string { - // `XXXXXX` is what `mkstemp` expects to find in the string - std::string tmpFilename = (std::filesystem::temp_directory_path() / "XXXXXX").string(); - int fd = mkstemp(tmpFilename.data()); - if (fd < 0) { - NLog::log("{}Error: could not create tmp file: errno {}", Colors::RED, errno); - return ""; - } - (void)close(fd); - bool ok = spawnKitty("kitty_activating", - {"-o", "allow_remote_control=yes", "--", "/bin/sh", "-c", "while [ -f \"" + tmpFilename + "\" ]; do :; done; kitten @ focus-window; sleep infinity"}); - if (!ok) { - NLog::log("{}Error: failed to spawn kitty", Colors::RED); - return ""; - } - return tmpFilename; - }; - // Unfullscreen on conflict { OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2")); @@ -343,6 +338,365 @@ static bool testWindowFocusOnFullscreenConflict() { return true; } +static void testMaximizeSize() { + NLog::log("{}Testing maximize size", Colors::GREEN); + + EXPECT(spawnKitty("kitty_A"), true); + + // check kitty properties. Maximizing shouldnt change its size + { + auto str = getFromSocket("/clients"); + EXPECT(str.contains("at: 22,22"), true); + EXPECT(str.contains("size: 1876,1036"), true); + EXPECT(str.contains("fullscreen: 0"), true); + } + + OK(getFromSocket("/dispatch fullscreen 1")); + + { + auto str = getFromSocket("/clients"); + EXPECT(str.contains("at: 22,22"), true); + EXPECT(str.contains("size: 1876,1036"), true); + EXPECT(str.contains("fullscreen: 1"), true); + } + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + NLog::log("{}Expecting 0 windows", Colors::YELLOW); + 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); + + Tests::killAllWindows(); + OK(getFromSocket("/keyword input:follow_mouse 2")); + OK(getFromSocket("/keyword input:float_switch_override_focus 0")); + + EXPECT(spawnKitty("a"), true); + OK(getFromSocket("/dispatch setfloating")); + OK(getFromSocket("/dispatch movewindowpixel exact 500 300,activewindow")); + OK(getFromSocket("/dispatch resizewindowpixel exact 400 400,activewindow")); + + EXPECT(spawnKitty("b"), true); + OK(getFromSocket("/dispatch setfloating")); + OK(getFromSocket("/dispatch movewindowpixel exact 500 300,activewindow")); + OK(getFromSocket("/dispatch resizewindowpixel exact 400 400,activewindow")); + + auto getTopWindow = []() -> std::string { + auto clients = getFromSocket("/clients"); + return (clients.rfind("class: a") > clients.rfind("class: b")) ? "a" : "b"; + }; + + EXPECT(getTopWindow(), std::string("b")); + OK(getFromSocket("/dispatch movecursor 700 500")); + + OK(getFromSocket("/dispatch focuswindow class:a")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: a"); + + OK(getFromSocket("/dispatch bringactivetotop")); + EXPECT(getTopWindow(), std::string("a")); + + OK(getFromSocket("/dispatch plugin:test:click 272,1")); + OK(getFromSocket("/dispatch plugin:test:click 272,0")); + + EXPECT(getTopWindow(), std::string("a")); + + 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 testContentRules() { + NLog::log("{}Testing content window rules", Colors::YELLOW); + + // kill me PLEASE + + OK(getFromSocket("/keyword windowrule match:class kitty_content_string, content game")); + OK(getFromSocket("/keyword windowrule match:class kitty_content_numbers, content 3")); + OK(getFromSocket("/keyword windowrule match:content game, border_size 10")); + OK(getFromSocket("/keyword windowrule match:content 3, opacity 0.5")); + + const auto testProps = []() { + EXPECT_CONTAINS(getFromSocket("/getprop active border_size"), "10"); + EXPECT_CONTAINS(getFromSocket("/getprop active opacity"), "0.5"); + }; + if (!spawnKitty("kitty_content_string")) + return false; + waitForActiveWindow("kitty_content_string"); + testProps(); + + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); + + if (!spawnKitty("kitty_content_numbers")) + return false; + waitForActiveWindow("kitty_content_numbers"); + testProps(); + + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); + return true; +} + +static bool test14038() { + NLog::log("{}Testing #14038 crash", Colors::YELLOW); + + if (!spawnKitty("kitty_14038")) + return false; + + OK(getFromSocket("/dispatch movetoworkspacesilent special:a,class:kitty_14038")); + OK(getFromSocket("/dispatch togglefloating class:kitty_14038")); + OK(getFromSocket("/dispatch pin class:kitty_14038")); + OK(getFromSocket("/dispatch togglefloating class:kitty_14038")); + + // this should not crash hyprland. If we are alive, we good. + + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); + return true; +} + static bool test() { NLog::log("{}Testing windows", Colors::GREEN); @@ -364,22 +718,40 @@ static bool test() { NLog::log("{}Testing window split ratios", Colors::YELLOW); { - const double RATIO = 1.25; - const double PERCENT = RATIO / 2.0 * 100.0; - const int GAPSIN = 5; - const int GAPSOUT = 20; - const int BORDERS = 2 * 2; - const int WTRIM = BORDERS + GAPSIN + GAPSOUT; - const int HEIGHT = 1080 - (BORDERS + (GAPSOUT * 2)); - const int WIDTH1 = std::round(1920.0 / 2.0 * (2 - RATIO)) - WTRIM; - const int WIDTH2 = std::round(1920.0 / 2.0 * RATIO) - WTRIM; + const double INITIAL_RATIO = 1.25; + const int GAPSIN = 5; + const int GAPSOUT = 20; + const int BORDERSIZE = 2; + const int BORDERS = BORDERSIZE * 2; + const int MONITOR_W = 1920; + const int MONITOR_H = 1080; + + const float totalAvailableHeight = MONITOR_H - (GAPSOUT * 2); + const int HEIGHT = std::floor(totalAvailableHeight) - BORDERS; + const float availableWidthForSplit = MONITOR_W - (GAPSOUT * 2) - GAPSIN; + + auto calculateFinalWidth = [&](double boxWidth, bool isLeftWindow) { + double gapLeft = isLeftWindow ? GAPSOUT : GAPSIN; + double gapRight = isLeftWindow ? GAPSIN : GAPSOUT; + return std::floor(boxWidth - gapLeft - gapRight - BORDERS); + }; + + double geomBoxWidthA_R1 = (availableWidthForSplit * INITIAL_RATIO / 2.0) + GAPSOUT + (GAPSIN / 2.0); + double geomBoxWidthB_R1 = MONITOR_W - geomBoxWidthA_R1; + const int WIDTH1 = calculateFinalWidth(geomBoxWidthB_R1, false); + + const double INVERTED_RATIO = 0.75; + double geomBoxWidthA_R2 = (availableWidthForSplit * INVERTED_RATIO / 2.0) + GAPSOUT + (GAPSIN / 2.0); + double geomBoxWidthB_R2 = MONITOR_W - geomBoxWidthA_R2; + const int WIDTH2 = calculateFinalWidth(geomBoxWidthB_R2, false); + const int WIDTH_A_FINAL = calculateFinalWidth(geomBoxWidthA_R2, true); OK(getFromSocket("/keyword dwindle:default_split_ratio 1.25")); if (!spawnKitty("kitty_B")) return false; - NLog::log("{}Expecting kitty_B to take up roughly {}% of screen width", Colors::YELLOW, 100 - PERCENT); + NLog::log("{}Expecting kitty_B size: {},{}", Colors::YELLOW, WIDTH1, HEIGHT); EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH1, HEIGHT)); OK(getFromSocket("/dispatch killwindow activewindow")); @@ -391,12 +763,38 @@ static bool test() { if (!spawnKitty("kitty_B")) return false; - NLog::log("{}Expecting kitty_B to take up roughly {}% of screen width", Colors::YELLOW, PERCENT); - EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH2, HEIGHT)); + try { + NLog::log("{}Expecting kitty_B size: {},{}", Colors::YELLOW, WIDTH2, HEIGHT); - OK(getFromSocket("/dispatch focuswindow class:kitty_A")); - NLog::log("{}Expecting kitty_A to have the same width as the previous kitty_B", Colors::YELLOW); - EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH1, HEIGHT)); + { + auto data = getFromSocket("/activewindow"); + data = data.substr(data.find("size:") + 5); + data = data.substr(0, data.find('\n')); + + Hyprutils::String::CVarList2 sizes(std::move(data), 0, ','); + + EXPECT_MAX_DELTA(std::stoi(std::string{sizes[0]}), WIDTH2, 2); + EXPECT_MAX_DELTA(std::stoi(std::string{sizes[1]}), HEIGHT, 2); + } + + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + NLog::log("{}Expecting kitty_A size: {},{}", Colors::YELLOW, WIDTH_A_FINAL, HEIGHT); + + { + auto data = getFromSocket("/activewindow"); + data = data.substr(data.find("size:") + 5); + data = data.substr(0, data.find('\n')); + + Hyprutils::String::CVarList2 sizes(std::move(data), 0, ','); + + EXPECT_MAX_DELTA(std::stoi(std::string{sizes[0]}), WIDTH_A_FINAL, 2); + EXPECT_MAX_DELTA(std::stoi(std::string{sizes[1]}), HEIGHT, 2); + } + + } catch (...) { + NLog::log("{}Exception thrown", Colors::RED); + EXPECT(false, true); + } OK(getFromSocket("/keyword dwindle:default_split_ratio 1")); } @@ -412,14 +810,14 @@ static bool test() { EXPECT(Tests::windowCount(), 3); NLog::log("{}Checking props of xeyes", Colors::YELLOW); - // check some window props of xeyes, try to tile them + // check some window props of xeyes, try to float it { auto str = getFromSocket("/clients"); - EXPECT_CONTAINS(str, "floating: 1"); - getFromSocket("/dispatch settiled class:XEyes"); + EXPECT_NOT_CONTAINS(str, "floating: 1"); + getFromSocket("/dispatch setfloating class:XEyes"); std::this_thread::sleep_for(std::chrono::milliseconds(200)); str = getFromSocket("/clients"); - EXPECT_NOT_CONTAINS(str, "floating: 1"); + EXPECT_CONTAINS(str, "floating: 1"); } // kill all @@ -477,10 +875,10 @@ static bool test() { EXPECT_CONTAINS(dwindle, "size: 1500,500"); EXPECT_CONTAINS(dwindle, "at: 210,290"); - if (!spawnKitty("kitty_maxsize")) - return false; - - EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 1200,500"); + // Fuck this test, it's fucking stupid - vax + // if (!spawnKitty("kitty_maxsize")) + // return false; + // EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 1200,500"); Tests::killAllWindows(); EXPECT(Tests::windowCount(), 0); @@ -497,8 +895,69 @@ static bool test() { if (!spawnKitty("kitty_maxsize")) return false; + // FIXME: I can't be arsed. OK(getFromSocket("/dispatch focuswindow class:kitty_maxsize")); - EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 1200,500") + // EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 1200,500") + + NLog::log("{}Reloading config", Colors::YELLOW); + OK(getFromSocket("/reload")); + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); + } + + NLog::log("{}Testing minsize/maxsize rules", Colors::YELLOW); + { + // Disable size limits tiled and check if props are working and not getting skipped + OK(getFromSocket("/keyword misc:size_limits_tiled 0")); + OK(getFromSocket("/keyword windowrule[kitty-max-rule]:match:class kitty_maxsize")); + OK(getFromSocket("/keyword windowrule[kitty-max-rule]:max_size 1500 500")); + OK(getFromSocket("r/keyword windowrule[kitty-max-rule]:min_size 1200 500")); + if (!spawnKitty("kitty_maxsize")) + return false; + + { + auto res = getFromSocket("/getprop active max_size"); + EXPECT_CONTAINS(res, "1500"); + EXPECT_CONTAINS(res, "500"); + } + + { + auto res = getFromSocket("/getprop active min_size"); + EXPECT_CONTAINS(res, "1200"); + EXPECT_CONTAINS(res, "500"); + } + + NLog::log("{}Reloading config", Colors::YELLOW); + OK(getFromSocket("/reload")); + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); + } + + { + // Set float + OK(getFromSocket("/keyword windowrule[kitty-max-rule]:match:class kitty_maxsize")); + OK(getFromSocket("/keyword windowrule[kitty-max-rule]:max_size 1200 500")); + OK(getFromSocket("r/keyword windowrule[kitty-max-rule]:min_size 1200 500")); + OK(getFromSocket("r/keyword windowrule[kitty-max-rule]:float yes")); + if (!spawnKitty("kitty_maxsize")) + return false; + + { + auto res = getFromSocket("/getprop active max_size"); + EXPECT_CONTAINS(res, "1200"); + EXPECT_CONTAINS(res, "500"); + } + + { + auto res = getFromSocket("/getprop active min_size"); + EXPECT_CONTAINS(res, "1200"); + EXPECT_CONTAINS(res, "500"); + } + + { + auto res = getFromSocket("/activewindow"); + EXPECT_CONTAINS(res, "size: 1200,500"); + } NLog::log("{}Reloading config", Colors::YELLOW); OK(getFromSocket("/reload")); @@ -542,6 +1001,23 @@ static bool test() { Tests::killAllWindows(); + OK(getFromSocket("/keyword windowrule[border-magic-kitty]:match:class border_kitty")); + OK(getFromSocket("/keyword windowrule[border-magic-kitty]:border_color rgba(c6ff00ff) rgba(ff0000ee) 45deg")); + + if (!spawnKitty("border_kitty")) + return false; + + OK(getFromSocket("/dispatch focuswindow class:border_kitty")); + + { + auto str = getFromSocket("/getprop active active_border_color"); + EXPECT_CONTAINS(str, "ffc6ff00"); + EXPECT_CONTAINS(str, "eeff0000"); + EXPECT_CONTAINS(str, "45deg"); + } + + Tests::killAllWindows(); + if (!spawnKitty("tag_kitty")) return false; @@ -625,7 +1101,8 @@ static bool test() { Tests::killAllWindows(); // test expression rules - OK(getFromSocket("/keyword windowrule match:class expr_kitty, float yes, size monitor_w*0.5 monitor_h*0.5, move 20+(monitor_w*0.1) monitor_h*0.5")); + OK(getFromSocket("/keyword windowrule match:class expr_kitty, float yes, size monitor_w*0.5 monitor_h*0.5, min_size monitor_w*0.25 monitor_h*0.25, " + "max_size monitor_w*0.75 monitor_h*0.75, move 20+(monitor_w*0.1) monitor_h*0.5")); if (!spawnKitty("expr_kitty")) return false; @@ -635,12 +1112,20 @@ static bool test() { EXPECT_CONTAINS(str, "floating: 1"); EXPECT_CONTAINS(str, "at: 212,540"); EXPECT_CONTAINS(str, "size: 960,540"); + + auto min = getFromSocket("/getprop active min_size"); + EXPECT_CONTAINS(min, "480"); + EXPECT_CONTAINS(min, "270"); + + auto max = getFromSocket("/getprop active max_size"); + EXPECT_CONTAINS(max, "1440"); + EXPECT_CONTAINS(max, "810"); } OK(getFromSocket("/reload")); Tests::killAllWindows(); - OK(getFromSocket("/dispatch plugin:test:add_rule")); + OK(getFromSocket("/dispatch plugin:test:add_window_rule")); OK(getFromSocket("/reload")); OK(getFromSocket("/keyword windowrule match:class plugin_kitty, plugin_rule effect")); @@ -648,12 +1133,12 @@ static bool test() { if (!spawnKitty("plugin_kitty")) return false; - OK(getFromSocket("/dispatch plugin:test:check_rule")); + OK(getFromSocket("/dispatch plugin:test:check_window_rule")); OK(getFromSocket("/reload")); Tests::killAllWindows(); - OK(getFromSocket("/dispatch plugin:test:add_rule")); + OK(getFromSocket("/dispatch plugin:test:add_window_rule")); OK(getFromSocket("/reload")); OK(getFromSocket("/keyword windowrule[test-plugin-rule]:match:class plugin_kitty")); @@ -662,12 +1147,22 @@ static bool test() { if (!spawnKitty("plugin_kitty")) return false; - OK(getFromSocket("/dispatch plugin:test:check_rule")); + OK(getFromSocket("/dispatch plugin:test:check_window_rule")); OK(getFromSocket("/reload")); Tests::killAllWindows(); testGroupRules(); + testMaximizeSize(); + testFloatingFocusOnFullscreen(); + testBringActiveToTopMouseMovement(); + testGroupFallbackFocus(); + testInitialFloatSize(); + testWindowRuleFocusOnActivate(); + testPinnedWorkspacesValid(); + testWindowRuleWorkspaceEmpty(); + testContentRules(); + test14038(); 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 622236dcf..f7c948f26 100644 --- a/hyprtester/src/tests/main/workspaces.cpp +++ b/hyprtester/src/tests/main/workspaces.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include "../shared.hpp" @@ -14,10 +15,413 @@ static int ret = 0; using namespace Hyprutils::OS; using namespace Hyprutils::Memory; +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); + { + + CScopeGuard guard = {[&]() { + NLog::log("{}Cleaning up asymmetric gap test", Colors::YELLOW); + Tests::killAllWindows(); + OK(getFromSocket("/reload")); + }}; + + OK(getFromSocket("/dispatch workspace name:gap_split_test")); + OK(getFromSocket("r/keyword general:gaps_in 0")); + OK(getFromSocket("r/keyword general:border_size 0")); + OK(getFromSocket("r/keyword dwindle:split_width_multiplier 1.0")); + OK(getFromSocket("r/keyword workspace name:gap_split_test,gapsout:0 1000 0 0")); + + NLog::log("{}Testing default split (force_split = 0)", Colors::YELLOW); + OK(getFromSocket("r/keyword dwindle:force_split 0")); + + if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B")) + return false; + + NLog::log("{}Expecting vertical split (B below A)", Colors::YELLOW); + OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0"); + OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540"); + + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); + + NLog::log("{}Testing force_split = 1", Colors::YELLOW); + OK(getFromSocket("r/keyword dwindle:force_split 1")); + + if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B")) + return false; + + NLog::log("{}Expecting vertical split (B above A)", Colors::YELLOW); + OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0"); + OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540"); + + NLog::log("{}Expecting horizontal split (C left of B)", Colors::YELLOW); + OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B")); + + if (!Tests::spawnKitty("gaps_kitty_C")) + return false; + + OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_C")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0"); + OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 460,0"); + + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); + + NLog::log("{}Testing force_split = 2", Colors::YELLOW); + OK(getFromSocket("r/keyword dwindle:force_split 2")); + + if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B")) + return false; + + NLog::log("{}Expecting vertical split (B below A)", Colors::YELLOW); + OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0"); + OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540"); + + NLog::log("{}Expecting horizontal split (C right of A)", Colors::YELLOW); + OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A")); + + if (!Tests::spawnKitty("gaps_kitty_C")) + return false; + + OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0"); + OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_C")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 460,0"); + } + + // kill all + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + return true; +} + +static void testWorkspaceHistoryMultiMon() { + NLog::log("{}Testing multimon workspace history tracker", Colors::YELLOW); + + // Initial state: + OK(getFromSocket("/dispatch focusmonitor HEADLESS-2")); + OK(getFromSocket("/dispatch workspace 10")); + Tests::spawnKitty(); + OK(getFromSocket("/dispatch workspace 11")); + Tests::spawnKitty(); + + OK(getFromSocket("/dispatch focusmonitor HEADLESS-3")); + OK(getFromSocket("/dispatch workspace 12")); + Tests::spawnKitty(); + + OK(getFromSocket("/dispatch focusmonitor HEADLESS-2")); + { + auto str = getFromSocket("/activeworkspace"); + EXPECT_CONTAINS(str, "workspace ID 11"); + } + OK(getFromSocket("/dispatch workspace previous_per_monitor")); + { + auto str = getFromSocket("/activeworkspace"); + EXPECT_CONTAINS(str, "workspace ID 10"); + } + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + +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); @@ -352,13 +756,21 @@ 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(); + testWorkspaceHistoryMultiMon(); + + // 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 8cdd648e8..32824f3b6 100644 --- a/hyprtester/src/tests/shared.cpp +++ b/hyprtester/src/tests/shared.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "../shared.hpp" #include "../hyprctlCompat.hpp" @@ -39,6 +40,38 @@ CUniquePointer Tests::spawnKitty(const std::string& class_, const std: return kitty; } +CUniquePointer Tests::spawnLayerKitty(const std::string& namespace_, const std::vector args) { + std::vector programArgs = args; + if (!namespace_.empty()) { + programArgs.insert(programArgs.begin(), "--class"); + programArgs.insert(programArgs.begin() + 1, namespace_); + } + + programArgs.insert(programArgs.begin(), "+kitten"); + programArgs.insert(programArgs.begin() + 1, "panel"); + + CUniquePointer kitty = makeUnique("kitty", programArgs); + kitty->addEnv("WAYLAND_DISPLAY", WLDISPLAY); + kitty->runAsync(); + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + // wait while the layer spawns + int counter = 0; + while (processAlive(kitty->pid()) && countOccurrences(getFromSocket("/layers"), std::format("pid: {}", kitty->pid())) == 0) { + counter++; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + if (counter > 50) + return nullptr; + } + + if (!processAlive(kitty->pid())) + return nullptr; + + return kitty; +} + bool Tests::processAlive(pid_t pid) { errno = 0; int ret = kill(pid, 0); @@ -96,6 +129,38 @@ void Tests::waitUntilWindowsN(int n) { } } +int Tests::layerCount() { + return countOccurrences(getFromSocket("/layers"), "namespace: "); +} + +bool Tests::killAllLayers() { + auto str = getFromSocket("/layers"); + auto pos = str.find("pid: "); + while (pos != std::string::npos) { + auto pid = stoi(str.substr(pos + 5, str.find('\n', pos))); + kill(pid, 15); + + // we need to wait for a bit because for some reason otherwise we'll end up + // with layers with pid -1 if they are all removed at the same time + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + pos = str.find("pid: ", pos + 5); + } + + int counter = 0; + while (Tests::layerCount() != 0) { + counter++; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + if (counter > 50) { + std::println("{}Timed out waiting for layers to close", Colors::RED); + return false; + } + } + + return true; +} + std::string Tests::execAndGet(const std::string& cmd) { CProcess proc("/bin/sh", {"-c", cmd}); @@ -105,3 +170,24 @@ 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; +} + +std::string Tests::getWindowAttribute(const std::string& winInfo, const std::string& attr) { + auto pos = winInfo.find(attr); + if (pos == std::string::npos) { + NLog::log("{}Window attribute not found: '{}'", Colors::RED, attr); + return "Wrong window attribute"; + } + auto pos2 = winInfo.find('\n', pos); + return winInfo.substr(pos, pos2 - pos); +} diff --git a/hyprtester/src/tests/shared.hpp b/hyprtester/src/tests/shared.hpp index fe28a69d1..95058e040 100644 --- a/hyprtester/src/tests/shared.hpp +++ b/hyprtester/src/tests/shared.hpp @@ -9,10 +9,15 @@ //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); + std::string getWindowAttribute(const std::string& winInfo, const std::string& attr); }; diff --git a/hyprtester/test.conf b/hyprtester/test.conf index ac28bc5a6..ab4f8ee36 100644 --- a/hyprtester/test.conf +++ b/hyprtester/test.conf @@ -179,6 +179,17 @@ master { new_status = master } +scrolling { + fullscreen_on_one_column = true + column_width = 0.5 + focus_fit_method = 1 + follow_focus = true + follow_min_visible = 1 + explicit_column_widths = 0.25, 0.333, 0.5, 0.667, 0.75, 1.0 + wrap_focus = true + wrap_swapcol = true +} + # https://wiki.hyprland.org/Configuring/Variables/#misc misc { force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers @@ -239,7 +250,7 @@ bind = $mainMod, E, exec, $fileManager bind = $mainMod, V, togglefloating, bind = $mainMod, R, exec, $menu bind = $mainMod, P, pseudo, # dwindle -bind = $mainMod, J, togglesplit, # dwindle +bind = $mainMod, J, layoutmsg, togglesplit, # dwindle # Move focus with mainMod + arrow keys bind = $mainMod, left, movefocus, l @@ -398,3 +409,5 @@ gesture = 5, left, dispatcher, sendshortcut, , i, activewindow gesture = 5, right, dispatcher, sendshortcut, , t, activewindow gesture = 4, right, dispatcher, sendshortcut, , return, activewindow gesture = 4, left, dispatcher, movecursortocorner, 1 + +gesturep = 2, right, float diff --git a/nix/default.nix b/nix/default.nix index 45fd273b8..51bb58fef 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -11,7 +11,9 @@ cairo, epoll-shim, git, - glaze, + glaze-hyprland, + glslang, + gtest, hyprcursor, hyprgraphics, hyprland-protocols, @@ -19,10 +21,18 @@ hyprlang, hyprutils, hyprwayland-scanner, + hyprwire, + lcms2, libGL, libdrm, libexecinfo, libinput, + libxcb, + libxcb-errors, + libxcb-render-util, + libxcb-wm, + libxdmcp, + libxcursor, libxkbcommon, libuuid, libgbm, @@ -36,9 +46,9 @@ wayland, wayland-protocols, wayland-scanner, - xorg, xwayland, debug ? false, + withTests ? debug, enableXWayland ? true, withSystemd ? lib.meta.availableOn stdenv.hostPlatform systemd, wrapRuntimeDeps ? true, @@ -52,12 +62,23 @@ hidpiXWayland ? false, legacyRenderer ? false, withHyprtester ? false, -}: let +}: +let 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 [ @@ -67,162 +88,184 @@ customStdenv = foldl' (acc: adapter: adapter acc) stdenv adapters; in - assert assertMsg (!nvidiaPatches) "The option `nvidiaPatches` has been removed."; - assert assertMsg (!enableNvidiaPatches) "The option `enableNvidiaPatches` has been removed."; - assert assertMsg (!hidpiXWayland) "The option `hidpiXWayland` has been removed. Please refer https://wiki.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: { - pname = "hyprland${optionalString debug "-debug"}"; - inherit version; +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 ( + !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: { + pname = "hyprland${optionalString debug "-debug"}"; + inherit version withTests; - src = fs.toSource { - root = ../.; - fileset = - fs.intersection - # allows non-flake builds to only include files tracked by git - (fs.gitTracked ../.) - (fs.unions (flatten [ + src = fs.toSource { + root = ../.; + fileset = + 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 - ../hyprtester + ../hyprpm ../LICENSE ../protocols ../src + ../start ../systemd ../VERSION (fs.fileFilter (file: file.hasExt "1") ../docs) - (fs.fileFilter (file: file.hasExt "conf" || file.hasExt "desktop") ../example) + (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 = '' - # Fix hardcoded paths to /usr installation - sed -i "s#/usr#$out#" src/render/OpenGL.cpp + postPatch = '' + # Fix hardcoded paths to /usr installation + sed -i "s#/usr#$out#" src/render/OpenGL.cpp - # Remove extra @PREFIX@ to fix pkg-config paths - sed -i "s#@PREFIX@/##g" hyprland.pc.in - ''; + # Remove extra @PREFIX@ to fix some paths + sed -i "s#@PREFIX@/##g" hyprland.pc.in + sed -i "s#@PREFIX@/##g" example/hyprland.desktop.in + ''; - env = { - GIT_COMMITS = revCount; - GIT_COMMIT_DATE = date; - GIT_COMMIT_HASH = commit; - GIT_DIRTY = if (commit == "") then "clean" else "dirty"; - GIT_TAG = "v${trim (readFile "${finalAttrs.src}/VERSION")}"; - }; + env = { + GIT_COMMITS = revCount; + GIT_COMMIT_DATE = date; + GIT_COMMIT_HASH = commit; + GIT_DIRTY = if (commit == "") then "clean" else "dirty"; + GIT_TAG = "v${trim (readFile "${finalAttrs.src}/VERSION")}"; + }; - depsBuildBuild = [ - pkg-config - ]; + depsBuildBuild = [ + pkg-config + ]; - nativeBuildInputs = [ - hyprwayland-scanner - makeWrapper - cmake - pkg-config - ]; + nativeBuildInputs = [ + hyprwayland-scanner + hyprwire + makeWrapper + cmake + pkg-config + ]; - outputs = [ - "out" - "man" - "dev" - ]; + outputs = [ + "out" + "man" + "dev" + ]; - buildInputs = concatLists [ - [ - aquamarine - cairo - git - glaze - hyprcursor - hyprgraphics - hyprland-protocols - hyprlang - hyprutils - libdrm - libGL - libinput - libuuid - libxkbcommon - libgbm - muparser - pango - pciutils - re2 - tomlplusplus - udis86-hyprland - wayland - wayland-protocols - wayland-scanner - xorg.libXcursor - ] - (optionals customStdenv.hostPlatform.isBSD [epoll-shim]) - (optionals customStdenv.hostPlatform.isMusl [libexecinfo]) - (optionals enableXWayland [ - xorg.libxcb - xorg.libXdmcp - xorg.xcbutilerrors - xorg.xcbutilrenderutil - xorg.xcbutilwm - xwayland - ]) - (optional withSystemd systemd) - ]; + buildInputs = concatLists [ + [ + aquamarine + cairo + git + glaze-hyprland + glslang + gtest + hyprcursor + hyprgraphics + hyprland-protocols + hyprlang + hyprutils + hyprwire + lcms2 + libdrm + libgbm + libGL + libinput + libuuid + libxcursor + libxkbcommon + muparser + pango + pciutils + re2 + tomlplusplus + udis86-hyprland + wayland + wayland-protocols + wayland-scanner + ] + (optionals customStdenv.hostPlatform.isBSD [ epoll-shim ]) + (optionals customStdenv.hostPlatform.isMusl [ libexecinfo ]) + (optionals enableXWayland [ + libxcb + libxcb-errors + libxcb-render-util + libxcb-wm + libxdmcp + xwayland + ]) + (optional withSystemd systemd) + ]; - strictDeps = true; + strictDeps = true; - cmakeBuildType = - if debug - then "Debug" - else "RelWithDebInfo"; + cmakeBuildType = if debug then "Debug" else "RelWithDebInfo"; - # we want as much debug info as possible - dontStrip = debug; + # we want as much debug info as possible + dontStrip = debug; - cmakeFlags = mapAttrsToList cmakeBool { - "NO_XWAYLAND" = !enableXWayland; - "LEGACY_RENDERER" = legacyRenderer; - "NO_SYSTEMD" = !withSystemd; - "CMAKE_DISABLE_PRECOMPILE_HEADERS" = true; - "NO_UWSM" = true; - "NO_HYPRPM" = true; - "TRACY_ENABLE" = false; - "BUILD_HYPRTESTER" = true; - }; + cmakeFlags = mapAttrsToList cmakeBool { + "BUILT_WITH_NIX" = true; + "NO_XWAYLAND" = !enableXWayland; + "LEGACY_RENDERER" = legacyRenderer; + "NO_SYSTEMD" = !withSystemd; + "CMAKE_DISABLE_PRECOMPILE_HEADERS" = true; + "NO_UWSM" = !withSystemd; + "TRACY_ENABLE" = false; + "WITH_TESTS" = withTests; + }; - preConfigure = '' - substituteInPlace hyprtester/CMakeLists.txt --replace-fail \ - "\''${CMAKE_CURRENT_BINARY_DIR}" \ - "${placeholder "out"}/bin" - ''; + preConfigure = '' + substituteInPlace hyprtester/CMakeLists.txt --replace-fail \ + "\''${CMAKE_CURRENT_BINARY_DIR}" \ + "${placeholder "out"}/bin" + ''; - postInstall = '' - ${optionalString wrapRuntimeDeps '' - wrapProgram $out/bin/Hyprland \ - --suffix PATH : ${makeBinPath [ + postInstall = '' + ${optionalString wrapRuntimeDeps '' + wrapProgram $out/bin/Hyprland \ + --suffix PATH : ${ + makeBinPath [ binutils hyprland-guiutils pciutils pkgconf - ]} - ''} + ] + } + ''} - install hyprtester/pointer-warp -t $out/bin - install hyprtester/pointer-scroll -t $out/bin - ''; + ${optionalString withTests '' + install hyprtester/pointer-warp -t $out/bin + install hyprtester/pointer-scroll -t $out/bin + install hyprtester/shortcut-inhibitor -t $out/bin + install hyprland_gtests -t $out/bin + install hyprtester/child-window -t $out/bin + ''} + ''; - passthru.providedSessions = ["hyprland"]; + passthru.providedSessions = [ "hyprland" ] ++ optionals withSystemd [ "hyprland-uwsm" ]; - meta = { - homepage = "https://github.com/hyprwm/Hyprland"; - description = "Dynamic tiling Wayland compositor that doesn't sacrifice on its looks"; - license = lib.licenses.bsd3; - platforms = lib.platforms.linux; - mainProgram = "Hyprland"; - }; - }) + meta = { + homepage = "https://github.com/hyprwm/Hyprland"; + description = "Dynamic tiling Wayland compositor that doesn't sacrifice on its looks"; + license = lib.licenses.bsd3; + platforms = lib.platforms.linux; + mainProgram = "Hyprland"; + }; +}) diff --git a/nix/formatter.nix b/nix/formatter.nix index 66721c2c8..ac340ae2c 100644 --- a/nix/formatter.nix +++ b/nix/formatter.nix @@ -2,7 +2,7 @@ writeShellApplication, deadnix, statix, - alejandra, + nixfmt, llvmPackages_19, fd, }: @@ -11,7 +11,7 @@ writeShellApplication { runtimeInputs = [ deadnix statix - alejandra + nixfmt llvmPackages_19.clang-tools fd ]; @@ -24,14 +24,14 @@ writeShellApplication { nix_format() { if [ "$*" = 0 ]; then fd '.*\.nix' . -E "$excludes" -x statix fix -- {} \; - fd '.*\.nix' . -E "$excludes" -X deadnix -e -- {} \; -X alejandra {} \; + fd '.*\.nix' . -E "$excludes" -X deadnix -e -- {} \; -X nixfmt {} \; elif [ -d "$1" ]; then fd '.*\.nix' "$1" -E "$excludes" -i -x statix fix -- {} \; - fd '.*\.nix' "$1" -E "$excludes" -i -X deadnix -e -- {} \; -X alejandra {} \; + fd '.*\.nix' "$1" -E "$excludes" -i -X deadnix -e -- {} \; -X nixfmt {} \; else statix fix -- "$1" deadnix -e "$1" - alejandra "$1" + nixfmt "$1" fi } diff --git a/nix/hm-module.nix b/nix/hm-module.nix index e3c788d05..948b8217f 100644 --- a/nix/hm-module.nix +++ b/nix/hm-module.nix @@ -1,13 +1,15 @@ -self: { - config, +self: +{ lib, pkgs, ... -}: let +}: +let inherit (pkgs.stdenv.hostPlatform) system; package = self.packages.${system}.default; -in { +in +{ config = { wayland.windowManager.hyprland.package = lib.mkDefault package; }; diff --git a/nix/lib.nix b/nix/lib.nix index ca3aadee0..54d234401 100644 --- a/nix/lib.nix +++ b/nix/lib.nix @@ -1,4 +1,5 @@ -lib: let +lib: +let inherit (lib) attrNames filterAttrs @@ -17,7 +18,7 @@ lib: let This function takes a nested attribute set and converts it into Hyprland-compatible configuration syntax, supporting top, bottom, and regular command sections. - + Commands are flattened using the `flattenAttrs` function, and attributes are formatted as `key = value` pairs. Lists are expanded as duplicate keys to match Hyprland's expected format. @@ -81,44 +82,51 @@ lib: let ::: */ - toHyprlang = { - topCommandsPrefixes ? ["$" "bezier"], - bottomCommandsPrefixes ? [], - }: attrs: let - toHyprlang' = attrs: let - # Specially configured `toKeyValue` generator with support for duplicate keys - # and a legible key-value separator. - mkCommands = generators.toKeyValue { - mkKeyValue = generators.mkKeyValueDefault {} " = "; - listsAsDuplicateKeys = true; - indent = ""; # No indent, since we don't have nesting - }; + toHyprlang = + { + topCommandsPrefixes ? [ + "$" + "bezier" + ], + bottomCommandsPrefixes ? [ ], + }: + attrs: + let + toHyprlang' = + attrs: + let + # Specially configured `toKeyValue` generator with support for duplicate keys + # and a legible key-value separator. + mkCommands = generators.toKeyValue { + mkKeyValue = generators.mkKeyValueDefault { } " = "; + listsAsDuplicateKeys = true; + indent = ""; # No indent, since we don't have nesting + }; - # Flatten the attrset, combining keys in a "path" like `"a:b:c" = "x"`. - # Uses `flattenAttrs` with a colon separator. - commands = flattenAttrs (p: k: "${p}:${k}") attrs; + # Flatten the attrset, combining keys in a "path" like `"a:b:c" = "x"`. + # Uses `flattenAttrs` with a colon separator. + commands = flattenAttrs (p: k: "${p}:${k}") attrs; - # General filtering function to check if a key starts with any prefix in a given list. - filterCommands = list: n: - foldl (acc: prefix: acc || hasPrefix prefix n) false list; + # General filtering function to check if a key starts with any prefix in a given list. + filterCommands = list: n: foldl (acc: prefix: acc || hasPrefix prefix n) false list; - # Partition keys into top commands and the rest - result = partition (filterCommands topCommandsPrefixes) (attrNames commands); - topCommands = filterAttrs (n: _: builtins.elem n result.right) commands; - remainingCommands = removeAttrs commands result.right; + # Partition keys into top commands and the rest + result = partition (filterCommands topCommandsPrefixes) (attrNames commands); + topCommands = filterAttrs (n: _: builtins.elem n result.right) commands; + remainingCommands = removeAttrs commands result.right; - # Partition remaining commands into bottom commands and regular commands - result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong; - bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands; - regularCommands = removeAttrs remainingCommands result2.right; + # Partition remaining commands into bottom commands and regular commands + result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong; + bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands; + regularCommands = removeAttrs remainingCommands result2.right; + in + # Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands. + concatMapStrings mkCommands [ + topCommands + regularCommands + bottomCommands + ]; in - # Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands. - concatMapStrings mkCommands [ - topCommands - regularCommands - bottomCommands - ]; - in toHyprlang' attrs; /** @@ -131,7 +139,7 @@ lib: let Configuration: * `pred` - A function `(string -> string -> string)` defining how keys should be concatenated. - + # Inputs Structured function argument: @@ -139,7 +147,7 @@ lib: let : pred (required) : A function that determines how parent and child keys should be combined into a single key. It takes a `prefix` (parent key) and `key` (current key) and returns the joined key. - + Value: : The nested attribute set to be flattened. @@ -174,26 +182,21 @@ lib: let ``` ::: - */ - flattenAttrs = pred: attrs: let - flattenAttrs' = prefix: attrs: - builtins.foldl' ( - acc: key: let - value = attrs.${key}; - newKey = - if prefix == "" - then key - else pred prefix key; - in - acc - // ( - if builtins.isAttrs value - then flattenAttrs' newKey value - else {"${newKey}" = value;} - ) - ) {} (builtins.attrNames attrs); - in + flattenAttrs = + pred: attrs: + let + flattenAttrs' = + prefix: attrs: + builtins.foldl' ( + acc: key: + let + value = attrs.${key}; + newKey = if prefix == "" then key else pred prefix key; + in + acc // (if builtins.isAttrs value then flattenAttrs' newKey value else { "${newKey}" = value; }) + ) { } (builtins.attrNames attrs); + in flattenAttrs' "" attrs; in { diff --git a/nix/module.nix b/nix/module.nix index 91705347d..322639435 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -1,18 +1,21 @@ -inputs: { +inputs: +{ config, lib, pkgs, ... -}: let +}: +let inherit (pkgs.stdenv.hostPlatform) system; selflib = import ./lib.nix lib; cfg = config.programs.hyprland; -in { +in +{ options = { programs.hyprland = { plugins = lib.mkOption { type = with lib.types; listOf (either package path); - default = []; + default = [ ]; description = '' List of Hyprland plugins to use. Can either be packages or absolute plugin paths. @@ -20,23 +23,25 @@ in { }; settings = lib.mkOption { - type = with lib.types; let - valueType = - nullOr (oneOf [ - bool - int - float - str - path - (attrsOf valueType) - (listOf valueType) - ]) - // { - description = "Hyprland configuration value"; - }; - in + type = + with lib.types; + let + valueType = + nullOr (oneOf [ + bool + int + float + str + path + (attrsOf valueType) + (listOf valueType) + ]) + // { + description = "Hyprland configuration value"; + }; + in valueType; - default = {}; + default = { }; description = '' Hyprland configuration written in Nix. Entries with the same key should be written as lists. Variables' and colors' names should be @@ -92,8 +97,15 @@ in { topPrefixes = lib.mkOption { type = with lib.types; listOf str; - default = ["$" "bezier"]; - example = ["$" "bezier" "source"]; + default = [ + "$" + "bezier" + ]; + example = [ + "$" + "bezier" + "source" + ]; description = '' List of prefix of attributes to put at the top of the config. ''; @@ -101,8 +113,8 @@ in { bottomPrefixes = lib.mkOption { type = with lib.types; listOf str; - default = []; - example = ["source"]; + default = [ ]; + example = [ "source" ]; description = '' List of prefix of attributes to put at the bottom of the config. ''; @@ -117,35 +129,36 @@ in { }; } (lib.mkIf cfg.enable { - environment.etc."xdg/hypr/hyprland.conf" = let - shouldGenerate = cfg.extraConfig != "" || cfg.settings != {} || cfg.plugins != []; + environment.etc."xdg/hypr/hyprland.conf" = + let + shouldGenerate = cfg.extraConfig != "" || cfg.settings != { } || cfg.plugins != [ ]; - pluginsToHyprlang = plugins: - selflib.toHyprlang { - topCommandsPrefixes = cfg.topPrefixes; - bottomCommandsPrefixes = cfg.bottomPrefixes; - } - { - "exec-once" = let - mkEntry = entry: - if lib.types.package.check entry - then "${entry}/lib/lib${entry.pname}.so" - else entry; - hyprctl = lib.getExe' config.programs.hyprland.package "hyprctl"; - in - map (p: "${hyprctl} plugin load ${mkEntry p}") cfg.plugins; - }; - in - lib.mkIf shouldGenerate { - text = - lib.optionalString (cfg.plugins != []) - (pluginsToHyprlang cfg.plugins) - + lib.optionalString (cfg.settings != {}) - (selflib.toHyprlang { + pluginsToHyprlang = + _plugins: + selflib.toHyprlang + { topCommandsPrefixes = cfg.topPrefixes; bottomCommandsPrefixes = cfg.bottomPrefixes; } - cfg.settings) + { + "exec-once" = + let + mkEntry = + entry: if lib.types.package.check entry then "${entry}/lib/lib${entry.pname}.so" else entry; + hyprctl = lib.getExe' config.programs.hyprland.package "hyprctl"; + in + map (p: "${hyprctl} plugin load ${mkEntry p}") cfg.plugins; + }; + in + lib.mkIf shouldGenerate { + text = + lib.optionalString (cfg.plugins != [ ]) (pluginsToHyprlang cfg.plugins) + + lib.optionalString (cfg.settings != { }) ( + selflib.toHyprlang { + topCommandsPrefixes = cfg.topPrefixes; + bottomCommandsPrefixes = cfg.bottomPrefixes; + } cfg.settings + ) + lib.optionalString (cfg.extraConfig != "") cfg.extraConfig; }; }) diff --git a/nix/overlays.nix b/nix/overlays.nix index c7ef95b86..699090510 100644 --- a/nix/overlays.nix +++ b/nix/overlays.nix @@ -2,22 +2,29 @@ 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 + hyprland-extras + ] + ); - # Packages for variations of Hyprland, dependencies included. + # Packages for variations of Hyprland, all dependencies included. hyprland-packages = lib.composeManyExtensions [ # Dependencies inputs.aquamarine.overlays.default @@ -28,61 +35,68 @@ in { inputs.hyprlang.overlays.default inputs.hyprutils.overlays.default inputs.hyprwayland-scanner.overlays.default - self.overlays.udis86 - + inputs.hyprwire.overlays.default # Hyprland packages themselves - (final: _prev: let + self.overlays.hyprland + ]; + + # Hyprland with its internal dependencies. + hyprland = lib.composeManyExtensions (with self.overlays; [ + udis86 + glaze + hyprland-no-deps + ]); + + # Hyprland without any dependencies. + hyprland-no-deps = + final: _prev: + let date = mkDate (self.lastModifiedDate or "19700101"); version = "${ver}+date=${date}_${self.shortRev or "dirty"}"; - in { + 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-hyprtester = - builtins.trace '' - hyprland-with-hyprtester was removed. Please use the hyprland package. - Hyprtester is always built now. - '' - final.hyprland; + hyprland-unwrapped = final.hyprland.override { wrapRuntimeDeps = false; }; + + hyprland-with-tests = final.hyprland.override { withTests = true; }; + + hyprland-with-hyprtester = builtins.trace '' + hyprland-with-hyprtester was removed. Please use the hyprland package. + Hyprtester is always built now. + '' final.hyprland; # 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; + hyprland-legacy-renderer = builtins.trace '' + hyprland-legacy-renderer was removed. Please use the hyprland package. + Legacy renderer is no longer supported. + '' final.hyprland; - hyprland-nvidia = - builtins.trace '' - hyprland-nvidia was removed. Please use the hyprland package. - Nvidia patches are no longer needed. - '' - final.hyprland; + hyprland-nvidia = builtins.trace '' + hyprland-nvidia was removed. Please use the hyprland package. + Nvidia patches are no longer needed. + '' final.hyprland; - 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; - }) - ]; + 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; + }; # Debug hyprland-debug = lib.composeManyExtensions [ # 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; }; }) ]; @@ -96,15 +110,26 @@ 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-hyprland = prev.glaze.override { + enableSSL = false; + enableInterop = false; + }; }; } diff --git a/nix/tests/default.nix b/nix/tests/default.nix index ef92a4635..c17379925 100644 --- a/nix/tests/default.nix +++ b/nix/tests/default.nix @@ -1,89 +1,107 @@ -inputs: pkgs: let +inputs: pkgs: +let flake = inputs.self.packages.${pkgs.stdenv.hostPlatform.system}; - hyprland = flake.hyprland; -in { + hyprland = flake.hyprland-with-tests; +in +{ tests = pkgs.testers.runNixOSTest { name = "hyprland-tests"; - nodes.machine = {pkgs, ...}: { - environment.systemPackages = with pkgs; [ - # Programs needed for tests - jq - kitty - wl-clipboard - xorg.xeyes - ]; + nodes.machine = + { pkgs, ... }: + { + environment.systemPackages = with pkgs; [ + # Programs needed for tests + jq + kitty + wl-clipboard + xeyes + ]; - # Enabled by default for some reason - services.speechd.enable = false; + # Enabled by default for some reason + services.speechd.enable = false; - environment.variables = { - "AQ_TRACE" = "1"; - "HYPRLAND_TRACE" = "1"; - "XDG_RUNTIME_DIR" = "/tmp"; - "XDG_CACHE_HOME" = "/tmp"; - "KITTY_CONFIG_DIRECTORY" = "/etc/kitty"; - }; - - environment.etc."kitty/kitty.conf".text = '' - confirm_os_window_close 0 - ''; - - programs.hyprland = { - enable = true; - package = hyprland; - # We don't need portals in this test, so we don't set portalPackage - }; - - # Test configuration - environment.etc."test.conf".source = "${hyprland}/share/hypr/test.conf"; - - # Disable portals - xdg.portal.enable = pkgs.lib.mkForce false; - - # Autologin root into tty - services.getty.autologinUser = "alice"; - - system.stateVersion = "24.11"; - - users.users.alice = { - isNormalUser = true; - }; - - virtualisation = { - cores = 4; - # Might crash with less - memorySize = 8192; - resolution = { - x = 1920; - y = 1080; + environment.variables = { + "AQ_TRACE" = "1"; + "HYPRLAND_TRACE" = "1"; + "XDG_RUNTIME_DIR" = "/tmp"; + "XDG_CACHE_HOME" = "/tmp"; + "KITTY_CONFIG_DIRECTORY" = "/etc/kitty"; }; - # Doesn't seem to do much, thought it would fix XWayland crashing - qemu.options = ["-vga none -device virtio-gpu-pci"]; + environment.etc."kitty/kitty.conf".text = '' + confirm_os_window_close 0 + remember_window_size no + initial_window_width 640 + initial_window_height 400 + ''; + + programs.hyprland = { + enable = true; + package = hyprland; + # We don't need portals in this test, so we don't set portalPackage + }; + + # Test configuration + environment.etc."test.conf".source = "${hyprland}/share/hypr/test.conf"; + + # Disable portals + xdg.portal.enable = pkgs.lib.mkForce false; + + # Autologin root into tty + services.getty.autologinUser = "alice"; + + system.stateVersion = "24.11"; + + users.users.alice = { + isNormalUser = true; + }; + + virtualisation = { + cores = 4; + # Might crash with less + memorySize = 8192; + resolution = { + x = 1920; + y = 1080; + }; + + # Doesn't seem to do much, thought it would fix XWayland crashing + qemu.options = [ "-vga none -device virtio-gpu-pci" ]; + }; }; - }; testScript = '' # Wait for tty to be up machine.wait_for_unit("multi-user.target") + + # Run gtests + print("Running gtests") + exit_status, _out = machine.execute("su - alice -c 'hyprland_gtests 2>&1 | tee /tmp/gtestslog; exit ''${PIPESTATUS[0]}'") + machine.execute(f'echo {exit_status} > /tmp/exit_status_gtests') + + # Print logs for visibility in CI + _, out = machine.execute("cat /tmp/gtestslog") + print(f"gtests log:\n{out}") + # Run hyprtester testing framework/suite print("Running hyprtester") exit_status, _out = machine.execute("su - alice -c 'hyprtester -b ${hyprland}/bin/Hyprland -c /etc/test.conf -p ${hyprland}/lib/hyprtestplugin.so 2>&1 | tee /tmp/testerlog; exit ''${PIPESTATUS[0]}'") print(f"Hyprtester exited with {exit_status}") - # Copy logs to host - machine.execute('cp "$(find /tmp/hypr -name *.log | head -1)" /tmp/hyprlog') - machine.execute(f'echo {exit_status} > /tmp/exit_status') - machine.copy_from_vm("/tmp/testerlog") - machine.copy_from_vm("/tmp/hyprlog") - machine.copy_from_vm("/tmp/exit_status") - # Print logs for visibility in CI _, out = machine.execute("cat /tmp/testerlog") print(f"Hyprtester log:\n{out}") + # Copy logs to host + machine.execute('cp "$(find /tmp/hypr -name *.log | head -1)" /tmp/hyprlog') + machine.execute(f'echo {exit_status} > /tmp/exit_status') + machine.copy_from_vm("/tmp/gtestslog") + machine.copy_from_vm("/tmp/testerlog") + machine.copy_from_vm("/tmp/hyprlog") + machine.copy_from_vm("/tmp/exit_status") + # Finally - shutdown machine.shutdown() ''; diff --git a/protocols/frog-color-management-v1.xml b/protocols/frog-color-management-v1.xml deleted file mode 100644 index aab235a7e..000000000 --- a/protocols/frog-color-management-v1.xml +++ /dev/null @@ -1,366 +0,0 @@ - - - - - Copyright © 2023 Joshua Ashton for Valve Software - Copyright © 2023 Xaver Hugl - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice (including the next - paragraph) shall be included in all copies or substantial portions of the - Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - - - - The aim of this color management extension is to get HDR games working quickly, - and have an easy way to test implementations in the wild before the upstream - protocol is ready to be merged. - For that purpose it's intentionally limited and cut down and does not serve - all uses cases. - - - - - The color management factory singleton creates color managed surface objects. - - - - - - - - - - - - - - - - Interface for changing surface color management and HDR state. - - An implementation must: support every part of the version - of the frog_color_managed_surface interface it exposes. - Including all known enums associated with a given version. - - - - - Destroying the color managed surface resets all known color - state for the surface back to 'undefined' implementation-specific - values. - - - - - - Extended information on the transfer functions described - here can be found in the Khronos Data Format specification: - https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - Extended information on render intents described - here can be found in ICC.1:2022: - - https://www.color.org/specification/ICC.1-2022-05.pdf - - - - - - - NOTE: On a surface with "perceptual" (default) render intent, handling of the container's - color volume - is implementation-specific, and may differ between different transfer functions it is paired - with: - ie. sRGB + 709 rendering may have it's primaries widened to more of the available display's - gamut - to be be more pleasing for the viewer. - Compared to scRGB Linear + 709 being treated faithfully as 709 - (including utilizing negatives out of the 709 gamut triangle) - - - - - - - Forwards HDR metadata from the client to the compositor. - - HDR Metadata Infoframe as per CTA 861.G spec. - - Usage of this HDR metadata is implementation specific and - outside of the scope of this protocol. - - - - Mastering Red Color Primary X Coordinate of the Data. - - Coded as unsigned 16-bit values in units of - 0.00002, where 0x0000 represents zero and 0xC350 - represents 1.0000. - - - - - Mastering Red Color Primary Y Coordinate of the Data. - - Coded as unsigned 16-bit values in units of - 0.00002, where 0x0000 represents zero and 0xC350 - represents 1.0000. - - - - - Mastering Green Color Primary X Coordinate of the Data. - - Coded as unsigned 16-bit values in units of - 0.00002, where 0x0000 represents zero and 0xC350 - represents 1.0000. - - - - - Mastering Green Color Primary Y Coordinate of the Data. - - Coded as unsigned 16-bit values in units of - 0.00002, where 0x0000 represents zero and 0xC350 - represents 1.0000. - - - - - Mastering Blue Color Primary X Coordinate of the Data. - - Coded as unsigned 16-bit values in units of - 0.00002, where 0x0000 represents zero and 0xC350 - represents 1.0000. - - - - - Mastering Blue Color Primary Y Coordinate of the Data. - - Coded as unsigned 16-bit values in units of - 0.00002, where 0x0000 represents zero and 0xC350 - represents 1.0000. - - - - - Mastering White Point X Coordinate of the Data. - - These are coded as unsigned 16-bit values in units of - 0.00002, where 0x0000 represents zero and 0xC350 - represents 1.0000. - - - - - Mastering White Point Y Coordinate of the Data. - - These are coded as unsigned 16-bit values in units of - 0.00002, where 0x0000 represents zero and 0xC350 - represents 1.0000. - - - - - Max Mastering Display Luminance. - This value is coded as an unsigned 16-bit value in units of 1 cd/m2, - where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. - - - - - Min Mastering Display Luminance. - This value is coded as an unsigned 16-bit value in units of - 0.0001 cd/m2, where 0x0001 represents 0.0001 cd/m2 and 0xFFFF - represents 6.5535 cd/m2. - - - - - Max Content Light Level. - This value is coded as an unsigned 16-bit value in units of 1 cd/m2, - where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. - - - - - Max Frame Average Light Level. - This value is coded as an unsigned 16-bit value in units of 1 cd/m2, - where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. - - - - - - - Current preferred metadata for a surface. - The application should use this information to tone-map its buffers - to this target before committing. - - This metadata does not necessarily correspond to any physical output, but - rather what the compositor thinks would be best for a given surface. - - - - Specifies a known transfer function that corresponds to the - output the surface is targeting. - - - - - Output Red Color Primary X Coordinate of the Data. - - Coded as unsigned 16-bit values in units of - 0.00002, where 0x0000 represents zero and 0xC350 - represents 1.0000. - - - - - Output Red Color Primary Y Coordinate of the Data. - - Coded as unsigned 16-bit values in units of - 0.00002, where 0x0000 represents zero and 0xC350 - represents 1.0000. - - - - - Output Green Color Primary X Coordinate of the Data. - - Coded as unsigned 16-bit values in units of - 0.00002, where 0x0000 represents zero and 0xC350 - represents 1.0000. - - - - - Output Green Color Primary Y Coordinate of the Data. - - Coded as unsigned 16-bit values in units of - 0.00002, where 0x0000 represents zero and 0xC350 - represents 1.0000. - - - - - Output Blue Color Primary X Coordinate of the Data. - - Coded as unsigned 16-bit values in units of - 0.00002, where 0x0000 represents zero and 0xC350 - represents 1.0000. - - - - - Output Blue Color Primary Y Coordinate of the Data. - - Coded as unsigned 16-bit values in units of - 0.00002, where 0x0000 represents zero and 0xC350 - represents 1.0000. - - - - - Output White Point X Coordinate of the Data. - - These are coded as unsigned 16-bit values in units of - 0.00002, where 0x0000 represents zero and 0xC350 - represents 1.0000. - - - - - Output White Point Y Coordinate of the Data. - - These are coded as unsigned 16-bit values in units of - 0.00002, where 0x0000 represents zero and 0xC350 - represents 1.0000. - - - - - Max Output Luminance - The max luminance in nits that the output is capable of rendering in small areas. - Content should: not exceed this value to avoid clipping. - - This value is coded as an unsigned 16-bit value in units of 1 cd/m2, - where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. - - - - - Min Output Luminance - The min luminance that the output is capable of rendering. - Content should: not exceed this value to avoid clipping. - - This value is coded as an unsigned 16-bit value in units of - 0.0001 cd/m2, where 0x0001 represents 0.0001 cd/m2 and 0xFFFF - represents 6.5535 cd/m2. - - - - - Max Full Frame Luminance - The max luminance in nits that the output is capable of rendering for the - full frame sustained. - - This value is coded as an unsigned 16-bit value in units of 1 cd/m2, - where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. - - - - - \ No newline at end of file diff --git a/protocols/xx-color-management-v4.xml b/protocols/xx-color-management-v4.xml deleted file mode 100644 index 23ff716ed..000000000 --- a/protocols/xx-color-management-v4.xml +++ /dev/null @@ -1,1457 +0,0 @@ - - - - Copyright 2019 Sebastian Wick - Copyright 2019 Erwin Burema - Copyright 2020 AMD - Copyright 2020-2024 Collabora, Ltd. - Copyright 2024 Xaver Hugl - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice (including the next - paragraph) shall be included in all copies or substantial portions of the - Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - - - - The aim of the color management extension is to allow clients to know - the color properties of outputs, and to tell the compositor about the color - properties of their content on surfaces. Doing this enables a compositor - to perform automatic color management of content for different outputs - according to how content is intended to look like. - - The color properties are represented as an image description object which - is immutable after it has been created. A wl_output always has an - associated image description that clients can observe. A wl_surface - always has an associated preferred image description as a hint chosen by - the compositor that clients can also observe. Clients can set an image - description on a wl_surface to denote the color characteristics of the - surface contents. - - An image description includes SDR and HDR colorimetry and encoding, HDR - metadata, and viewing environment parameters. An image description does - not include the properties set through color-representation extension. - It is expected that the color-representation extension is used in - conjunction with the color management extension when necessary, - particularly with the YUV family of pixel formats. - - Recommendation ITU-T H.273 - "Coding-independent code points for video signal type identification" - shall be referred to as simply H.273 here. - - The color-and-hdr repository - (https://gitlab.freedesktop.org/pq/color-and-hdr) contains - background information on the protocol design and legacy color management. - It also contains a glossary, learning resources for digital color, tools, - samples and more. - - The terminology used in this protocol is based on common color science and - color encoding terminology where possible. The glossary in the color-and-hdr - repository shall be the authority on the definition of terms in this - protocol. - - - - - A global interface used for getting color management extensions for - wl_surface and wl_output objects, and for creating client defined image - description objects. The extension interfaces allow - getting the image description of outputs and setting the image - description of surfaces. - - - - - Destroy the xx_color_manager_v4 object. This does not affect any other - objects in any way. - - - - - - - - - - - See the ICC.1:2022 specification from the International Color Consortium - for more details about rendering intents. - - The principles of ICC defined rendering intents apply with all types of - image descriptions, not only those with ICC file profiles. - - Compositors must support the perceptual rendering intent. Other - rendering intents are optional. - - - - - - - - - - - - - - - - - - - - The compositor supports set_mastering_display_primaries request with a - target color volume fully contained inside the primary color volume. - - - - - The compositor additionally supports target color volumes that - extend outside of the primary color volume. - - This can only be advertised if feature set_mastering_display_primaries - is supported as well. - - - - - - - Named color primaries used to encode well-known sets of primaries. H.273 - is the authority, when it comes to the exact values of primaries and - authoritative specifications, where an equivalent code point exists. - - Descriptions do list the specifications for convenience. - - - - - Color primaries as defined by - - Rec. ITU-R BT.709-6 - - Rec. ITU-R BT.1361-0 conventional colour gamut system and extended - colour gamut system (historical) - - IEC 61966-2-1 sRGB or sYCC - - IEC 61966-2-4 - - Society of Motion Picture and Television Engineers (SMPTE) RP 177 - (1993) Annex B - Equivalent to H.273 ColourPrimaries code point 1. - - - - - Color primaries as defined by - - Rec. ITU-R BT.470-6 System M (historical) - - United States National Television System Committee 1953 - Recommendation for transmission standards for color television - - United States Federal Communications Commission (2003) Title 47 Code - of Federal Regulations 73.682 (a)(20) - Equivalent to H.273 ColourPrimaries code point 4. - - - - - Color primaries as defined by - - Rec. ITU-R BT.470-6 System B, G (historical) - - Rec. ITU-R BT.601-7 625 - - Rec. ITU-R BT.1358-0 625 (historical) - - Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM - Equivalent to H.273 ColourPrimaries code point 5. - - - - - Color primaries as defined by - - Rec. ITU-R BT.601-7 525 - - Rec. ITU-R BT.1358-1 525 or 625 (historical) - - Rec. ITU-R BT.1700-0 NTSC - - SMPTE 170M (2004) - - SMPTE 240M (1999) (historical) - Equivalent to H.273 ColourPrimaries code point 6 and 7. - - - - - Color primaries as defined by H.273 for generic film. - Equivalent to H.273 ColourPrimaries code point 8. - - - - - Color primaries as defined by - - Rec. ITU-R BT.2020-2 - - Rec. ITU-R BT.2100-0 - Equivalent to H.273 ColourPrimaries code point 9. - - - - - Color primaries as defined as the maximum of the CIE 1931 XYZ color - space by - - SMPTE ST 428-1 - - (CIE 1931 XYZ as in ISO 11664-1) - Equivalent to H.273 ColourPrimaries code point 10. - - - - - Color primaries as defined by Digital Cinema System and published in - SMPTE RP 431-2 (2011). Equivalent to H.273 ColourPrimaries code point - 11. - - - - - Color primaries as defined by Digital Cinema System and published in - SMPTE EG 432-1 (2010). - Equivalent to H.273 ColourPrimaries code point 12. - - - - - Color primaries as defined by Adobe as "Adobe RGB" and later published - by ISO 12640-4 (2011). - - - - - - - Named transfer functions used to encode well-known transfer - characteristics. H.273 is the authority, when it comes to the exact - formulas and authoritative specifications, where an equivalent code - point exists. - - Descriptions do list the specifications for convenience. - - - - - Transfer characteristics as defined by - - Rec. ITU-R BT.709-6 - - Rec. ITU-R BT.1361-0 conventional colour gamut system (historical) - Equivalent to H.273 TransferCharacteristics code point 1, 6, 14, 15. - - - - - Transfer characteristics as defined by - - Rec. ITU-R BT.470-6 System M (historical) - - United States National Television System Committee 1953 - Recommendation for transmission standards for color television - - United States Federal Communications Commission (2003) Title 47 Code - of Federal Regulations 73.682 (a) (20) - - Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM - Equivalent to H.273 TransferCharacteristics code point 4. - - - - - Transfer characteristics as defined by - - Rec. ITU-R BT.470-6 System B, G (historical) - Equivalent to H.273 TransferCharacteristics code point 5. - - - - - Transfer characteristics as defined by - - SMPTE ST 240 (1999) - Equivalent to H.273 TransferCharacteristics code point 7. - - - - - Linear transfer characteristics. - Equivalent to H.273 TransferCharacteristics code point 8. - - - - - Logarithmic transfer characteristic (100:1 range). - Equivalent to H.273 TransferCharacteristics code point 9. - - - - - Logarithmic transfer characteristic (100 * Sqrt(10) : 1 range). - Equivalent to H.273 TransferCharacteristics code point 10. - - - - - Transfer characteristics as defined by - - IEC 61966-2-4 - Equivalent to H.273 TransferCharacteristics code point 11. - - - - - Transfer characteristics as defined by - - Rec. ITU-R BT.1361-0 extended colour gamut system (historical) - Equivalent to H.273 TransferCharacteristics code point 12. - - - - - Transfer characteristics as defined by - - IEC 61966-2-1 sRGB - Equivalent to H.273 TransferCharacteristics code point 13 with - MatrixCoefficients set to 0. - - - - - Transfer characteristics as defined by - - IEC 61966-2-1 sYCC - Equivalent to H.273 TransferCharacteristics code point 13 with - MatrixCoefficients set to anything but 0. - - - - - Transfer characteristics as defined by - - SMPTE ST 2084 (2014) for 10-, 12-, 14- and 16-bit systems - - Rec. ITU-R BT.2100-2 perceptual quantization (PQ) system - Equivalent to H.273 TransferCharacteristics code point 16. - - This TF implies these default luminances - - primary color volume minimum: 0.005 cd/m² - - primary color volume maximum: 10000 cd/m² - - reference white: 203 cd/m² - - - - - Transfer characteristics as defined by - - SMPTE ST 428-1 (2019) - Equivalent to H.273 TransferCharacteristics code point 17. - - - - - Transfer characteristics as defined by - - ARIB STD-B67 (2015) - - Rec. ITU-R BT.2100-2 hybrid log-gamma (HLG) system - Equivalent to H.273 TransferCharacteristics code point 18. - - This TF implies these default luminances - - primary color volume minimum: 0.005 cd/m² - - primary color volume maximum: 1000 cd/m² - - reference white: 203 cd/m² - Note: HLG is a scene referred signal. All absolute luminance values - used here for HLG assume a 1000 cd/m² display. - - - - - - - This creates a new xx_color_management_output_v4 object for the - given wl_output. - - See the xx_color_management_output_v4 interface for more details. - - - - - - - - - If a xx_color_management_surface_v4 object already exists for the given - wl_surface, the protocol error surface_exists is raised. - - This creates a new color xx_color_management_surface_v4 object for the - given wl_surface. - - See the xx_color_management_surface_v4 interface for more details. - - - - - - - - - This creates a new color xx_color_management_feedback_surface_v4 object - for the given wl_surface. - - See the xx_color_management_feedback_surface_v4 interface for more - details. - - - - - - - - - Makes a new ICC-based image description creator object with all - properties initially unset. The client can then use the object's - interface to define all the required properties for an image description - and finally create a xx_image_description_v4 object. - - This request can be used when the compositor advertises - xx_color_manager_v4.feature.icc_v2_v4. - Otherwise this request raises the protocol error unsupported_feature. - - - - - - - - Makes a new parametric image description creator object with all - properties initially unset. The client can then use the object's - interface to define all the required properties for an image description - and finally create a xx_image_description_v4 object. - - This request can be used when the compositor advertises - xx_color_manager_v4.feature.parametric. - Otherwise this request raises the protocol error unsupported_feature. - - - - - - - - When this object is created, it shall immediately send this event once - for each rendering intent the compositor supports. - - - - - - - - When this object is created, it shall immediately send this event once - for each compositor supported feature listed in the enumeration. - - - - - - - - When this object is created, it shall immediately send this event once - for each named transfer function the compositor supports with the - parametric image description creator. - - - - - - - - When this object is created, it shall immediately send this event once - for each named set of primaries the compositor supports with the - parametric image description creator. - - - - - - - - - A xx_color_management_output_v4 describes the color properties of an - output. - - The xx_color_management_output_v4 is associated with the wl_output global - underlying the wl_output object. Therefore the client destroying the - wl_output object has no impact, but the compositor removing the output - global makes the xx_color_management_output_v4 object inert. - - - - - Destroy the color xx_color_management_output_v4 object. This does not - affect any remaining protocol objects. - - - - - - This event is sent whenever the image description of the output changed, - followed by one wl_output.done event common to output events across all - extensions. - - If the client wants to use the updated image description, it needs to do - get_image_description again, because image description objects are - immutable. - - - - - - This creates a new xx_image_description_v4 object for the current image - description of the output. There always is exactly one image description - active for an output so the client should destroy the image description - created by earlier invocations of this request. This request is usually - sent as a reaction to the image_description_changed event or when - creating a xx_color_management_output_v4 object. - - The image description of an output represents the color encoding the - output expects. There might be performance and power advantages, as well - as improved color reproduction, if a content update matches the image - description of the output it is being shown on. If a content update is - shown on any other output than the one it matches the image description - of, then the color reproduction on those outputs might be considerably - worse. - - The created xx_image_description_v4 object preserves the image - description of the output from the time the object was created. - - The resulting image description object allows get_information request. - - If this protocol object is inert, the resulting image description object - shall immediately deliver the xx_image_description_v4.failed event with - the no_output cause. - - If the interface version is inadequate for the output's image - description, meaning that the client does not support all the events - needed to deliver the crucial information, the resulting image - description object shall immediately deliver the - xx_image_description_v4.failed event with the low_version cause. - - Otherwise the object shall immediately deliver the ready event. - - - - - - - - - A xx_color_management_surface_v4 allows the client to set the color - space and HDR properties of a surface. - - If the wl_surface associated with the xx_color_management_surface_v4 is - destroyed, the xx_color_management_surface_v4 object becomes inert. - - - - - Destroy the xx_color_management_surface_v4 object and do the same as - unset_image_description. - - - - - - - - - - - - Set the image description of the underlying surface. The image - description and rendering intent are double-buffered state, see - wl_surface.commit. - - It is the client's responsibility to understand the image description - it sets on a surface, and to provide content that matches that image - description. Compositors might convert images to match their own or any - other image descriptions. - - Image description whose creation gracefully failed (received - xx_image_description_v4.failed) are forbidden in this request, and in - such case the protocol error image_description is raised. - - All image descriptions whose creation succeeded (received - xx_image_description_v4.ready) are allowed and must always be accepted - by the compositor. - - A rendering intent provides the client's preference on how content - colors should be mapped to each output. The render_intent value must - be one advertised by the compositor with - xx_color_manager_v4.render_intent event, otherwise the protocol error - render_intent is raised. - - By default, a surface does not have an associated image description - nor a rendering intent. The handling of color on such surfaces is - compositor implementation defined. Compositors should handle such - surfaces as sRGB but may handle them differently if they have specific - requirements. - - - - - - - - - This request removes any image description from the surface. See - set_image_description for how a compositor handles a surface without - an image description. This is double-buffered state, see - wl_surface.commit. - - - - - - - A xx_color_management_feedback_surface_v4 allows the client to get the - preferred color description of a surface. - - If the wl_surface associated with this object is destroyed, the - xx_color_management_feedback_surface_v4 object becomes inert. - - - - - Destroy the xx_color_management_feedback_surface_v4 object. - - - - - - - - - - - The preferred image description is the one which likely has the most - performance and/or quality benefits for the compositor if used by the - client for its wl_surface contents. This event is sent whenever the - compositor changes the wl_surface's preferred image description. - - This event is merely a notification. When the client wants to know - what the preferred image description is, it shall use the get_preferred - request. - - The preferred image description is not automatically used for anything. - It is only a hint, and clients may set any valid image description with - set_image_description but there might be performance and color accuracy - improvements by providing the wl_surface contents in the preferred - image description. Therefore clients that can, should render according - to the preferred image description - - - - - - If this protocol object is inert, the protocol error inert is raised. - - The preferred image description represents the compositor's preferred - color encoding for this wl_surface at the current time. There might be - performance and power advantages, as well as improved color - reproduction, if the image description of a content update matches the - preferred image description. - - This creates a new xx_image_description_v4 object for the currently - preferred image description for the wl_surface. The client should - stop using and destroy the image descriptions created by earlier - invocations of this request for the associated wl_surface. - This request is usually sent as a reaction to the preferred_changed - event or when creating a xx_color_management_feedback_surface_v4 object - if the client is capable of adapting to image descriptions. - - The created xx_image_description_v4 object preserves the preferred image - description of the wl_surface from the time the object was created. - - The resulting image description object allows get_information request. - - If the interface version is inadequate for the preferred image - description, meaning that the client does not support all the - events needed to deliver the crucial information, the resulting image - description object shall immediately deliver the - xx_image_description_v4.failed event with the low_version cause, - otherwise the object shall immediately deliver the ready event. - - - - - - - - - This type of object is used for collecting all the information required - to create a xx_image_description_v4 object from an ICC file. A complete - set of required parameters consists of these properties: - - ICC file - - Each required property must be set exactly once if the client is to create - an image description. The set requests verify that a property was not - already set. The create request verifies that all required properties are - set. There may be several alternative requests for setting each property, - and in that case the client must choose one of them. - - Once all properties have been set, the create request must be used to - create the image description object, destroying the creator in the - process. - - - - - - - - - - - - - - - Create an image description object based on the ICC information - previously set on this object. A compositor must parse the ICC data in - some undefined but finite amount of time. - - The completeness of the parameter set is verified. If the set is not - complete, the protocol error incomplete_set is raised. For the - definition of a complete set, see the description of this interface. - - If the particular combination of the information is not supported - by the compositor, the resulting image description object shall - immediately deliver the xx_image_description_v4.failed event with the - 'unsupported' cause. If a valid image description was created from the - information, the xx_image_description_v4.ready event will eventually - be sent instead. - - This request destroys the xx_image_description_creator_icc_v4 object. - - The resulting image description object does not allow get_information - request. - - - - - - - - Sets the ICC profile file to be used as the basis of the image - description. - - The data shall be found through the given fd at the given offset, having - the given length. The fd must seekable and readable. Violating these - requirements raises the bad_fd protocol error. - - If reading the data fails due to an error independent of the client, the - compositor shall send the xx_image_description_v4.failed event on the - created xx_image_description_v4 with the 'operating_system' cause. - - The maximum size of the ICC profile is 4 MB. If length is greater than - that or zero, the protocol error bad_size is raised. If offset + length - exceeds the file size, the protocol error out_of_file is raised. - - A compositor may read the file at any time starting from this request - and only until whichever happens first: - - If create request was issued, the xx_image_description_v4 object - delivers either failed or ready event; or - - if create request was not issued, this - xx_image_description_creator_icc_v4 object is destroyed. - - A compositor shall not modify the contents of the file, and the fd may - be sealed for writes and size changes. The client must ensure to its - best ability that the data does not change while the compositor is - reading it. - - The data must represent a valid ICC profile. The ICC profile version - must be 2 or 4, it must be a 3 channel profile and the class must be - Display or ColorSpace. Violating these requirements will not result in a - protocol error but will eventually send the - xx_image_description_v4.failed event on the created - xx_image_description_v4 with the 'unsupported' cause. - - See the International Color Consortium specification ICC.1:2022 for more - details about ICC profiles. - - If ICC file has already been set on this object, the protocol error - already_set is raised. - - - - - - - - - - - This type of object is used for collecting all the parameters required - to create a xx_image_description_v4 object. A complete set of required - parameters consists of these properties: - - transfer characteristic function (tf) - - chromaticities of primaries and white point (primary color volume) - - The following properties are optional and have a well-defined default - if not explicitly set: - - primary color volume luminance range - - reference white luminance level - - mastering display primaries and white point (target color volume) - - mastering luminance range - - maximum content light level - - maximum frame-average light level - - Each required property must be set exactly once if the client is to create - an image description. The set requests verify that a property was not - already set. The create request verifies that all required properties are - set. There may be several alternative requests for setting each property, - and in that case the client must choose one of them. - - Once all properties have been set, the create request must be used to - create the image description object, destroying the creator in the - process. - - - - - - - - - - - - - - - - - - Create an image description object based on the parameters previously - set on this object. - - The completeness of the parameter set is verified. If the set is not - complete, the protocol error incomplete_set is raised. For the - definition of a complete set, see the description of this interface. - - Also, the combination of the parameter set is verified. If the set is - not consistent, the protocol error inconsistent_set is raised. - - If the particular combination of the parameter set is not supported - by the compositor, the resulting image description object shall - immediately deliver the xx_image_description_v4.failed event with the - 'unsupported' cause. If a valid image description was created from the - parameter set, the xx_image_description_v4.ready event will eventually - be sent instead. - - This request destroys the xx_image_description_creator_params_v4 - object. - - The resulting image description object does not allow get_information - request. - - - - - - - - Sets the transfer characteristic using explicitly enumerated named - functions. - - When the resulting image description is attached to an image, the - content should be encoded and decoded according to the industry standard - practices for the transfer characteristic. - - Only names advertised with xx_color_manager_v4 event supported_tf_named - are allowed. Other values shall raise the protocol error invalid_tf. - - If transfer characteristic has already been set on this object, the - protocol error already_set is raised. - - - - - - - - Sets the color component transfer characteristic to a power curve with - the given exponent. This curve represents the conversion from electrical - to optical pixel or color values. - - When the resulting image description is attached to an image, the - content should be encoded with the inverse of the power curve. - - The curve exponent shall be multiplied by 10000 to get the argument eexp - value to carry the precision of 4 decimals. - - The curve exponent must be at least 1.0 and at most 10.0. Otherwise the - protocol error invalid_tf is raised. - - If transfer characteristic has already been set on this object, the - protocol error already_set is raised. - - This request can be used when the compositor advertises - xx_color_manager_v4.feature.set_tf_power. Otherwise this request raises - the protocol error unsupported_feature. - - - - - - - - Sets the color primaries and white point using explicitly named sets. - This describes the primary color volume which is the basis for color - value encoding. - - Only names advertised with xx_color_manager_v4 event - supported_primaries_named are allowed. Other values shall raise the - protocol error invalid_primaries. - - If primaries have already been set on this object, the protocol error - already_set is raised. - - - - - - - - Sets the color primaries and white point using CIE 1931 xy chromaticity - coordinates. This describes the primary color volume which is the basis - for color value encoding. - - Each coordinate value is multiplied by 10000 to get the argument value - to carry precision of 4 decimals. - - If primaries have already been set on this object, the protocol error - already_set is raised. - - This request can be used if the compositor advertises - xx_color_manager_v4.feature.set_primaries. Otherwise this request raises - the protocol error unsupported_feature. - - - - - - - - - - - - - - - Sets the primary color volume luminance range and the reference white - luminance level. - - The default luminances are - - primary color volume minimum: 0.2 cd/m² - - primary color volume maximum: 80 cd/m² - - reference white: 80 cd/m² - - Setting a named transfer characteristic can imply other default - luminances. - - The default luminances get overwritten when this request is used. - - 'min_lum' and 'max_lum' specify the minimum and maximum luminances of - the primary color volume as reproduced by the targeted display. - - 'reference_lum' specifies the luminance of the reference white as - reproduced by the targeted display, and reflects the targeted viewing - environment. - - Compositors should make sure that all content is anchored, meaning that - an input signal level of 'reference_lum' on one image description and - another input signal level of 'reference_lum' on another image - description should produce the same output level, even though the - 'reference_lum' on both image representations can be different. - - If 'max_lum' is less than the 'reference_lum', or 'reference_lum' is - less than or equal to 'min_lum', the protocol error invalid_luminance is - raised. - - The minimum luminance is multiplied by 10000 to get the argument - 'min_lum' value and carries precision of 4 decimals. The maximum - luminance and reference white luminance values are unscaled. - - If the primary color volume luminance range and the reference white - luminance level have already been set on this object, the protocol error - already_set is raised. - - This request can be used if the compositor advertises - xx_color_manager_v4.feature.set_luminances. Otherwise this request - raises the protocol error unsupported_feature. - - - - - - - - - - Provides the color primaries and white point of the mastering display - using CIE 1931 xy chromaticity coordinates. This is compatible with the - SMPTE ST 2086 definition of HDR static metadata. - - The mastering display primaries define the target color volume. - - If mastering display primaries are not explicitly set, the target color - volume is assumed to be equal to the primary color volume. - - The target color volume is defined by all tristimulus values between 0.0 - and 1.0 (inclusive) of the color space defined by the given mastering - display primaries and white point. The colorimetry is identical between - the container color space and the mastering display color space, - including that no chromatic adaptation is applied even if the white - points differ. - - The target color volume can exceed the primary color volume to allow for - a greater color volume with an existing color space definition (for - example scRGB). It can be smaller than the primary color volume to - minimize gamut and tone mapping distances for big color spaces (HDR - metadata). - - To make use of the entire target color volume a suitable pixel format - has to be chosen (e.g. floating point to exceed the primary color - volume, or abusing limited quantization range as with xvYCC). - - Each coordinate value is multiplied by 10000 to get the argument value - to carry precision of 4 decimals. - - If mastering display primaries have already been set on this object, the - protocol error already_set is raised. - - This request can be used if the compositor advertises - xx_color_manager_v4.feature.set_mastering_display_primaries. Otherwise - this request raises the protocol error unsupported_feature. The - advertisement implies support only for target color volumes fully - contained within the primary color volume. - - If a compositor additionally supports target color volume exceeding the - primary color volume, it must advertise - xx_color_manager_v4.feature.extended_target_volume. If a client uses - target color volume exceeding the primary color volume and the - compositor does not support it, the result is implementation defined. - Compositors are recommended to detect this case and fail the image - description gracefully, but it may as well result in color artifacts. - - - - - - - - - - - - - - - Sets the luminance range that was used during the content mastering - process as the minimum and maximum absolute luminance L. This is - compatible with the SMPTE ST 2086 definition of HDR static metadata. - - The mastering luminance range is undefined by default. - - If max L is less than or equal to min L, the protocol error - invalid_luminance is raised. - - Min L value is multiplied by 10000 to get the argument min_lum value - and carry precision of 4 decimals. Max L value is unscaled for max_lum. - - - - - - - - - Sets the maximum content light level (max_cll) as defined by CTA-861-H. - - This can only be set when set_tf_cicp is used to set the transfer - characteristic to Rec. ITU-R BT.2100-2 perceptual quantization system. - Otherwise, 'create' request shall raise inconsistent_set protocol - error. - - max_cll is undefined by default. - - - - - - - - Sets the maximum frame-average light level (max_fall) as defined by - CTA-861-H. - - This can only be set when set_tf_cicp is used to set the transfer - characteristic to Rec. ITU-R BT.2100-2 perceptual quantization system. - Otherwise, 'create' request shall raise inconsistent_set protocol error. - - max_fall is undefined by default. - - - - - - - - - An image description carries information about the color encoding used on - a surface when attached to a wl_surface via - xx_color_management_surface_v4.set_image_description. A compositor can use - this information to decode pixel values into colorimetrically meaningful - quantities. - - Note, that the xx_image_description_v4 object is not ready to be used - immediately after creation. The object eventually delivers either the - 'ready' or the 'failed' event, specified in all requests creating it. The - object is deemed "ready" after receiving the 'ready' event. - - An object which is not ready is illegal to use, it can only be destroyed. - Any other request in this interface shall result in the 'not_ready' - protocol error. Attempts to use an object which is not ready through other - interfaces shall raise protocol errors defined there. - - Once created and regardless of how it was created, a - xx_image_description_v4 object always refers to one fixed image - description. It cannot change after creation. - - - - - Destroy this object. It is safe to destroy an object which is not ready. - - Destroying a xx_image_description_v4 object has no side-effects, not - even if a xx_color_management_surface_v4.set_image_description has not - yet been followed by a wl_surface.commit. - - - - - - - - - - - - - - - - - - - - - - If creating a xx_image_description_v4 object fails for a reason that is - not defined as a protocol error, this event is sent. - - The requests that create image description objects define whether and - when this can occur. Only such creation requests can trigger this event. - This event cannot be triggered after the image description was - successfully formed. - - Once this event has been sent, the xx_image_description_v4 object will - never become ready and it can only be destroyed. - - - - - - - - - Once this event has been sent, the xx_image_description_v4 object is - deemed "ready". Ready objects can be used to send requests and can be - used through other interfaces. - - Every ready xx_image_description_v4 protocol object refers to an - underlying image description record in the compositor. Multiple protocol - objects may end up referring to the same record. Clients may identify - these "copies" by comparing their id numbers: if the numbers from two - protocol objects are identical, the protocol objects refer to the same - image description record. Two different image description records - cannot have the same id number simultaneously. The id number does not - change during the lifetime of the image description record. - - The id number is valid only as long as the protocol object is alive. If - all protocol objects referring to the same image description record are - destroyed, the id number may be recycled for a different image - description record. - - Image description id number is not a protocol object id. Zero is - reserved as an invalid id number. It shall not be possible for a client - to refer to an image description by its id number in protocol. The id - numbers might not be portable between Wayland connections. - - This identity allows clients to de-duplicate image description records - and avoid get_information request if they already have the image - description information. - - - - - - - - Creates a xx_image_description_info_v4 object which delivers the - information that makes up the image description. - - Not all image description protocol objects allow get_information - request. Whether it is allowed or not is defined by the request that - created the object. If get_information is not allowed, the protocol - error no_information is raised. - - - - - - - - - Sends all matching events describing an image description object exactly - once and finally sends the 'done' event. - - Once a xx_image_description_info_v4 object has delivered a 'done' event it - is automatically destroyed. - - Every xx_image_description_info_v4 created from the same - xx_image_description_v4 shall always return the exact same data. - - - - - Signals the end of information events and destroys the object. - - - - - - The icc argument provides a file descriptor to the client which may be - memory-mapped to provide the ICC profile matching the image description. - The fd is read-only, and if mapped then it must be mapped with - MAP_PRIVATE by the client. - - The ICC profile version and other details are determined by the - compositor. There is no provision for a client to ask for a specific - kind of a profile. - - - - - - - - - - Delivers the primary color volume primaries and white point using CIE - 1931 xy chromaticity coordinates. - - Each coordinate value is multiplied by 10000 to get the argument value - to carry precision of 4 decimals. - - - - - - - - - - - - - - - Delivers the primary color volume primaries and white point using an - explicitly enumerated named set. - - - - - - - - The color component transfer characteristic of this image description is - a pure power curve. This event provides the exponent of the power - function. This curve represents the conversion from electrical to - optical pixel or color values. - - The curve exponent has been multiplied by 10000 to get the argument eexp - value to carry the precision of 4 decimals. - - - - - - - - Delivers the transfer characteristic using an explicitly enumerated - named function. - - - - - - - - Delivers the primary color volume luminance range and the reference - white luminance level. - - The minimum luminance is multiplied by 10000 to get the argument - 'min_lum' value and carries precision of 4 decimals. The maximum - luminance and reference white luminance values are unscaled. - - - - - - - - - - Provides the color primaries and white point of the target color volume - using CIE 1931 xy chromaticity coordinates. This is compatible with the - SMPTE ST 2086 definition of HDR static metadata for mastering displays. - - While primary color volume is about how color is encoded, the target - color volume is the actually displayable color volume. If target color - volume is equal to the primary color volume, then this event is not - sent. - - Each coordinate value is multiplied by 10000 to get the argument value - to carry precision of 4 decimals. - - - - - - - - - - - - - - - Provides the luminance range that the image description is targeting as - the minimum and maximum absolute luminance L. This is compatible with - the SMPTE ST 2086 definition of HDR static metadata. - - This luminance range is only theoretical and may not correspond to the - luminance of light emitted on an actual display. - - Min L value is multiplied by 10000 to get the argument min_lum value and - carry precision of 4 decimals. Max L value is unscaled for max_lum. - - - - - - - - - Provides the targeted max_cll of the image description. max_cll is - defined by CTA-861-H. - - This luminance is only theoretical and may not correspond to the - luminance of light emitted on an actual display. - - - - - - - - Provides the targeted max_fall of the image description. max_fall is - defined by CTA-861-H. - - This luminance is only theoretical and may not correspond to the - luminance of light emitted on an actual display. - - - - - - \ No newline at end of file diff --git a/scripts/generateShaderIncludes.sh b/scripts/generateShaderIncludes.sh index 20c78e9d9..c9419031a 100755 --- a/scripts/generateShaderIncludes.sh +++ b/scripts/generateShaderIncludes.sh @@ -15,7 +15,7 @@ echo 'static const std::map SHADERS = {' >> ./src/rend for filename in `ls ${SHADERS_SRC}`; do echo "-- ${filename}" - { echo 'R"#('; cat ${SHADERS_SRC}/${filename}; echo ')#"'; } > ./src/render/shaders/${filename}.inc + { echo -n 'R"#('; cat ${SHADERS_SRC}/${filename}; echo ')#"'; } > ./src/render/shaders/${filename}.inc echo "{\"${filename}\"," >> ./src/render/shaders/Shaders.hpp echo "#include \"./${filename}.inc\"" >> ./src/render/shaders/Shaders.hpp echo "}," >> ./src/render/shaders/Shaders.hpp diff --git a/src/Compositor.cpp b/src/Compositor.cpp index e8829fd08..f823b62cb 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -2,12 +2,17 @@ #include #include "Compositor.hpp" -#include "debug/Log.hpp" +#include "debug/log/Logger.hpp" #include "desktop/DesktopTypes.hpp" #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" +#include "config/legacy/ConfigManager.hpp" +#include "config/shared/inotify/ConfigWatcher.hpp" +#include "config/shared/monitor/MonitorRuleManager.hpp" #include "managers/CursorManager.hpp" #include "managers/TokenManager.hpp" #include "managers/PointerManager.hpp" @@ -17,6 +22,7 @@ #include "managers/ANRManager.hpp" #include "managers/eventLoop/EventLoopManager.hpp" #include "managers/permissions/DynamicPermissionManager.hpp" +#include "managers/screenshare/ScreenshareManager.hpp" #include #include #include @@ -27,11 +33,14 @@ #include #include #include "debug/HyprCtl.hpp" -#include "debug/CrashReporter.hpp" +#include "debug/crash/CrashReporter.hpp" +#include "render/GLRenderer.hpp" +#include "render/ShaderLoader.hpp" #ifdef USES_SYSTEMD #include // for SdNotify #endif #include "helpers/fs/FsUtils.hpp" +#include "helpers/env/Env.hpp" #include "protocols/FractionalScale.hpp" #include "protocols/PointerConstraints.hpp" #include "protocols/LayerShell.hpp" @@ -41,7 +50,8 @@ #include "protocols/ColorManagement.hpp" #include "protocols/core/Compositor.hpp" #include "protocols/core/Subcompositor.hpp" -#include "desktop/LayerSurface.hpp" +#include "desktop/view/LayerSurface.hpp" +#include "layout/space/Space.hpp" #include "render/Renderer.hpp" #include "xwayland/XWayland.hpp" #include "helpers/ByteOperations.hpp" @@ -52,23 +62,28 @@ #include "managers/XWaylandManager.hpp" #include "config/ConfigManager.hpp" +#include "config/shared/workspace/WorkspaceRuleManager.hpp" #include "render/OpenGL.hpp" #include "managers/input/InputManager.hpp" #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" -#include "hyprerror/HyprError.hpp" -#include "debug/HyprNotificationOverlay.hpp" -#include "debug/HyprDebugOverlay.hpp" +#include "errorOverlay/Overlay.hpp" +#include "notification/NotificationOverlay.hpp" +#include "debug/Overlay.hpp" #include "helpers/MonitorFrameScheduler.hpp" #include "i18n/Engine.hpp" +#include "layout/LayoutManager.hpp" +#include "layout/target/WindowTarget.hpp" +#include "event/EventBus.hpp" #include +#include +#include #include #include @@ -83,9 +98,10 @@ using namespace Hyprutils::String; using namespace Aquamarine; using enum NContentType::eContentType; using namespace NColorManagement; +using namespace Render::GL; static int handleCritSignal(int signo, void* data) { - Debug::log(LOG, "Hyprland received signal {}", signo); + Log::logger->log(Log::DEBUG, "Hyprland received signal {}", signo); if (signo == SIGTERM || signo == SIGINT || signo == SIGKILL) g_pCompositor->stopCompositor(); @@ -99,20 +115,15 @@ 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"; - write(2, msg, strlen(msg)); + char const* msg = "\nCrashReporter exceeded timeout, forcefully exiting\n"; + [[maybe_unused]] auto w = write(2, msg, strlen(msg)); abort(); }); alarm(15); - NCrashReporter::createAndSaveCrash(sig); + CrashReporter::createAndSaveCrash(sig); abort(); } @@ -124,28 +135,16 @@ static void handleUserSignal(int sig) { } } -static eLogLevel aqLevelToHl(Aquamarine::eBackendLogLevel level) { - switch (level) { - case Aquamarine::eBackendLogLevel::AQ_LOG_TRACE: return TRACE; - case Aquamarine::eBackendLogLevel::AQ_LOG_DEBUG: return LOG; - case Aquamarine::eBackendLogLevel::AQ_LOG_ERROR: return ERR; - case Aquamarine::eBackendLogLevel::AQ_LOG_WARNING: return WARN; - case Aquamarine::eBackendLogLevel::AQ_LOG_CRITICAL: return CRIT; - default: break; - } - - return NONE; -} - -static void aqLog(Aquamarine::eBackendLogLevel level, std::string msg) { - Debug::log(aqLevelToHl(level), "[AQ] {}", msg); +bool CCompositor::setWatchdogFd(int fd) { + m_watchdogWriteFd = Hyprutils::OS::CFileDescriptor{fd}; + return m_watchdogWriteFd.isValid() && !m_watchdogWriteFd.isClosed(); } void CCompositor::bumpNofile() { if (!getrlimit(RLIMIT_NOFILE, &m_originalNofile)) - Debug::log(LOG, "Old rlimit: soft -> {}, hard -> {}", m_originalNofile.rlim_cur, m_originalNofile.rlim_max); + Log::logger->log(Log::DEBUG, "Old rlimit: soft -> {}, hard -> {}", m_originalNofile.rlim_cur, m_originalNofile.rlim_max); else { - Debug::log(ERR, "Failed to get NOFILE rlimits"); + Log::logger->log(Log::ERR, "Failed to get NOFILE rlimits"); m_originalNofile.rlim_max = 0; return; } @@ -155,13 +154,13 @@ void CCompositor::bumpNofile() { newLimit.rlim_cur = newLimit.rlim_max; if (setrlimit(RLIMIT_NOFILE, &newLimit) < 0) { - Debug::log(ERR, "Failed bumping NOFILE limits higher"); + Log::logger->log(Log::ERR, "Failed bumping NOFILE limits higher"); m_originalNofile.rlim_max = 0; return; } if (!getrlimit(RLIMIT_NOFILE, &newLimit)) - Debug::log(LOG, "New rlimit: soft -> {}, hard -> {}", newLimit.rlim_cur, newLimit.rlim_max); + Log::logger->log(Log::DEBUG, "New rlimit: soft -> {}, hard -> {}", newLimit.rlim_cur, newLimit.rlim_max); } void CCompositor::restoreNofile() { @@ -169,7 +168,7 @@ void CCompositor::restoreNofile() { return; if (setrlimit(RLIMIT_NOFILE, &m_originalNofile) < 0) - Debug::log(ERR, "Failed restoring NOFILE limits"); + Log::logger->log(Log::ERR, "Failed restoring NOFILE limits"); } bool CCompositor::supportsDrmSyncobjTimeline() const { @@ -229,27 +228,27 @@ CCompositor::CCompositor(bool onlyConfig) : m_onlyConfigVerification(onlyConfig) throw std::runtime_error("CCompositor() failed"); } - Debug::init(m_instancePath); + Log::logger->initIS(m_instancePath); - Debug::log(LOG, "Instance Signature: {}", m_instanceSignature); + Log::logger->log(Log::DEBUG, "Instance Signature: {}", m_instanceSignature); - Debug::log(LOG, "Runtime directory: {}", m_instancePath); + Log::logger->log(Log::DEBUG, "Runtime directory: {}", m_instancePath); - Debug::log(LOG, "Hyprland PID: {}", m_hyprlandPID); + Log::logger->log(Log::DEBUG, "Hyprland PID: {}", m_hyprlandPID); - Debug::log(LOG, "===== SYSTEM INFO: ====="); + Log::logger->log(Log::DEBUG, "===== SYSTEM INFO: ====="); logSystemInfo(); - Debug::log(LOG, "========================"); + Log::logger->log(Log::DEBUG, "========================"); - Debug::log(NONE, "\n\n"); // pad + Log::logger->log(Log::DEBUG, "\n\n"); // pad - Debug::log(INFO, "If you are crashing, or encounter any bugs, please consult https://wiki.hypr.land/Crashes-and-Bugs/\n\n"); + Log::logger->log(Log::INFO, "If you are crashing, or encounter any bugs, please consult https://wiki.hypr.land/Crashes-and-Bugs/\n\n"); setRandomSplash(); - Debug::log(LOG, "\nCurrent splash: {}\n\n", m_currentSplash); + Log::logger->log(Log::DEBUG, "\nCurrent splash: {}\n\n", m_currentSplash); bumpNofile(); } @@ -291,12 +290,12 @@ 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(); + Config::initConfigManager(); + Config::mgr()->init(); - std::println("\n\n======== Config parsing result:\n\n{}", g_pConfigManager->verify()); + std::println("\n\n======== Config parsing result:\n\n{}", Config::mgr()->verify()); return; } @@ -309,7 +308,7 @@ void CCompositor::initServer(std::string socketName, int socketFd) { // register crit signal handler m_critSigSource = wl_event_loop_add_signal(m_wlEventLoop, SIGTERM, handleCritSignal, nullptr); - if (!envEnabled("HYPRLAND_NO_CRASHREPORTER")) { + if (!Env::envEnabled("HYPRLAND_NO_CRASHREPORTER")) { signal(SIGSEGV, handleUnrecoverableSignal); signal(SIGABRT, handleUnrecoverableSignal); } @@ -317,14 +316,16 @@ void CCompositor::initServer(std::string socketName, int socketFd) { initManagers(STAGE_PRIORITY); - if (envEnabled("HYPRLAND_TRACE")) - Debug::m_trace = true; + Log::logger->initCallbacks(); // set the buffer size to 1MB to avoid disconnects due to an app hanging for a short while wl_display_set_default_max_buffer_size(m_wlDisplay, 1_MB); - Aquamarine::SBackendOptions options{}; - options.logFunction = aqLog; + Aquamarine::SBackendOptions options{}; + SP conn = makeShared(Log::logger->hu()); + conn->setLogLevel(Log::DEBUG); + conn->setName("aquamarine"); + options.logConnection = std::move(conn); std::vector implementations; Aquamarine::SBackendImplementationOptions option; @@ -341,9 +342,10 @@ void CCompositor::initServer(std::string socketName, int socketFd) { m_aqBackend = CBackend::create(implementations, options); if (!m_aqBackend) { - Debug::log(CRIT, - "m_pAqBackend was null! This usually means aquamarine could not find a GPU or encountered some issues. Make sure you're running either on a tty or on a Wayland " - "session, NOT an X11 one."); + Log::logger->log( + Log::CRIT, + "m_pAqBackend was null! This usually means aquamarine could not find a GPU or encountered some issues. Make sure you're running either on a tty or on a Wayland " + "session, NOT an X11 one."); throwError("CBackend::create() failed!"); } @@ -352,19 +354,20 @@ void CCompositor::initServer(std::string socketName, int socketFd) { initAllSignals(); if (!m_aqBackend->start()) { - Debug::log(CRIT, - "m_pAqBackend couldn't start! This usually means aquamarine could not find a GPU or encountered some issues. Make sure you're running either on a tty or on a " - "Wayland session, NOT an X11 one."); + Log::logger->log( + Log::CRIT, + "m_pAqBackend couldn't start! This usually means aquamarine could not find a GPU or encountered some issues. Make sure you're running either on a tty or on a " + "Wayland session, NOT an X11 one."); throwError("CBackend::create() failed!"); } m_initialized = true; m_drm.fd = m_aqBackend->drmFD(); - Debug::log(LOG, "Running on DRMFD: {}", m_drm.fd); + Log::logger->log(Log::DEBUG, "Running on DRMFD: {}", m_drm.fd); m_drmRenderNode.fd = m_aqBackend->drmRenderNodeFD(); - Debug::log(LOG, "Using RENDERNODEFD: {}", m_drmRenderNode.fd); + Log::logger->log(Log::DEBUG, "Using RENDERNODEFD: {}", m_drmRenderNode.fd); #if defined(__linux__) auto syncObjSupport = [](auto fd) { @@ -376,16 +379,16 @@ void CCompositor::initServer(std::string socketName, int socketFd) { return ret == 0 && cap != 0; }; - if ((m_drm.syncobjSupport = syncObjSupport(m_drm.fd))) - Debug::log(LOG, "DRM DisplayNode syncobj timeline support: {}", m_drm.syncobjSupport ? "yes" : "no"); + m_drm.syncobjSupport = syncObjSupport(m_drm.fd); + Log::logger->log(Log::DEBUG, "DRM DisplayNode syncobj timeline support: {}", m_drm.syncobjSupport ? "yes" : "no"); - if ((m_drmRenderNode.syncObjSupport = syncObjSupport(m_drmRenderNode.fd))) - Debug::log(LOG, "DRM RenderNode syncobj timeline support: {}", m_drmRenderNode.syncObjSupport ? "yes" : "no"); + m_drmRenderNode.syncObjSupport = syncObjSupport(m_drmRenderNode.fd); + Log::logger->log(Log::DEBUG, "DRM RenderNode syncobj timeline support: {}", m_drmRenderNode.syncObjSupport ? "yes" : "no"); if (!m_drm.syncobjSupport && !m_drmRenderNode.syncObjSupport) - Debug::log(LOG, "DRM no syncobj support, disabling explicit sync"); + Log::logger->log(Log::DEBUG, "DRM no syncobj support, disabling explicit sync"); #else - Debug::log(LOG, "DRM syncobj timeline support: no (not linux)"); + Log::logger->log(Log::DEBUG, "DRM syncobj timeline support: no (not linux)"); #endif if (!socketName.empty() && socketFd != -1) { @@ -393,9 +396,9 @@ void CCompositor::initServer(std::string socketName, int socketFd) { const auto RETVAL = wl_display_add_socket_fd(m_wlDisplay, socketFd); if (RETVAL >= 0) { m_wlDisplaySocket = socketName; - Debug::log(LOG, "wl_display_add_socket_fd for {} succeeded with {}", socketName, RETVAL); + Log::logger->log(Log::DEBUG, "wl_display_add_socket_fd for {} succeeded with {}", socketName, RETVAL); } else - Debug::log(WARN, "wl_display_add_socket_fd for {} returned {}: skipping", socketName, RETVAL); + Log::logger->log(Log::WARN, "wl_display_add_socket_fd for {} returned {}: skipping", socketName, RETVAL); } else { // get socket, avoid using 0 for (int candidate = 1; candidate <= 32; candidate++) { @@ -403,22 +406,22 @@ void CCompositor::initServer(std::string socketName, int socketFd) { const auto RETVAL = wl_display_add_socket(m_wlDisplay, CANDIDATESTR.c_str()); if (RETVAL >= 0) { m_wlDisplaySocket = CANDIDATESTR; - Debug::log(LOG, "wl_display_add_socket for {} succeeded with {}", CANDIDATESTR, RETVAL); + Log::logger->log(Log::DEBUG, "wl_display_add_socket for {} succeeded with {}", CANDIDATESTR, RETVAL); break; } else - Debug::log(WARN, "wl_display_add_socket for {} returned {}: skipping candidate {}", CANDIDATESTR, RETVAL, candidate); + Log::logger->log(Log::WARN, "wl_display_add_socket for {} returned {}: skipping candidate {}", CANDIDATESTR, RETVAL, candidate); } } if (m_wlDisplaySocket.empty()) { - Debug::log(WARN, "All candidates failed, trying wl_display_add_socket_auto"); + Log::logger->log(Log::WARN, "All candidates failed, trying wl_display_add_socket_auto"); const auto SOCKETSTR = wl_display_add_socket_auto(m_wlDisplay); if (SOCKETSTR) m_wlDisplaySocket = SOCKETSTR; } if (m_wlDisplaySocket.empty()) { - Debug::log(CRIT, "m_szWLDisplaySocket NULL!"); + Log::logger->log(Log::CRIT, "m_szWLDisplaySocket NULL!"); throwError("m_szWLDisplaySocket was null! (wl_display_add_socket and wl_display_add_socket_auto failed)"); } @@ -440,7 +443,7 @@ void CCompositor::initServer(std::string socketName, int socketFd) { void CCompositor::initAllSignals() { m_aqBackend->events.newOutput.listenStatic([this](const SP& output) { - Debug::log(LOG, "New aquamarine output with name {}", output->name); + Log::logger->log(Log::DEBUG, "New aquamarine output with name {}", output->name); if (m_initialized) onNewMonitor(output); else @@ -448,54 +451,59 @@ void CCompositor::initAllSignals() { }); m_aqBackend->events.newPointer.listenStatic([](const SP& dev) { - Debug::log(LOG, "New aquamarine pointer with name {}", dev->getName()); + Log::logger->log(Log::DEBUG, "New aquamarine pointer with name {}", dev->getName()); g_pInputManager->newMouse(dev); g_pInputManager->updateCapabilities(); }); m_aqBackend->events.newKeyboard.listenStatic([](const SP& dev) { - Debug::log(LOG, "New aquamarine keyboard with name {}", dev->getName()); + Log::logger->log(Log::DEBUG, "New aquamarine keyboard with name {}", dev->getName()); g_pInputManager->newKeyboard(dev); g_pInputManager->updateCapabilities(); }); m_aqBackend->events.newTouch.listenStatic([](const SP& dev) { - Debug::log(LOG, "New aquamarine touch with name {}", dev->getName()); + Log::logger->log(Log::DEBUG, "New aquamarine touch with name {}", dev->getName()); g_pInputManager->newTouchDevice(dev); g_pInputManager->updateCapabilities(); }); m_aqBackend->events.newSwitch.listenStatic([](const SP& dev) { - Debug::log(LOG, "New aquamarine switch with name {}", dev->getName()); + Log::logger->log(Log::DEBUG, "New aquamarine switch with name {}", dev->getName()); g_pInputManager->newSwitch(dev); }); m_aqBackend->events.newTablet.listenStatic([](const SP& dev) { - Debug::log(LOG, "New aquamarine tablet with name {}", dev->getName()); + Log::logger->log(Log::DEBUG, "New aquamarine tablet with name {}", dev->getName()); g_pInputManager->newTablet(dev); }); m_aqBackend->events.newTabletPad.listenStatic([](const SP& dev) { - Debug::log(LOG, "New aquamarine tablet pad with name {}", dev->getName()); + Log::logger->log(Log::DEBUG, "New aquamarine tablet pad with name {}", dev->getName()); g_pInputManager->newTabletPad(dev); }); if (m_aqBackend->hasSession()) { m_aqBackend->session->events.changeActive.listenStatic([this] { if (m_aqBackend->session->active) { - Debug::log(LOG, "Session got activated!"); + Log::logger->log(Log::DEBUG, "Session got activated!"); 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); + auto cpy = m->m_activeMonitorRule; + m->applyMonitorRule(std::move(cpy), true); } - g_pConfigManager->m_wantsMonitorReload = true; + Config::monitorRuleMgr()->scheduleReload(); g_pCursorManager->syncGsettings(); } else { - Debug::log(LOG, "Session got deactivated!"); + Log::logger->log(Log::DEBUG, "Session got deactivated!"); m_sessionActive = false; } @@ -519,7 +527,7 @@ void CCompositor::cleanEnvironment() { if (m_desktopEnvSet) unsetenv("XDG_CURRENT_DESKTOP"); - if (m_aqBackend->hasSession() && !envEnabled("HYPRLAND_NO_SD_VARS")) { + if (m_aqBackend->hasSession() && !Env::envEnabled("HYPRLAND_NO_SD_VARS")) { const auto CMD = #ifdef USES_SYSTEMD "systemctl --user unset-environment DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP QT_QPA_PLATFORMTHEME PATH XDG_DATA_DIRS && hash " @@ -531,7 +539,7 @@ void CCompositor::cleanEnvironment() { } void CCompositor::stopCompositor() { - Debug::log(LOG, "Hyprland is stopping!"); + Log::logger->log(Log::DEBUG, "Hyprland is stopping!"); // this stops the wayland loop, wl_display_run wl_display_terminate(m_wlDisplay); @@ -542,16 +550,18 @@ void CCompositor::cleanup() { if (!m_wlDisplay) return; + if (m_watchdogWriteFd.isValid()) [[maybe_unused]] + auto w = write(m_watchdogWriteFd.get(), "end", 3); + signal(SIGABRT, SIG_DFL); signal(SIGSEGV, SIG_DFL); removeLockFile(); - m_isShuttingDown = true; - Debug::m_shuttingDown = true; + m_isShuttingDown = true; #ifdef USES_SYSTEMD - if (NSystemd::sdBooted() > 0 && !envEnabled("HYPRLAND_NO_SD_NOTIFY")) + if (NSystemd::sdBooted() > 0 && !Env::envEnabled("HYPRLAND_NO_SD_NOTIFY")) NSystemd::sdNotify(0, "STOPPING=1"); #endif @@ -566,9 +576,6 @@ void CCompositor::cleanup() { for (auto const& m : m_monitors) { g_pHyprOpenGL->destroyMonitorResources(m); - - m->m_output->state->setEnabled(false); - m->m_state.commit(); } g_pXWayland.reset(); @@ -583,19 +590,18 @@ void CCompositor::cleanup() { g_pDecorationPositioner.reset(); g_pCursorManager.reset(); g_pPluginSystem.reset(); - g_pHyprNotificationOverlay.reset(); - g_pDebugOverlay.reset(); + Notification::overlay().reset(); + Debug::overlay().reset(); g_pEventManager.reset(); g_pSessionLockManager.reset(); - g_pProtocolManager.reset(); g_pHyprRenderer.reset(); + g_pProtocolManager.reset(); g_pHyprOpenGL.reset(); - g_pConfigManager.reset(); - g_pLayoutManager.reset(); - g_pHyprError.reset(); - g_pConfigManager.reset(); + Render::g_pShaderLoader.reset(); + Config::mgr().reset(); + g_layoutManager.reset(); + ErrorOverlay::overlay().reset(); g_pKeybindManager.reset(); - g_pHookSystem.reset(); g_pXWaylandManager.reset(); g_pPointerManager.reset(); g_pSeatManager.reset(); @@ -603,8 +609,9 @@ void CCompositor::cleanup() { g_pEventLoopManager.reset(); g_pVersionKeeperMgr.reset(); g_pDonationNagManager.reset(); + g_pWelcomeManager.reset(); g_pANRManager.reset(); - g_pConfigWatcher.reset(); + Config::watcher().reset(); g_pAsyncResourceGatherer.reset(); if (m_aqBackend) @@ -615,103 +622,107 @@ void CCompositor::cleanup() { // this frees all wayland resources, including sockets wl_display_destroy(m_wlDisplay); - - Debug::close(); } void CCompositor::initManagers(eManagersInitStage stage) { switch (stage) { case STAGE_PRIORITY: { - Debug::log(LOG, "Creating the EventLoopManager!"); + Log::logger->log(Log::DEBUG, "Creating the EventLoopManager!"); g_pEventLoopManager = makeUnique(m_wlDisplay, m_wlEventLoop); - Debug::log(LOG, "Creating the HookSystem!"); - g_pHookSystem = makeUnique(); - - Debug::log(LOG, "Creating the KeybindManager!"); + Log::logger->log(Log::DEBUG, "Creating the KeybindManager!"); g_pKeybindManager = makeUnique(); - Debug::log(LOG, "Creating the AnimationManager!"); + Log::logger->log(Log::DEBUG, "Creating the AnimationManager!"); g_pAnimationManager = makeUnique(); - Debug::log(LOG, "Creating the DynamicPermissionManager!"); + Log::logger->log(Log::DEBUG, "Creating the DynamicPermissionManager!"); g_pDynamicPermissionManager = makeUnique(); - Debug::log(LOG, "Creating the ConfigManager!"); - g_pConfigManager = makeUnique(); + Log::logger->log(Log::DEBUG, "Creating the ConfigManager!"); + if (!Config::initConfigManager()) + exit(1); - Debug::log(LOG, "Creating the CHyprError!"); - g_pHyprError = makeUnique(); + Log::logger->log(Log::DEBUG, "Creating the Error Overlay!"); + ErrorOverlay::overlay(); - Debug::log(LOG, "Creating the LayoutManager!"); - g_pLayoutManager = makeUnique(); + Log::logger->log(Log::DEBUG, "Creating the LayoutManager!"); + g_layoutManager = makeUnique(); - Debug::log(LOG, "Creating the TokenManager!"); + Log::logger->log(Log::DEBUG, "Creating the TokenManager!"); g_pTokenManager = makeUnique(); - g_pConfigManager->init(); + Config::mgr()->init(); - Debug::log(LOG, "Creating the PointerManager!"); + Log::logger->log(Log::DEBUG, "Creating the PointerManager!"); g_pPointerManager = makeUnique(); - Debug::log(LOG, "Creating the EventManager!"); + Log::logger->log(Log::DEBUG, "Creating the EventManager!"); g_pEventManager = makeUnique(); - Debug::log(LOG, "Creating the AsyncResourceGatherer!"); + Log::logger->log(Log::DEBUG, "Creating the AsyncResourceGatherer!"); g_pAsyncResourceGatherer = makeUnique(); } break; case STAGE_BASICINIT: { - Debug::log(LOG, "Creating the CHyprOpenGLImpl!"); + Log::logger->log(Log::DEBUG, "Creating the CHyprOpenGLImpl!"); g_pHyprOpenGL = makeUnique(); - Debug::log(LOG, "Creating the ProtocolManager!"); + Log::logger->log(Log::DEBUG, "Creating the HyprRenderer!"); + g_pHyprRenderer = makeUnique(); + + Log::logger->log(Log::DEBUG, "Creating the ProtocolManager!"); g_pProtocolManager = makeUnique(); - Debug::log(LOG, "Creating the SeatManager!"); + Log::logger->log(Log::DEBUG, "Creating the SeatManager!"); g_pSeatManager = makeUnique(); + + // init focus state els + Desktop::History::windowTracker(); + Desktop::History::workspaceTracker(); + } break; case STAGE_LATE: { - Debug::log(LOG, "Creating CHyprCtl"); + Log::logger->log(Log::DEBUG, "Creating CHyprCtl"); g_pHyprCtl = makeUnique(); - Debug::log(LOG, "Creating the InputManager!"); + Log::logger->log(Log::DEBUG, "Creating the InputManager!"); g_pInputManager = makeUnique(); - Debug::log(LOG, "Creating the HyprRenderer!"); - g_pHyprRenderer = makeUnique(); - - Debug::log(LOG, "Creating the XWaylandManager!"); + Log::logger->log(Log::DEBUG, "Creating the XWaylandManager!"); g_pXWaylandManager = makeUnique(); - Debug::log(LOG, "Creating the SessionLockManager!"); + Log::logger->log(Log::DEBUG, "Creating the SessionLockManager!"); g_pSessionLockManager = makeUnique(); - Debug::log(LOG, "Creating the HyprDebugOverlay!"); - g_pDebugOverlay = makeUnique(); + Log::logger->log(Log::DEBUG, "Creating the Debug Overlay!"); + Debug::overlay(); - Debug::log(LOG, "Creating the HyprNotificationOverlay!"); - g_pHyprNotificationOverlay = makeUnique(); + Log::logger->log(Log::DEBUG, "Creating the NotificationOverlay!"); + Notification::overlay(); - Debug::log(LOG, "Creating the PluginSystem!"); + Log::logger->log(Log::DEBUG, "Creating the PluginSystem!"); g_pPluginSystem = makeUnique(); - g_pConfigManager->handlePluginLoads(); + Config::mgr()->handlePluginLoads(); - Debug::log(LOG, "Creating the DecorationPositioner!"); + Log::logger->log(Log::DEBUG, "Creating the DecorationPositioner!"); g_pDecorationPositioner = makeUnique(); - Debug::log(LOG, "Creating the CursorManager!"); + Log::logger->log(Log::DEBUG, "Creating the CursorManager!"); g_pCursorManager = makeUnique(); - Debug::log(LOG, "Creating the VersionKeeper!"); + Log::logger->log(Log::DEBUG, "Creating the VersionKeeper!"); g_pVersionKeeperMgr = makeUnique(); - Debug::log(LOG, "Creating the DonationNag!"); + Log::logger->log(Log::DEBUG, "Creating the DonationNag!"); g_pDonationNagManager = makeUnique(); - Debug::log(LOG, "Creating the ANRManager!"); + Log::logger->log(Log::DEBUG, "Creating the WelcomeManager!"); + g_pWelcomeManager = makeUnique(); + + Log::logger->log(Log::DEBUG, "Creating the ANRManager!"); g_pANRManager = makeUnique(); - Debug::log(LOG, "Starting XWayland"); + Log::logger->log(Log::DEBUG, "Starting XWayland"); g_pXWayland = makeUnique(g_pCompositor->m_wantsXwayland); } break; default: UNREACHABLE(); @@ -746,7 +757,7 @@ void CCompositor::prepareFallbackOutput() { } if (!headless) { - Debug::log(WARN, "No headless in prepareFallbackOutput?!"); + Log::logger->log(Log::WARN, "No headless in prepareFallbackOutput?!"); return; } @@ -763,7 +774,7 @@ void CCompositor::startCompositor() { /* Session-less Hyprland usually means a nest, don't update the env in that case */ m_aqBackend->hasSession() && /* Activation environment management is not disabled */ - !envEnabled("HYPRLAND_NO_SD_VARS")) { + !Env::envEnabled("HYPRLAND_NO_SD_VARS")) { const auto CMD = #ifdef USES_SYSTEMD "systemctl --user import-environment DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP QT_QPA_PLATFORMTHEME PATH XDG_DATA_DIRS && hash " @@ -773,7 +784,7 @@ void CCompositor::startCompositor() { CKeybindManager::spawn(CMD); } - Debug::log(LOG, "Running on WAYLAND_DISPLAY: {}", m_wlDisplaySocket); + Log::logger->log(Log::DEBUG, "Running on WAYLAND_DISPLAY: {}", m_wlDisplaySocket); prepareFallbackOutput(); @@ -782,18 +793,23 @@ void CCompositor::startCompositor() { #ifdef USES_SYSTEMD if (NSystemd::sdBooted() > 0) { // tell systemd that we are ready so it can start other bond, following, related units - if (!envEnabled("HYPRLAND_NO_SD_NOTIFY")) + if (!Env::envEnabled("HYPRLAND_NO_SD_NOTIFY")) NSystemd::sdNotify(0, "READY=1"); } else - Debug::log(LOG, "systemd integration is baked in but system itself is not booted à la systemd!"); + Log::logger->log(Log::DEBUG, "systemd integration is baked in but system itself is not booted à la systemd!"); #endif createLockFile(); - EMIT_HOOK_EVENT("ready", nullptr); + Event::bus()->m_events.ready.emit(); + + if (m_watchdogWriteFd.isValid()) { + if (write(m_watchdogWriteFd.get(), "vax", 3) < 0) + Log::logger->log(Log::ERR, "startCompositor: failed to write to watchdogWriteFd {}: {}", m_watchdogWriteFd.get(), strerror(errno)); + } // This blocks until we are done. - Debug::log(LOG, "Hyprland is ready, running the event loop!"); + Log::logger->log(Log::DEBUG, "Hyprland is ready, running the event loop!"); g_pEventLoopManager->enterLoop(); } @@ -830,7 +846,7 @@ PHLMONITOR CCompositor::getMonitorFromCursor() { PHLMONITOR CCompositor::getMonitorFromVector(const Vector2D& point) { if (m_monitors.empty()) { - Debug::log(WARN, "getMonitorFromVector called with empty monitor list"); + Log::logger->log(Log::WARN, "getMonitorFromVector called with empty monitor list"); return nullptr; } @@ -856,7 +872,7 @@ PHLMONITOR CCompositor::getMonitorFromVector(const Vector2D& point) { } if (!pBestMon) { // ????? - Debug::log(WARN, "getMonitorFromVector no close mon???"); + Log::logger->log(Log::WARN, "getMonitorFromVector no close mon???"); return m_monitors.front(); } @@ -868,9 +884,9 @@ PHLMONITOR CCompositor::getMonitorFromVector(const Vector2D& point) { void CCompositor::removeWindowFromVectorSafe(PHLWINDOW pWindow) { if (!pWindow->m_fadingOut) { - EMIT_HOOK_EVENT("destroyWindow", pWindow); + Event::bus()->m_events.window.destroy.emit(pWindow); - std::erase_if(m_windows, [&](SP& el) { return el == pWindow; }); + std::erase_if(m_windows, [&](SP& el) { return el == pWindow; }); std::erase_if(m_windowsFadingOut, [&](PHLWINDOWREF el) { return el.lock() == pWindow; }); } } @@ -879,22 +895,29 @@ bool CCompositor::monitorExists(PHLMONITOR pMonitor) { return std::ranges::any_of(m_realMonitors, [&](const PHLMONITOR& m) { return m == pMonitor; }); } -PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t properties, PHLWINDOW pIgnoreWindow) { - const auto PMONITOR = getMonitorFromVector(pos); +PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint16_t properties, PHLWINDOW pIgnoreWindow) { + const auto PMONITOR = getMonitorFromVector(pos); + if (!PMONITOR) + return nullptr; + static auto PRESIZEONBORDER = CConfigValue("general:resize_on_border"); static auto PBORDERSIZE = CConfigValue("general:border_size"); static auto PBORDERGRABEXTEND = CConfigValue("general:extend_border_grab_area"); static auto PSPECIALFALLTHRU = CConfigValue("input:special_fallthrough"); static auto PMODALPARENTBLOCKING = CConfigValue("general:modal_parent_blocking"); + static auto PFOLLOWMOUSESHRINK = CConfigValue("input:follow_mouse_shrink"); const auto BORDER_GRAB_AREA = *PRESIZEONBORDER ? *PBORDERSIZE + *PBORDERGRABEXTEND : 0; - const bool ONLY_PRIORITY = properties & FOCUS_PRIORITY; + const bool ONLY_PRIORITY = properties & Desktop::View::FOCUS_PRIORITY; + const bool FOLLOW_MOUSE_CHECK = properties & Desktop::View::FOLLOW_MOUSE_CHECK; + const auto HITBOX_SHRINK = FOLLOW_MOUSE_CHECK ? *PFOLLOWMOUSESHRINK : 0; + const auto LASTFOCUSED = Desktop::focusState()->window(); const auto isShadowedByModal = [](PHLWINDOW w) -> bool { return *PMODALPARENTBLOCKING && w->m_xdgSurface && w->m_xdgSurface->m_toplevel && w->m_xdgSurface->m_toplevel->anyChildModal(); }; // pinned windows on top of floating regardless - if (properties & ALLOW_FLOATING) { + if (properties & Desktop::View::ALLOW_FLOATING) { for (auto const& w : m_windows | std::views::reverse) { if (ONLY_PRIORITY && !w->priorityFocus()) continue; @@ -903,7 +926,9 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper w != pIgnoreWindow && !isShadowedByModal(w)) { const auto BB = w->getWindowBoxUnified(properties); CBox box = BB.copy().expand(!w->isX11OverrideRedirect() ? BORDER_GRAB_AREA : 0); - if (box.containsPoint(g_pPointerManager->position())) + if (HITBOX_SHRINK > 0 && w != LASTFOCUSED) + box = box.copy().expand(-HITBOX_SHRINK); + if (box.containsPoint(pos)) return w; if (!w->m_isX11) { @@ -945,7 +970,9 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper const auto BB = w->getWindowBoxUnified(properties); CBox box = BB.copy().expand(!w->isX11OverrideRedirect() ? BORDER_GRAB_AREA : 0); - if (box.containsPoint(g_pPointerManager->position())) { + if (HITBOX_SHRINK > 0 && w != LASTFOCUSED) + box = box.copy().expand(-HITBOX_SHRINK); + if (box.containsPoint(pos)) { if (w->m_isX11 && w->isX11OverrideRedirect() && !w->m_xwaylandSurface->wantsFocus()) { // Override Redirect @@ -966,21 +993,31 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper return nullptr; }; - if (properties & ALLOW_FLOATING) { + if (properties & Desktop::View::ALLOW_FLOATING) { // first loop over floating cuz they're above, m_lWindows should be sorted bottom->top, for tiled it doesn't matter. auto found = floating(true); if (found) return found; } - if (properties & FLOATING_ONLY) + if (properties & Desktop::View::FLOATING_ONLY) return floating(false); const WORKSPACEID WSPID = special ? PMONITOR->activeSpecialWorkspaceID() : PMONITOR->activeWorkspaceID(); const auto PWORKSPACE = getWorkspaceByID(WSPID); - if (PWORKSPACE->m_hasFullscreenWindow && !(properties & SKIP_FULLSCREEN_PRIORITY) && !ONLY_PRIORITY) - return PWORKSPACE->getFullscreenWindow(); + if (PWORKSPACE->m_hasFullscreenWindow && !(properties & Desktop::View::SKIP_FULLSCREEN_PRIORITY) && !ONLY_PRIORITY) { + const auto FS_WINDOW = PWORKSPACE->getFullscreenWindow(); + + if (!FS_WINDOW) + return nullptr; + + // for maximized windows, don't return a window if we are not directly on it. + if (FS_WINDOW->m_fullscreenState.internal != FSMODE_MAXIMIZED || FS_WINDOW->getWindowBoxUnified(properties).containsPoint(pos)) + return PWORKSPACE->getFullscreenWindow(); + else + return nullptr; + } auto found = floating(false); if (found) @@ -1016,7 +1053,58 @@ 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 & USE_PROP_TILED) ? w->getWindowBoxUnified(properties) : CBox{w->m_position, w->m_size}; + 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()) { + const auto WORKAREA = PWORKSPACE->m_space->workArea(); + static auto isWindowCloseToWorkAreaEdge = [&](const Math::eDirection dir) -> bool { + constexpr double STICK_THRESHOLD = 2.0; // This constant is taken from isAdjacent in CCompositor::getWindowInDirection + double aEdge = -1; + double bEdge = -1; + + switch (dir) { + case Math::DIRECTION_LEFT: + aEdge = WORKAREA.x; + bEdge = box.x; + break; + case Math::DIRECTION_RIGHT: + aEdge = WORKAREA.x + WORKAREA.width; + bEdge = box.x + box.width; + break; + case Math::DIRECTION_UP: + aEdge = WORKAREA.y; + bEdge = box.y; + break; + case Math::DIRECTION_DOWN: + aEdge = WORKAREA.y + WORKAREA.height; + bEdge = box.y + box.height; + break; + default: break; + } + const double delta = aEdge - bEdge; + if (std::abs(delta) < STICK_THRESHOLD) + return true; + else + return false; + }; + + if (isWindowCloseToWorkAreaEdge(Math::eDirection::DIRECTION_LEFT)) { + box.x -= BORDER_GRAB_AREA; + box.width += BORDER_GRAB_AREA; + } + + if (isWindowCloseToWorkAreaEdge(Math::eDirection::DIRECTION_RIGHT)) + box.width += BORDER_GRAB_AREA; + + if (isWindowCloseToWorkAreaEdge(Math::eDirection::DIRECTION_UP)) { + box.y -= BORDER_GRAB_AREA; + box.height += BORDER_GRAB_AREA; + } + + if (isWindowCloseToWorkAreaEdge(Math::eDirection::DIRECTION_DOWN)) + box.height += BORDER_GRAB_AREA; + } + if (HITBOX_SHRINK > 0 && w != LASTFOCUSED) + box = box.copy().expand(-HITBOX_SHRINK); if (box.containsPoint(pos)) return w; } @@ -1052,10 +1140,10 @@ SP CCompositor::vectorWindowToSurface(const Vector2D& pos, P if (PPOPUP) { const auto OFF = PPOPUP->coordsRelativeToParent(); sl = pos - pWindow->m_realPosition->goal() - OFF; - return PPOPUP->m_wlSurface->resource(); + return PPOPUP->wlSurface()->resource(); } - auto [surf, local] = pWindow->m_wlSurface->resource()->at(pos - pWindow->m_realPosition->goal(), true); + auto [surf, local] = pWindow->wlSurface()->resource()->at(pos - pWindow->m_realPosition->goal(), true); if (surf) { sl = local; return surf; @@ -1077,7 +1165,7 @@ Vector2D CCompositor::vectorToSurfaceLocal(const Vector2D& vec, PHLWINDOW pWindo std::tuple, Vector2D> iterData = {pSurface, {-1337, -1337}}; - pWindow->m_wlSurface->resource()->breadthfirst( + pWindow->wlSurface()->resource()->breadthfirst( [](SP surf, const Vector2D& offset, void* data) { const auto PDATA = sc, Vector2D>*>(data); if (surf == std::get<0>(*PDATA)) @@ -1116,7 +1204,7 @@ PHLMONITOR CCompositor::getRealMonitorFromOutput(SP out) { SP CCompositor::vectorToLayerPopupSurface(const Vector2D& pos, PHLMONITOR monitor, Vector2D* sCoords, PHLLS* ppLayerSurfaceFound) { for (auto const& lsl : monitor->m_layerSurfaceLayers | std::views::reverse) { for (auto const& ls : lsl | std::views::reverse) { - if (!ls->m_mapped || ls->m_fadingOut || !ls->m_layerSurface || (ls->m_layerSurface && !ls->m_layerSurface->m_mapped) || ls->m_alpha->value() == 0.f) + if (!ls->aliveAndVisible()) continue; auto SURFACEAT = ls->m_popupHead->at(pos, true); @@ -1124,7 +1212,7 @@ SP CCompositor::vectorToLayerPopupSurface(const Vector2D& po if (SURFACEAT) { *ppLayerSurfaceFound = ls.lock(); *sCoords = pos - SURFACEAT->coordsGlobal(); - return SURFACEAT->m_wlSurface->resource(); + return SURFACEAT->wlSurface()->resource(); } } } @@ -1136,8 +1224,7 @@ SP CCompositor::vectorToLayerSurface(const Vector2D& pos, st bool aboveLockscreen) { for (auto const& ls : *layerSurfaces | std::views::reverse) { - if (!ls->m_mapped || ls->m_fadingOut || !ls->m_layerSurface || (ls->m_layerSurface && !ls->m_layerSurface->m_surface->m_mapped) || ls->m_alpha->value() == 0.f || - (aboveLockscreen && ls->m_ruleApplicator->aboveLock().valueOrDefault() != 2)) + if (!ls->aliveAndVisible() || (aboveLockscreen && ls->m_ruleApplicator->aboveLock().valueOrDefault() != 2)) continue; auto [surf, local] = ls->m_layerSurface->m_surface->at(pos - ls->m_geometry.pos(), true); @@ -1161,7 +1248,12 @@ PHLWINDOW CCompositor::getWindowFromSurface(SP pSurface) { if (!pSurface || !pSurface->m_hlSurface) return nullptr; - return pSurface->m_hlSurface->getWindow(); + const auto VIEW = pSurface->m_hlSurface->view(); + + if (!VIEW || VIEW->type() != Desktop::View::VIEW_TYPE_WINDOW) + return nullptr; + + return dynamicPointerCast(VIEW); } PHLWINDOW CCompositor::getWindowFromHandle(uint32_t handle) { @@ -1199,7 +1291,7 @@ bool CCompositor::isWindowActive(PHLWINDOW pWindow) { if (!pWindow->m_isMapped) return false; - const auto PSURFACE = pWindow->m_wlSurface->resource(); + const auto PSURFACE = pWindow->wlSurface()->resource(); return PSURFACE == Desktop::focusState()->surface() || pWindow == Desktop::focusState()->window(); } @@ -1210,6 +1302,8 @@ void CCompositor::changeWindowZOrder(PHLWINDOW pWindow, bool top) { if (top) pWindow->m_createdOverFullscreen = true; + else + pWindow->m_createdOverFullscreen = false; if (pWindow == (top ? m_windows.back() : m_windows.front())) return; @@ -1281,7 +1375,7 @@ void CCompositor::cleanupFadingOut(const MONITORID& monid) { w.reset(); - Debug::log(LOG, "Cleanup: destroyed a window"); + Log::logger->log(Log::DEBUG, "Cleanup: destroyed a window"); return; } } @@ -1301,8 +1395,11 @@ void CCompositor::cleanupFadingOut(const MONITORID& monid) { continue; // mark blur for recalc - if (ls->m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND || ls->m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM) - g_pHyprOpenGL->markBlurDirtyForMonitor(getMonitorFromID(monid)); + if (ls->m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND || ls->m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM) { + auto mon = getMonitorFromID(monid); + if (mon) + mon->m_blurFBDirty = true; + } if (ls->m_fadingOut && ls->m_readyToDelete && ls->isFadedOut()) { for (auto const& m : m_monitors) { @@ -1318,7 +1415,7 @@ void CCompositor::cleanupFadingOut(const MONITORID& monid) { ls.reset(); - Debug::log(LOG, "Cleanup: destroyed a layersurface"); + Log::logger->log(Log::DEBUG, "Cleanup: destroyed a layersurface"); return; } @@ -1350,8 +1447,8 @@ void CCompositor::addToFadingOutSafe(PHLWINDOW pWindow) { m_windowsFadingOut.emplace_back(pWindow); } -PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, char dir) { - if (!isDirection(dir)) +PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, Math::eDirection dir) { + if (dir == Math::DIRECTION_DEFAULT) return nullptr; const auto PMONITOR = pWindow->m_monitor.lock(); @@ -1362,11 +1459,14 @@ PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, char dir) { const auto WINDOWIDEALBB = pWindow->isFullscreen() ? CBox{PMONITOR->m_position, PMONITOR->m_size} : pWindow->getWindowIdealBoundingBoxIgnoreReserved(); const auto PWORKSPACE = pWindow->m_workspace; + if (!PWORKSPACE) + return nullptr; // ?? + return getWindowInDirection(WINDOWIDEALBB, PWORKSPACE, dir, pWindow, pWindow->m_isFloating); } -PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, char dir, PHLWINDOW ignoreWindow, bool useVectorAngles) { - if (!isDirection(dir)) +PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow, bool useVectorAngles) { + if (dir == Math::DIRECTION_DEFAULT) return nullptr; // 0 -> history, 1 -> shared length @@ -1380,6 +1480,35 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks PHLWINDOW leaderWindow = nullptr; if (!useVectorAngles) { + // helper to check if two rectangles are adjacent along an axis, considering slight overlaps. + // returns true if: STICKS (delta <= 2) OR rectangles overlap but no more than 50% of the smaller dimension. + static auto isAdjacent = [](const double aMin, const double aMax, const double bMin, const double bMax) -> bool { + constexpr double STICK_THRESHOLD = 2.0; + constexpr double MAX_OVERLAP_RATIO = 0.5; + + const double aEdge = aMin; + const double bEdge = bMax; + const double delta = aEdge - bEdge; + + // old STICKS check for 2px + if (std::abs(delta) < STICK_THRESHOLD) + return true; + + if (delta >= 0) + return false; + + const double overlap = -delta; + const double sizeA = aMax - aMin; + const double sizeB = bMax - bMin; + + // reject if one rectangle fully contains the other + if ((bMin <= aMin && bMax >= aMax) || (aMin <= bMin && aMax >= bMax)) + return false; + + // accept if overlap is at most 50% of the smaller dimension + return overlap <= std::min(sizeA, sizeB) * MAX_OVERLAP_RATIO; + }; + for (auto const& w : m_windows) { if (w == ignoreWindow || !w->m_workspace || !w->m_isMapped || w->isHidden() || (!w->isFullscreen() && w->m_isFloating) || !w->m_workspace->isVisible()) continue; @@ -1401,28 +1530,23 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks double intersectLength = -1; switch (dir) { - case 'l': - if (STICKS(POSA.x, POSB.x + SIZEB.x)) { + case Math::DIRECTION_LEFT: + if (isAdjacent(POSA.x, POSA.x + SIZEA.x, POSB.x, POSB.x + SIZEB.x)) intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); - } break; - case 'r': - if (STICKS(POSA.x + SIZEA.x, POSB.x)) { + case Math::DIRECTION_RIGHT: + if (isAdjacent(POSB.x, POSB.x + SIZEB.x, POSA.x, POSA.x + SIZEA.x)) intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); - } break; - case 't': - case 'u': - if (STICKS(POSA.y, POSB.y + SIZEB.y)) { + case Math::DIRECTION_UP: + if (isAdjacent(POSA.y, POSA.y + SIZEA.y, POSB.y, POSB.y + SIZEB.y)) intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); - } break; - case 'b': - case 'd': - if (STICKS(POSA.y + SIZEA.y, POSB.y)) { + case Math::DIRECTION_DOWN: + if (isAdjacent(POSB.y, POSB.y + SIZEB.y, POSA.y, POSA.y + SIZEA.y)) intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); - } break; + default: break; } if (*PMETHOD == 0 /* history */) { @@ -1430,16 +1554,14 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks // get idx int windowIDX = -1; - const auto& HISTORY = Desktop::focusState()->windowHistory(); - for (size_t i = 0; i < HISTORY.size(); ++i) { + const auto& HISTORY = Desktop::History::windowTracker()->fullHistory(); + for (int64_t i = HISTORY.size() - 1; i >= 0; --i) { if (HISTORY[i] == w) { windowIDX = i; break; } } - windowIDX = Desktop::focusState()->windowHistory().size() - windowIDX; - if (windowIDX > leaderValue) { leaderValue = windowIDX; leaderWindow = w; @@ -1453,12 +1575,8 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks } } } else { - if (dir == 'u') - dir = 't'; - if (dir == 'd') - dir = 'b'; - - static const std::unordered_map VECTORS = {{'r', {1, 0}}, {'t', {0, -1}}, {'b', {0, 1}}, {'l', {-1, 0}}}; + static const std::unordered_map VECTORS = { + {Math::DIRECTION_RIGHT, {1, 0}}, {Math::DIRECTION_UP, {0, -1}}, {Math::DIRECTION_DOWN, {0, 1}}, {Math::DIRECTION_LEFT, {-1, 0}}}; // auto vectorAngles = [](const Vector2D& a, const Vector2D& b) -> double { @@ -1543,10 +1661,9 @@ static PHLWINDOW getWeakWindowPred(Iterator cur, Iterator end, Iterator begin, c PHLWINDOW CCompositor::getWindowCycleHist(PHLWINDOWREF cur, bool focusableOnly, std::optional floating, bool visible, bool next) { const auto FINDER = [&](const PHLWINDOWREF& w) { return isWindowAvailableForCycle(cur, w, focusableOnly, floating, visible); }; // also m_vWindowFocusHistory has reverse order, so when it is next - we need to reverse again - return next ? getWeakWindowPred(std::ranges::find(Desktop::focusState()->windowHistory() | std::views::reverse, cur), Desktop::focusState()->windowHistory().rend(), - Desktop::focusState()->windowHistory().rbegin(), FINDER) : - getWeakWindowPred(std::ranges::find(Desktop::focusState()->windowHistory(), cur), Desktop::focusState()->windowHistory().end(), - Desktop::focusState()->windowHistory().begin(), FINDER); + const auto& HISTORY = Desktop::History::windowTracker()->fullHistory(); + return next ? getWeakWindowPred(std::ranges::find(HISTORY, cur), HISTORY.end(), HISTORY.begin(), FINDER) : + getWeakWindowPred(std::ranges::find(HISTORY | std::views::reverse, cur), HISTORY.rend(), HISTORY.rbegin(), FINDER); } PHLWINDOW CCompositor::getWindowCycle(PHLWINDOW cur, bool focusableOnly, std::optional floating, bool visible, bool prev) { @@ -1563,11 +1680,11 @@ WORKSPACEID CCompositor::getNextAvailableNamedWorkspace() { } // Give priority to persistent workspaces to avoid any conflicts between them. - for (auto const& rule : g_pConfigManager->getAllWorkspaceRules()) { - if (!rule.isPersistent) + for (auto const& rule : Config::workspaceRuleMgr()->getAllWorkspaceRules()) { + if (!rule.m_isPersistent) continue; - if (rule.workspaceId < -1 && rule.workspaceId < lowest) - lowest = rule.workspaceId; + if (rule.m_workspaceId < -1 && rule.m_workspaceId < lowest) + lowest = rule.m_workspaceId; } return lowest - 1; @@ -1589,7 +1706,7 @@ PHLWORKSPACE CCompositor::getWorkspaceByString(const std::string& str) { try { return getWorkspaceByID(getWorkspaceIDNameFromString(str).id); - } catch (std::exception& e) { Debug::log(ERR, "Error in getWorkspaceByString, invalid id"); } + } catch (std::exception& e) { Log::logger->log(Log::ERR, "Error in getWorkspaceByString, invalid id"); } return nullptr; } @@ -1602,48 +1719,37 @@ bool CCompositor::isPointOnAnyMonitor(const Vector2D& point) { bool CCompositor::isPointOnReservedArea(const Vector2D& point, const PHLMONITOR pMonitor) { const auto PMONITOR = pMonitor ? pMonitor : getMonitorFromVector(point); - const auto XY1 = PMONITOR->m_position + PMONITOR->m_reservedTopLeft; - const auto XY2 = PMONITOR->m_position + PMONITOR->m_size - PMONITOR->m_reservedBottomRight; + auto box = PMONITOR->logicalBox(); + if (VECNOTINRECT(point, box.x - 1, box.y - 1, box.x + box.w + 1, box.y + box.h + 1)) + return false; - return VECNOTINRECT(point, XY1.x, XY1.y, XY2.x, XY2.y); + PMONITOR->m_reservedArea.applyip(box); + + return VECNOTINRECT(point, box.x, box.y, box.x + box.w, box.y + box.h); } -CBox CCompositor::calculateX11WorkArea() { +std::optional CCompositor::calculateX11WorkArea() { static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); - CBox workbox = {0, 0, 0, 0}; - bool firstMonitor = true; + // We more than likely won't be able to calculate one + // and even if we could this is minor + if (m_monitors.size() > 1 || m_monitors.empty()) + return std::nullopt; - for (const auto& monitor : m_monitors) { - // we ignore monitor->m_position on purpose - auto x = monitor->m_reservedTopLeft.x; - auto y = monitor->m_reservedTopLeft.y; - auto w = monitor->m_size.x - monitor->m_reservedBottomRight.x - x; - auto h = monitor->m_size.y - monitor->m_reservedBottomRight.y - y; - CBox box = {x, y, w, h}; - if ((*PXWLFORCESCALEZERO)) - box.scale(monitor->m_scale); + const auto M = m_monitors.front(); - if (firstMonitor) { - firstMonitor = false; - workbox = box; - } else { - // if this monitor creates a different workbox than previous monitor, we remove the _NET_WORKAREA property all together - if ((std::abs(box.x - workbox.x) > 3) || (std::abs(box.y - workbox.y) > 3) || (std::abs(box.w - workbox.w) > 3) || (std::abs(box.h - workbox.h) > 3)) { - workbox = {0, 0, 0, 0}; - break; - } - } - } + // we ignore monitor->m_position on purpose + CBox box = M->logicalBoxMinusReserved().translate(-M->m_position); + if ((*PXWLFORCESCALEZERO)) + box.scale(M->m_scale); - // returning 0, 0 will remove the _NET_WORKAREA property - return workbox; + return box.translate(M->m_xwaylandPosition); } -PHLMONITOR CCompositor::getMonitorInDirection(const char& dir) { +PHLMONITOR CCompositor::getMonitorInDirection(Math::eDirection dir) { return getMonitorInDirection(Desktop::focusState()->monitor(), dir); } -PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const char& dir) { +PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::eDirection dir) { if (!pSourceMonitor) return nullptr; @@ -1660,7 +1766,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c const auto POSB = m->m_position; const auto SIZEB = m->m_size; switch (dir) { - case 'l': + case Math::DIRECTION_LEFT: if (STICKS(POSA.x, POSB.x + SIZEB.x)) { const auto INTERSECTLEN = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); if (INTERSECTLEN > longestIntersect) { @@ -1669,7 +1775,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c } } break; - case 'r': + case Math::DIRECTION_RIGHT: if (STICKS(POSA.x + SIZEA.x, POSB.x)) { const auto INTERSECTLEN = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); if (INTERSECTLEN > longestIntersect) { @@ -1678,8 +1784,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c } } break; - case 't': - case 'u': + case Math::DIRECTION_UP: if (STICKS(POSA.y, POSB.y + SIZEB.y)) { const auto INTERSECTLEN = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); if (INTERSECTLEN > longestIntersect) { @@ -1688,8 +1793,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c } } break; - case 'b': - case 'd': + case Math::DIRECTION_DOWN: if (STICKS(POSA.y + SIZEA.y, POSB.y)) { const auto INTERSECTLEN = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); if (INTERSECTLEN > longestIntersect) { @@ -1698,6 +1802,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c } } break; + default: break; } } @@ -1753,7 +1858,7 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor // additionally, move floating and fs windows manually if (w->m_isFloating) - *w->m_realPosition = w->m_realPosition->goal() - pMonitorA->m_position + pMonitorB->m_position; + w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-pMonitorA->m_position + pMonitorB->m_position)); if (w->isFullscreen()) { *w->m_realPosition = pMonitorB->m_position; @@ -1778,7 +1883,7 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor // additionally, move floating and fs windows manually if (w->m_isFloating) - *w->m_realPosition = w->m_realPosition->goal() - pMonitorB->m_position + pMonitorA->m_position; + w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-pMonitorB->m_position + pMonitorA->m_position)); if (w->isFullscreen()) { *w->m_realPosition = pMonitorA->m_position; @@ -1792,11 +1897,11 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor pMonitorA->m_activeWorkspace = PWORKSPACEB; pMonitorB->m_activeWorkspace = PWORKSPACEA; - PWORKSPACEA->rememberPrevWorkspace(PWORKSPACEB); - PWORKSPACEB->rememberPrevWorkspace(PWORKSPACEA); + g_layoutManager->recalculateMonitor(pMonitorA); + g_layoutManager->recalculateMonitor(pMonitorB); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitorA->m_id); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitorB->m_id); + g_pHyprRenderer->damageMonitor(pMonitorB); + g_pHyprRenderer->damageMonitor(pMonitorA); g_pDesktopAnimationManager->setFullscreenFadeAnimation( PWORKSPACEB, PWORKSPACEB->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT); @@ -1806,29 +1911,32 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor if (pMonitorA->m_id == Desktop::focusState()->monitor()->m_id || pMonitorB->m_id == Desktop::focusState()->monitor()->m_id) { const auto LASTWIN = pMonitorA->m_id == Desktop::focusState()->monitor()->m_id ? PWORKSPACEB->getLastFocusedWindow() : PWORKSPACEA->getLastFocusedWindow(); Desktop::focusState()->fullWindowFocus( - LASTWIN ? LASTWIN : (g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING))); + 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); const auto PNEWWORKSPACE = pMonitorA->m_id == Desktop::focusState()->monitor()->m_id ? PWORKSPACEB : PWORKSPACEA; g_pEventManager->postEvent(SHyprIPCEvent{.event = "workspace", .data = PNEWWORKSPACE->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "workspacev2", .data = std::format("{},{}", PNEWWORKSPACE->m_id, PNEWWORKSPACE->m_name)}); - EMIT_HOOK_EVENT("workspace", PNEWWORKSPACE); + Event::bus()->m_events.workspace.active.emit(PNEWWORKSPACE); } - // event + // events g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspace", .data = PWORKSPACEA->m_name + "," + pMonitorB->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspacev2", .data = std::format("{},{},{}", PWORKSPACEA->m_id, PWORKSPACEA->m_name, pMonitorB->m_name)}); - EMIT_HOOK_EVENT("moveWorkspace", (std::vector{PWORKSPACEA, pMonitorB})); g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspace", .data = PWORKSPACEB->m_name + "," + pMonitorA->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspacev2", .data = std::format("{},{},{}", PWORKSPACEB->m_id, PWORKSPACEB->m_name, pMonitorA->m_name)}); - EMIT_HOOK_EVENT("moveWorkspace", (std::vector{PWORKSPACEB, pMonitorA})); + Event::bus()->m_events.workspace.moveToMonitor.emit(PWORKSPACEA, pMonitorB); + Event::bus()->m_events.workspace.moveToMonitor.emit(PWORKSPACEB, pMonitorA); } PHLMONITOR CCompositor::getMonitorFromString(const std::string& name) { if (name == "current") return Desktop::focusState()->monitor(); else if (isDirection(name)) - return getMonitorInDirection(name[0]); + return getMonitorInDirection(Math::fromChar(name[0])); else if (name[0] == '+' || name[0] == '-') { // relative @@ -1838,7 +1946,7 @@ PHLMONITOR CCompositor::getMonitorFromString(const std::string& name) { const auto OFFSET = name[0] == '-' ? name : name.substr(1); if (!isNumber(OFFSET)) { - Debug::log(ERR, "Error in getMonitorFromString: Not a number in relative."); + Log::logger->log(Log::ERR, "Error in getMonitorFromString: Not a number in relative."); return nullptr; } @@ -1862,7 +1970,7 @@ PHLMONITOR CCompositor::getMonitorFromString(const std::string& name) { } if (currentPlace != std::clamp(currentPlace, 0, sc(m_monitors.size()) - 1)) { - Debug::log(WARN, "Error in getMonitorFromString: Vaxry's code sucks."); + Log::logger->log(Log::WARN, "Error in getMonitorFromString: Vaxry's code sucks."); currentPlace = std::clamp(currentPlace, 0, sc(m_monitors.size()) - 1); } @@ -1874,14 +1982,14 @@ PHLMONITOR CCompositor::getMonitorFromString(const std::string& name) { monID = std::stoi(name); } catch (std::exception& e) { // shouldn't happen but jic - Debug::log(ERR, "Error in getMonitorFromString: invalid num"); + Log::logger->log(Log::ERR, "Error in getMonitorFromString: invalid num"); return nullptr; } if (monID > -1 && monID < sc(m_monitors.size())) { return getMonitorFromID(monID); } else { - Debug::log(ERR, "Error in getMonitorFromString: invalid arg 1"); + Log::logger->log(Log::ERR, "Error in getMonitorFromString: invalid arg 1"); return nullptr; } } else { @@ -1907,7 +2015,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo if (pWorkspace->m_monitor == pMonitor) return; - Debug::log(LOG, "moveWorkspaceToMonitor: Moving {} to monitor {}", pWorkspace->m_id, pMonitor->m_id); + Log::logger->log(Log::DEBUG, "moveWorkspaceToMonitor: Moving {} to monitor {}", pWorkspace->m_id, pMonitor->m_id); const auto POLDMON = pWorkspace->m_monitor.lock(); @@ -1931,24 +2039,25 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo nextWorkspaceOnMonitorID = 1; while (getWorkspaceByID(nextWorkspaceOnMonitorID) || [&]() -> bool { - const auto B = g_pConfigManager->getBoundMonitorForWS(std::to_string(nextWorkspaceOnMonitorID)); + const auto B = Config::workspaceRuleMgr()->getBoundMonitorForWS(std::to_string(nextWorkspaceOnMonitorID)); return B && B != POLDMON; }()) nextWorkspaceOnMonitorID++; - Debug::log(LOG, "moveWorkspaceToMonitor: Plugging gap with new {}", nextWorkspaceOnMonitorID); + Log::logger->log(Log::DEBUG, "moveWorkspaceToMonitor: Plugging gap with new {}", nextWorkspaceOnMonitorID); if (POLDMON) newWorkspace = g_pCompositor->createNewWorkspace(nextWorkspaceOnMonitorID, POLDMON->m_id); } - Debug::log(LOG, "moveWorkspaceToMonitor: Plugging gap with existing {}", nextWorkspaceOnMonitorID); + Log::logger->log(Log::DEBUG, "moveWorkspaceToMonitor: Plugging gap with existing {}", nextWorkspaceOnMonitorID); if (POLDMON) POLDMON->changeWorkspace(nextWorkspaceOnMonitorID, false, true, true); } // move the workspace pWorkspace->m_monitor = pMonitor; + pWorkspace->m_space->recheckWorkArea(); pWorkspace->m_events.monitorChanged.emit(); for (auto const& w : m_windows) { @@ -1964,17 +2073,18 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo if (w->m_isMapped && !w->isHidden()) { if (POLDMON) { if (w->m_isFloating) - *w->m_realPosition = w->m_realPosition->goal() - POLDMON->m_position + pMonitor->m_position; + w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-POLDMON->m_position + pMonitor->m_position)); if (w->isFullscreen()) { *w->m_realPosition = pMonitor->m_position; *w->m_realSize = pMonitor->m_size; } } else - *w->m_realPosition = Vector2D{ - (pMonitor->m_size.x != 0) ? sc(w->m_realPosition->goal().x) % sc(pMonitor->m_size.x) : 0, - (pMonitor->m_size.y != 0) ? sc(w->m_realPosition->goal().y) % sc(pMonitor->m_size.y) : 0, - }; + w->layoutTarget()->setPositionGlobal(CBox{Vector2D{ + (pMonitor->m_size.x != 0) ? sc(w->m_realPosition->goal().x) % sc(pMonitor->m_size.x) : 0, + (pMonitor->m_size.y != 0) ? sc(w->m_realPosition->goal().y) % sc(pMonitor->m_size.y) : 0, + }, + w->layoutTarget()->position().size()}); } w->updateToplevel(); @@ -1982,7 +2092,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo } if (SWITCHINGISACTIVE && POLDMON == Desktop::focusState()->monitor()) { // if it was active, preserve its' status. If it wasn't, don't. - Debug::log(LOG, "moveWorkspaceToMonitor: SWITCHINGISACTIVE, active {} -> {}", pMonitor->activeWorkspaceID(), pWorkspace->m_id); + Log::logger->log(Log::DEBUG, "moveWorkspaceToMonitor: SWITCHINGISACTIVE, active {} -> {}", pMonitor->activeWorkspaceID(), pWorkspace->m_id); if (valid(pMonitor->m_activeWorkspace)) { pMonitor->m_activeWorkspace->m_visible = false; @@ -2002,7 +2112,8 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo pWorkspace->m_events.activeChanged.emit(); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitor->m_id); + g_layoutManager->recalculateMonitor(pMonitor); + g_pHyprRenderer->damageMonitor(pMonitor); g_pDesktopAnimationManager->startAnimation(pWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); pWorkspace->m_visible = true; @@ -2015,7 +2126,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo // finalize if (POLDMON) { - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(POLDMON->m_id); + g_layoutManager->recalculateMonitor(POLDMON); if (valid(POLDMON->m_activeWorkspace)) g_pDesktopAnimationManager->setFullscreenFadeAnimation(POLDMON->m_activeWorkspace, POLDMON->m_activeWorkspace->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : @@ -2030,7 +2141,8 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo // event g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspace", .data = pWorkspace->m_name + "," + pMonitor->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspacev2", .data = std::format("{},{},{}", pWorkspace->m_id, pWorkspace->m_name, pMonitor->m_name)}); - EMIT_HOOK_EVENT("moveWorkspace", (std::vector{pWorkspace, pMonitor})); + + Event::bus()->m_events.workspace.moveToMonitor.emit(pWorkspace, pMonitor); } bool CCompositor::workspaceIDOutOfBounds(const WORKSPACEID& id) { @@ -2056,19 +2168,19 @@ void CCompositor::changeWindowFullscreenModeClient(const PHLWINDOW PWINDOW, cons // TODO: move fs functions to Desktop:: void CCompositor::setWindowFullscreenInternal(const PHLWINDOW PWINDOW, const eFullscreenMode MODE) { if (PWINDOW->m_ruleApplicator->syncFullscreen().valueOrDefault()) - setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = MODE, .client = MODE}); + setWindowFullscreenState(PWINDOW, Desktop::View::SFullscreenState{.internal = MODE, .client = MODE}); else - setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = MODE, .client = PWINDOW->m_fullscreenState.client}); + setWindowFullscreenState(PWINDOW, Desktop::View::SFullscreenState{.internal = MODE, .client = PWINDOW->m_fullscreenState.client}); } void CCompositor::setWindowFullscreenClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE) { if (PWINDOW->m_ruleApplicator->syncFullscreen().valueOrDefault()) - setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = MODE, .client = MODE}); + setWindowFullscreenState(PWINDOW, Desktop::View::SFullscreenState{.internal = MODE, .client = MODE}); else - setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = PWINDOW->m_fullscreenState.internal, .client = MODE}); + setWindowFullscreenState(PWINDOW, Desktop::View::SFullscreenState{.internal = PWINDOW->m_fullscreenState.internal, .client = MODE}); } -void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, SFullscreenState state) { +void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, Desktop::View::SFullscreenState state) { static auto PDIRECTSCANOUT = CConfigValue("render:direct_scanout"); static auto PALLOWPINFULLSCREEN = CConfigValue("binds:allow_pin_fullscreen"); @@ -2078,13 +2190,10 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, SFullscreenS state.internal = std::clamp(state.internal, sc(0), FSMODE_MAX); state.client = std::clamp(state.client, sc(0), FSMODE_MAX); - const auto PMONITOR = PWINDOW->m_monitor.lock(); - const auto PWORKSPACE = PWINDOW->m_workspace; + const auto PMONITOR = PWINDOW->m_monitor.lock(); + const auto PWORKSPACE = PWINDOW->m_workspace; - const eFullscreenMode CURRENT_EFFECTIVE_MODE = sc(std::bit_floor(sc(PWINDOW->m_fullscreenState.internal))); - const eFullscreenMode EFFECTIVE_MODE = sc(std::bit_floor(sc(state.internal))); - - if (PWINDOW->m_isFloating && CURRENT_EFFECTIVE_MODE == FSMODE_NONE && EFFECTIVE_MODE != FSMODE_NONE) + if (PWINDOW->m_isFloating && PWINDOW->m_fullscreenState.internal == FSMODE_NONE && state.internal != FSMODE_NONE) g_pHyprRenderer->damageWindow(PWINDOW); if (*PALLOWPINFULLSCREEN && !PWINDOW->m_pinFullscreened && !PWINDOW->isFullscreen() && PWINDOW->m_pinned) { @@ -2095,7 +2204,7 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, SFullscreenS if (PWORKSPACE->m_hasFullscreenWindow && !PWINDOW->isFullscreen()) setWindowFullscreenInternal(PWORKSPACE->getFullscreenWindow(), FSMODE_NONE); - const bool CHANGEINTERNAL = !PWINDOW->m_pinned && CURRENT_EFFECTIVE_MODE != EFFECTIVE_MODE; + const bool CHANGEINTERNAL = !PWINDOW->m_pinned && PWINDOW->m_fullscreenState.internal != state.internal; if (*PALLOWPINFULLSCREEN && PWINDOW->m_pinFullscreened && PWINDOW->isFullscreen() && !PWINDOW->m_pinned && state.internal == FSMODE_NONE) { PWINDOW->m_pinned = true; @@ -2113,30 +2222,44 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, SFullscreenS PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE); PWINDOW->updateDecorationValues(); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID()); + g_layoutManager->recalculateMonitor(PMONITOR); return; } - g_pLayoutManager->getCurrentLayout()->fullscreenRequestForWindow(PWINDOW, CURRENT_EFFECTIVE_MODE, EFFECTIVE_MODE); + // "Effective mode" is the fullscreen mode according to which a window is rendered. + // For fullscreen modes `FSMODE_NONE` (0), `FSMODE_MAXIMIZED` (1), and `FSMODE_FULLSCREEN` (2), + // the effective mode is the same as the fullscreen mode; + // for fullscreen mode `FSMODE_MAXIMIZED|FSMODE_FULLSCREEN` (a window is maximized then fullscreened), + // the effective mode is `FSMODE_FULLSCREEN` (2), since the window is rendered as a fullscreen window. + // But when the latter window exists fullscreen, it will return to `FSMODE_MAXIMIZED`, rather than `FSMODE_NONE`. + const eFullscreenMode OLD_EFFECTIVE_MODE = sc(std::bit_floor(sc(PWINDOW->m_fullscreenState.internal))); + const eFullscreenMode NEW_EFFECTIVE_MODE = sc(std::bit_floor(sc(state.internal))); + + PWORKSPACE->m_fullscreenMode = NEW_EFFECTIVE_MODE; + PWORKSPACE->m_hasFullscreenWindow = NEW_EFFECTIVE_MODE != FSMODE_NONE; + + g_layoutManager->fullscreenRequestForTarget(PWINDOW->layoutTarget(), OLD_EFFECTIVE_MODE, NEW_EFFECTIVE_MODE); PWINDOW->m_fullscreenState.internal = state.internal; - PWORKSPACE->m_fullscreenMode = EFFECTIVE_MODE; - PWORKSPACE->m_hasFullscreenWindow = EFFECTIVE_MODE != FSMODE_NONE; - g_pEventManager->postEvent(SHyprIPCEvent{.event = "fullscreen", .data = std::to_string(sc(EFFECTIVE_MODE) != FSMODE_NONE)}); - EMIT_HOOK_EVENT("fullscreen", PWINDOW); + g_pEventManager->postEvent(SHyprIPCEvent{.event = "fullscreen", .data = std::to_string(sc(NEW_EFFECTIVE_MODE) != FSMODE_NONE)}); + Event::bus()->m_events.window.fullscreen.emit(PWINDOW); PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE); PWINDOW->updateDecorationValues(); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID()); + g_layoutManager->recalculateMonitor(PMONITOR); - // make all windows on the same workspace under the fullscreen window + // make all windows and layers on the same workspace under the fullscreen window for (auto const& w : m_windows) { if (w->m_workspace == PWORKSPACE && !w->isFullscreen() && !w->m_fadingOut && !w->m_pinned) w->m_createdOverFullscreen = false; } + for (auto const& ls : m_layers) { + if (ls->m_monitor == PMONITOR) + ls->m_aboveFullscreen = false; + } g_pDesktopAnimationManager->setFullscreenFadeAnimation( PWORKSPACE, PWORKSPACE->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT); @@ -2156,10 +2279,10 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, SFullscreenS if (*PDIRECTSCANOUT == 1 || (*PDIRECTSCANOUT == 2 && PWINDOW->getContentType() == CONTENT_TYPE_GAME)) { auto surf = PWINDOW->getSolitaryResource(); if (surf) - g_pHyprRenderer->setSurfaceScanoutMode(surf, EFFECTIVE_MODE != FSMODE_NONE ? PMONITOR->m_self.lock() : nullptr); + g_pHyprRenderer->setSurfaceScanoutMode(surf, NEW_EFFECTIVE_MODE != FSMODE_NONE ? PMONITOR->m_self.lock() : nullptr); } - g_pConfigManager->ensureVRR(PMONITOR); + Config::monitorRuleMgr()->ensureVRR(PMONITOR); } PHLWINDOW CCompositor::getX11Parent(PHLWINDOW pWindow) { @@ -2239,7 +2362,7 @@ PHLWINDOW CCompositor::getWindowByRegex(const std::string& regexp_) { } for (auto const& w : g_pCompositor->m_windows) { - if (!w->m_isMapped || (w->isHidden() && !g_pLayoutManager->getCurrentLayout()->isWindowReachable(w))) + if (!w->m_isMapped) continue; switch (mode) { @@ -2328,12 +2451,12 @@ PHLLS CCompositor::getLayerSurfaceFromSurface(SP pSurface) { std::pair, bool> result = {pSurface, false}; for (auto const& ls : m_layers) { - if (ls->m_layerSurface && ls->m_layerSurface->m_surface == pSurface) - return ls; - - if (!ls->m_layerSurface || !ls->m_mapped) + if (!ls->aliveAndVisible()) continue; + if (ls->m_layerSurface->m_surface == pSurface) + return ls; + ls->m_layerSurface->m_surface->breadthfirst( [&result](SP surf, const Vector2D& offset, void* data) { if (surf == result.first) { @@ -2355,15 +2478,15 @@ Vector2D CCompositor::parseWindowVectorArgsRelative(const std::string& args, con if (!args.contains(' ') && !args.contains('\t')) return relativeTo; - const auto PMONITOR = Desktop::focusState()->monitor(); + const auto PMONITOR = Desktop::focusState()->monitor(); - bool xIsPercent = false; - bool yIsPercent = false; - bool isExact = false; + bool xIsPercent = false; + bool yIsPercent = false; + bool isExact = false; - CVarList varList(args, 0, 's', true); - std::string x = varList[0]; - std::string y = varList[1]; + CVarList2 varList(std::string{args}, 0, 's', true); + auto x = varList[0]; + auto y = varList[1]; if (x == "exact") { x = varList[1]; @@ -2381,8 +2504,8 @@ Vector2D CCompositor::parseWindowVectorArgsRelative(const std::string& args, con y = y.substr(0, y.length() - 1); } - if (!isNumber(x) || !isNumber(y)) { - Debug::log(ERR, "parseWindowVectorArgsRelative: args not numbers"); + if (!isNumber2(x) || !isNumber2(y)) { + Log::logger->log(Log::ERR, "parseWindowVectorArgsRelative: args not numbers"); return relativeTo; } @@ -2390,11 +2513,11 @@ Vector2D CCompositor::parseWindowVectorArgsRelative(const std::string& args, con int Y = 0; if (isExact) { - X = xIsPercent ? std::stof(x) * 0.01 * PMONITOR->m_size.x : std::stoi(x); - Y = yIsPercent ? std::stof(y) * 0.01 * PMONITOR->m_size.y : std::stoi(y); + X = xIsPercent ? *strToNumber(x) * 0.01 * PMONITOR->m_size.x : *strToNumber(x); + Y = yIsPercent ? *strToNumber(y) * 0.01 * PMONITOR->m_size.y : *strToNumber(y); } else { - X = xIsPercent ? (std::stof(x) * 0.01 * relativeTo.x) + relativeTo.x : std::stoi(x) + relativeTo.x; - Y = yIsPercent ? (std::stof(y) * 0.01 * relativeTo.y) + relativeTo.y : std::stoi(y) + relativeTo.y; + X = xIsPercent ? (*strToNumber(x) * 0.01 * relativeTo.x) + relativeTo.x : *strToNumber(x) + relativeTo.x; + Y = yIsPercent ? (*strToNumber(y) * 0.01 * relativeTo.y) + relativeTo.y : *strToNumber(y) + relativeTo.y; } return Vector2D(X, Y); @@ -2405,14 +2528,14 @@ PHLWORKSPACE CCompositor::createNewWorkspace(const WORKSPACEID& id, const MONITO auto monID = monid; // check if bound - if (const auto PMONITOR = g_pConfigManager->getBoundMonitorForWS(NAME); PMONITOR) + if (const auto PMONITOR = Config::workspaceRuleMgr()->getBoundMonitorForWS(NAME); PMONITOR) monID = PMONITOR->m_id; const bool SPECIAL = id >= SPECIAL_WORKSPACE_START && id <= -2; const auto PMONITOR = getMonitorFromID(monID); if (!PMONITOR) { - Debug::log(ERR, "BUG THIS: No pMonitor for new workspace in createNewWorkspace"); + Log::logger->log(Log::ERR, "BUG THIS: No pMonitor for new workspace in createNewWorkspace"); return nullptr; } @@ -2455,26 +2578,75 @@ std::vector CCompositor::getWorkspacesCopy() { void CCompositor::performUserChecks() { static auto PNOCHECKXDG = CConfigValue("misc:disable_xdg_env_checks"); static auto PNOCHECKGUIUTILS = CConfigValue("misc:disable_hyprland_guiutils_check"); + static auto PNOWATCHDOG = CConfigValue("misc:disable_watchdog_warning"); if (!*PNOCHECKXDG) { const auto CURRENT_DESKTOP_ENV = getenv("XDG_CURRENT_DESKTOP"); if (!CURRENT_DESKTOP_ENV || std::string{CURRENT_DESKTOP_ENV} != "Hyprland") { - g_pHyprNotificationOverlay->addNotification( + Notification::overlay()->addNotification( I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, {{"value", CURRENT_DESKTOP_ENV ? CURRENT_DESKTOP_ENV : "unset"}}), CHyprColor{}, 15000, ICON_WARNING); } } if (!*PNOCHECKGUIUTILS) { - if (!NFsUtils::executableExistsInPath("hyprland-dialog")) { - g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_NO_GUIUTILS), CHyprColor{}, 15000, ICON_WARNING); - } + if (!NFsUtils::executableExistsInPath("hyprland-dialog")) + Notification::overlay()->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_NO_GUIUTILS), CHyprColor{}, 15000, ICON_WARNING); } - if (g_pHyprOpenGL->m_failedAssetsNo > 0) { - g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_FAILED_ASSETS, {{"count", std::to_string(g_pHyprOpenGL->m_failedAssetsNo)}}), - CHyprColor{1.0, 0.1, 0.1, 1.0}, 15000, ICON_ERROR); + if (g_pHyprRenderer->m_failedAssetsNo > 0) { + Notification::overlay()->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_FAILED_ASSETS, {{"count", std::to_string(g_pHyprRenderer->m_failedAssetsNo)}}), + CHyprColor{1.0, 0.1, 0.1, 1.0}, 15000, ICON_ERROR); } + + if (!m_watchdogWriteFd.isValid() && !*PNOWATCHDOG) + Notification::overlay()->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_NO_WATCHDOG), CHyprColor{1.0, 0.1, 0.1, 1.0}, 15000, ICON_WARNING); + + if (m_safeMode) + openSafeModeBox(); +} + +void CCompositor::openSafeModeBox() { + const auto OPT_LOAD = I18n::i18nEngine()->localize(I18n::TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG); + const auto OPT_OPEN = I18n::i18nEngine()->localize(I18n::TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR); + const auto OPT_OK = I18n::i18nEngine()->localize(I18n::TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD); + + auto box = CAsyncDialogBox::create(I18n::i18nEngine()->localize(I18n::TXT_KEY_SAFE_MODE_TITLE), I18n::i18nEngine()->localize(I18n::TXT_KEY_SAFE_MODE_DESCRIPTION), + { + OPT_LOAD, + OPT_OPEN, + OPT_OK, + }); + + box->open()->then([OPT_LOAD, OPT_OK, OPT_OPEN, this](SP> result) { + if (result->hasError()) + return; + + const auto RES = result->result(); + + if (RES.starts_with(OPT_LOAD)) { + m_safeMode = false; + Config::mgr()->reload(); + } else if (RES.starts_with(OPT_OPEN)) { + std::string reportPath; + const auto HOME = getenv("HOME"); + const auto CACHE_HOME = getenv("XDG_CACHE_HOME"); + + if (CACHE_HOME && CACHE_HOME[0] != '\0') { + reportPath += CACHE_HOME; + reportPath += "/hyprland/"; + } else if (HOME && HOME[0] != '\0') { + reportPath += HOME; + reportPath += "/.cache/hyprland/"; + } + Hyprutils::OS::CProcess proc("xdg-open", {reportPath}); + + proc.runAsync(); + + // reopen + openSafeModeBox(); + } + }); } void CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWorkspace) { @@ -2495,59 +2667,30 @@ void CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWor setWindowFullscreenInternal(pWindow, FSMODE_NONE); const PHLWINDOW pFirstWindowOnWorkspace = pWorkspace->getFirstWindow(); - const int visibleWindowsOnWorkspace = pWorkspace->getWindows(std::nullopt, true); + const int visibleWindowsOnWorkspace = pWorkspace->getWindows(true, std::nullopt, true); const auto POSTOMON = pWindow->m_realPosition->goal() - (pWindow->m_monitor ? pWindow->m_monitor->m_position : Vector2D{}); const auto PWORKSPACEMONITOR = pWorkspace->m_monitor.lock(); - if (!pWindow->m_isFloating) - g_pLayoutManager->getCurrentLayout()->onWindowRemovedTiling(pWindow); - pWindow->moveToWorkspace(pWorkspace); pWindow->m_monitor = pWorkspace->m_monitor; static auto PGROUPONMOVETOWORKSPACE = CConfigValue("group:group_on_movetoworkspace"); - if (*PGROUPONMOVETOWORKSPACE && visibleWindowsOnWorkspace == 1 && pFirstWindowOnWorkspace && pFirstWindowOnWorkspace != pWindow && - pFirstWindowOnWorkspace->m_groupData.pNextWindow.lock() && pWindow->canBeGroupedInto(pFirstWindowOnWorkspace)) { - - pWindow->m_isFloating = pFirstWindowOnWorkspace->m_isFloating; // match the floating state. Needed to group tiled into floated and vice versa. - if (!pWindow->m_groupData.pNextWindow.expired()) { - PHLWINDOW next = pWindow->m_groupData.pNextWindow.lock(); - while (next != pWindow) { - next->m_isFloating = pFirstWindowOnWorkspace->m_isFloating; // match the floating state of group members - next = next->m_groupData.pNextWindow.lock(); - } - } - - static auto USECURRPOS = CConfigValue("group:insert_after_current"); - (*USECURRPOS ? pFirstWindowOnWorkspace : pFirstWindowOnWorkspace->getGroupTail())->insertWindowToGroup(pWindow); - - pFirstWindowOnWorkspace->setGroupCurrent(pWindow); - pWindow->updateWindowDecos(); - g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow); - - if (!pWindow->getDecorationByType(DECORATION_GROUPBAR)) - pWindow->addWindowDeco(makeUnique(pWindow)); - + if (*PGROUPONMOVETOWORKSPACE && visibleWindowsOnWorkspace == 1 && pFirstWindowOnWorkspace && pFirstWindowOnWorkspace != pWindow && pFirstWindowOnWorkspace->m_group && + pWindow->canBeGroupedInto(pFirstWindowOnWorkspace->m_group)) { + pFirstWindowOnWorkspace->m_group->add(pWindow); } else { - if (!pWindow->m_isFloating) - g_pLayoutManager->getCurrentLayout()->onWindowCreatedTiling(pWindow); - if (pWindow->m_isFloating) - *pWindow->m_realPosition = POSTOMON + PWORKSPACEMONITOR->m_position; + pWindow->layoutTarget()->setPositionGlobal(CBox{POSTOMON + PWORKSPACEMONITOR->m_position, pWindow->layoutTarget()->position().size()}); } pWindow->updateToplevel(); pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE); pWindow->uncacheWindowDecos(); - pWindow->updateGroupOutputs(); - if (!pWindow->m_groupData.pNextWindow.expired()) { - PHLWINDOW next = pWindow->m_groupData.pNextWindow.lock(); - while (next != pWindow) { - next->updateToplevel(); - next = next->m_groupData.pNextWindow.lock(); - } - } + if (pWindow->m_group) + pWindow->m_group->updateWorkspace(pWorkspace); + + g_layoutManager->newTarget(pWindow->layoutTarget(), pWorkspace->m_space); if (FULLSCREEN) setWindowFullscreenInternal(pWindow, FULLSCREENMODE); @@ -2596,9 +2739,9 @@ void CCompositor::checkMonitorOverlaps() { for (const auto& m : m_monitors) { if (!monitorRegion.copy().intersect(m->logicalBox()).empty()) { - Debug::log(ERR, "Monitor {}: detected overlap with layout", m->m_name); - g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, {{"name", m->m_name}}), CHyprColor{}, 15000, - ICON_WARNING); + Log::logger->log(Log::ERR, "Monitor {}: detected overlap with layout", m->m_name); + Notification::overlay()->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, {{"name", m->m_name}}), CHyprColor{}, 15000, + ICON_WARNING); break; } @@ -2608,22 +2751,22 @@ void CCompositor::checkMonitorOverlaps() { } void CCompositor::arrangeMonitors() { - static auto* const PXWLFORCESCALEZERO = rc(g_pConfigManager->getConfigValuePtr("xwayland:force_zero_scaling")); + static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); std::vector toArrange(m_monitors.begin(), m_monitors.end()); std::vector arranged; arranged.reserve(toArrange.size()); - Debug::log(LOG, "arrangeMonitors: {} to arrange", toArrange.size()); + Log::logger->log(Log::DEBUG, "arrangeMonitors: {} to arrange", toArrange.size()); for (auto it = toArrange.begin(); it != toArrange.end();) { auto m = *it; - if (m->m_activeMonitorRule.offset != Vector2D{-INT32_MAX, -INT32_MAX}) { + if (m->m_activeMonitorRule.m_offset != Vector2D{-INT32_MAX, -INT32_MAX}) { // explicit. - Debug::log(LOG, "arrangeMonitors: {} explicit {:j}", m->m_name, m->m_activeMonitorRule.offset); + Log::logger->log(Log::DEBUG, "arrangeMonitors: {} explicit {:j}", m->m_name, m->m_activeMonitorRule.m_offset); - m->moveTo(m->m_activeMonitorRule.offset); + m->moveTo(m->m_activeMonitorRule.m_offset); arranged.push_back(m); it = toArrange.erase(it); @@ -2664,31 +2807,31 @@ void CCompositor::arrangeMonitors() { // Moves the monitor to their appropriate position on the x/y axis and // increments/decrements the corresponding max offset. Vector2D newPosition = {0, 0}; - switch (m->m_activeMonitorRule.autoDir) { - case eAutoDirs::DIR_AUTO_UP: newPosition.y = maxYOffsetUp - m->m_size.y; break; - case eAutoDirs::DIR_AUTO_DOWN: newPosition.y = maxYOffsetDown; break; - case eAutoDirs::DIR_AUTO_LEFT: newPosition.x = maxXOffsetLeft - m->m_size.x; break; - case eAutoDirs::DIR_AUTO_RIGHT: - case eAutoDirs::DIR_AUTO_NONE: newPosition.x = maxXOffsetRight; break; - case eAutoDirs::DIR_AUTO_CENTER_UP: { + switch (m->m_activeMonitorRule.m_autoDir) { + case Config::eAutoDirs::DIR_AUTO_UP: newPosition.y = maxYOffsetUp - m->m_size.y; break; + case Config::eAutoDirs::DIR_AUTO_DOWN: newPosition.y = maxYOffsetDown; break; + case Config::eAutoDirs::DIR_AUTO_LEFT: newPosition.x = maxXOffsetLeft - m->m_size.x; break; + case Config::eAutoDirs::DIR_AUTO_RIGHT: + case Config::eAutoDirs::DIR_AUTO_NONE: newPosition.x = maxXOffsetRight; break; + case Config::eAutoDirs::DIR_AUTO_CENTER_UP: { int width = maxXOffsetRight - maxXOffsetLeft; newPosition.y = maxYOffsetUp - m->m_size.y; newPosition.x = maxXOffsetLeft + (width - m->m_size.x) / 2; break; } - case eAutoDirs::DIR_AUTO_CENTER_DOWN: { + case Config::eAutoDirs::DIR_AUTO_CENTER_DOWN: { int width = maxXOffsetRight - maxXOffsetLeft; newPosition.y = maxYOffsetDown; newPosition.x = maxXOffsetLeft + (width - m->m_size.x) / 2; break; } - case eAutoDirs::DIR_AUTO_CENTER_LEFT: { + case Config::eAutoDirs::DIR_AUTO_CENTER_LEFT: { int height = maxYOffsetDown - maxYOffsetUp; newPosition.x = maxXOffsetLeft - m->m_size.x; newPosition.y = maxYOffsetUp + (height - m->m_size.y) / 2; break; } - case eAutoDirs::DIR_AUTO_CENTER_RIGHT: { + case Config::eAutoDirs::DIR_AUTO_CENTER_RIGHT: { int height = maxYOffsetDown - maxYOffsetUp; newPosition.x = maxXOffsetRight; newPosition.y = maxYOffsetUp + (height - m->m_size.y) / 2; @@ -2696,7 +2839,7 @@ void CCompositor::arrangeMonitors() { } default: UNREACHABLE(); } - Debug::log(LOG, "arrangeMonitors: {} auto {:j}", m->m_name, m->m_position); + Log::logger->log(Log::DEBUG, "arrangeMonitors: {} auto {:j}", m->m_name, m->m_position); m->moveTo(newPosition); arranged.emplace_back(m); } @@ -2705,7 +2848,7 @@ void CCompositor::arrangeMonitors() { // and set xwayland positions aka auto for all maxXOffsetRight = 0; for (auto const& m : m_monitors) { - Debug::log(LOG, "arrangeMonitors: {} xwayland [{}, {}]", m->m_name, maxXOffsetRight, 0); + Log::logger->log(Log::DEBUG, "arrangeMonitors: {} xwayland [{}, {}]", m->m_name, maxXOffsetRight, 0); m->m_xwaylandPosition = {maxXOffsetRight, 0}; maxXOffsetRight += (*PXWLFORCESCALEZERO ? m->m_transformedSize.x : m->m_size.x); @@ -2716,12 +2859,17 @@ void CCompositor::arrangeMonitors() { } PROTO::xdgOutput->updateAllOutputs(); + Event::bus()->m_events.monitor.layoutChanged.emit(); #ifndef NO_XWAYLAND - CBox box = g_pCompositor->calculateX11WorkArea(); - if (!g_pXWayland || !g_pXWayland->m_wm) - return; - g_pXWayland->m_wm->updateWorkArea(box.x, box.y, box.w, box.h); + const auto box = g_pCompositor->calculateX11WorkArea(); + if (g_pXWayland && g_pXWayland->m_wm) { + if (box) + g_pXWayland->m_wm->updateWorkArea(box->x, box->y, box->w, box->h); + else + g_pXWayland->m_wm->updateWorkArea(0, 0, 0, 0); + } + #endif } @@ -2729,7 +2877,7 @@ void CCompositor::enterUnsafeState() { if (m_unsafeState) return; - Debug::log(LOG, "Entering unsafe state"); + Log::logger->log(Log::DEBUG, "Entering unsafe state"); if (!m_unsafeOutput->m_enabled) m_unsafeOutput->onConnect(false); @@ -2743,7 +2891,7 @@ void CCompositor::leaveUnsafeState() { if (!m_unsafeState) return; - Debug::log(LOG, "Leaving unsafe state"); + Log::logger->log(Log::DEBUG, "Leaving unsafe state"); m_unsafeState = false; @@ -2769,9 +2917,9 @@ void CCompositor::setPreferredScaleForSurface(SP pSurface, d PROTO::fractional->sendScale(pSurface, scale); pSurface->sendPreferredScale(std::ceil(scale)); - const auto PSURFACE = CWLSurface::fromResource(pSurface); + const auto PSURFACE = Desktop::View::CWLSurface::fromResource(pSurface); if (!PSURFACE) { - Debug::log(WARN, "Orphaned CWLSurfaceResource {:x} in setPreferredScaleForSurface", rc(pSurface.get())); + Log::logger->log(Log::WARN, "Orphaned CWLSurfaceResource {:x} in setPreferredScaleForSurface", rc(pSurface.get())); return; } @@ -2782,9 +2930,9 @@ void CCompositor::setPreferredScaleForSurface(SP pSurface, d void CCompositor::setPreferredTransformForSurface(SP pSurface, wl_output_transform transform) { pSurface->sendPreferredTransform(transform); - const auto PSURFACE = CWLSurface::fromResource(pSurface); + const auto PSURFACE = Desktop::View::CWLSurface::fromResource(pSurface); if (!PSURFACE) { - Debug::log(WARN, "Orphaned CWLSurfaceResource {:x} in setPreferredTransformForSurface", rc(pSurface.get())); + Log::logger->log(Log::WARN, "Orphaned CWLSurfaceResource {:x} in setPreferredTransformForSurface", rc(pSurface.get())); return; } @@ -2840,7 +2988,7 @@ void CCompositor::onNewMonitor(SP output) { output->name = "FALLBACK"; // we are allowed to do this :) } - Debug::log(LOG, "New output with name {}", output->name); + Log::logger->log(Log::DEBUG, "New output with name {}", output->name); PNEWMONITOR->m_name = output->name; PNEWMONITOR->m_self = PNEWMONITOR; @@ -2848,7 +2996,7 @@ void CCompositor::onNewMonitor(SP output) { PNEWMONITOR->m_id = FALLBACK ? MONITOR_INVALID : g_pCompositor->getNextAvailableMonitorID(output->name); PNEWMONITOR->m_isUnsafeFallback = FALLBACK; - EMIT_HOOK_EVENT("newMonitor", PNEWMONITOR); + Event::bus()->m_events.monitor.newMon.emit(PNEWMONITOR); if (!FALLBACK) PNEWMONITOR->onConnect(false); @@ -2863,7 +3011,8 @@ void CCompositor::onNewMonitor(SP output) { g_pCompositor->m_readyToProcess = true; - g_pConfigManager->m_wantsMonitorReload = true; + Config::monitorRuleMgr()->scheduleReload(); + g_pCompositor->scheduleFrameForMonitor(PNEWMONITOR, IOutput::AQ_SCHEDULE_NEW_MONITOR); checkDefaultCursorWarp(PNEWMONITOR); @@ -2879,61 +3028,85 @@ void CCompositor::onNewMonitor(SP output) { PNEWMONITOR->m_frameScheduler->onFrame(); if (PROTO::colorManagement && shouldChangePreferredImageDescription()) { - Debug::log(ERR, "FIXME: color management protocol is enabled, need a preferred image description id"); + Log::logger->log(Log::ERR, "FIXME: color management protocol is enabled, need a preferred image description id"); PROTO::colorManagement->onImagePreferredChanged(0); } } -SImageDescription CCompositor::getPreferredImageDescription() { +PImageDescription CCompositor::getPreferredImageDescription() { if (!PROTO::colorManagement) { - Debug::log(ERR, "FIXME: color management protocol is not enabled, returning empty image description"); - return SImageDescription{}; + Log::logger->log(Log::ERR, "FIXME: color management protocol is not enabled, returning empty image description"); + return getDefaultImageDescription(); } - Debug::log(WARN, "FIXME: color management protocol is enabled, determine correct preferred image description"); + Log::logger->log(Log::WARN, "FIXME: color management protocol is enabled, determine correct preferred image description"); // should determine some common settings to avoid unnecessary transformations while keeping maximum displayable precision - return m_monitors.size() == 1 ? m_monitors[0]->m_imageDescription : SImageDescription{.primaries = NColorPrimaries::BT709}; + return m_monitors.size() == 1 ? m_monitors[0]->m_imageDescription : CImageDescription::from(SImageDescription{.primaries = NColorPrimaries::BT709}); +} + +PImageDescription CCompositor::getHDRImageDescription() { + if (!PROTO::colorManagement) { + Log::logger->log(Log::ERR, "FIXME: color management protocol is not enabled, returning empty image description"); + return getDefaultImageDescription(); + } + + 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()}) : + DEFAULT_HDR_IMAGE_DESCRIPTION; } bool CCompositor::shouldChangePreferredImageDescription() { - Debug::log(WARN, "FIXME: color management protocol is enabled and outputs changed, check preferred image description changes"); + Log::logger->log(Log::WARN, "FIXME: color management protocol is enabled and outputs changed, check preferred image description changes"); return false; } -void CCompositor::ensurePersistentWorkspacesPresent(const std::vector& rules, PHLWORKSPACE pWorkspace) { +void CCompositor::ensurePersistentWorkspacesPresent(PHLWORKSPACE pWorkspace) { + ensurePersistentWorkspacesPresent(Config::workspaceRuleMgr()->getAllWorkspaceRules()); +} + +void CCompositor::ensurePersistentWorkspacesPresent(const std::vector& rules, PHLWORKSPACE pWorkspace) { if (!Desktop::focusState()->monitor()) return; std::vector persistentFound; for (const auto& rule : rules) { - if (!rule.isPersistent) + if (!rule.m_isPersistent) continue; PHLWORKSPACE PWORKSPACE = nullptr; if (pWorkspace) { - if (pWorkspace->matchesStaticSelector(rule.workspaceString)) + if (pWorkspace->matchesStaticSelector(rule.m_workspaceString)) PWORKSPACE = pWorkspace; else continue; } - auto PMONITOR = getMonitorFromString(rule.monitor); + auto PMONITOR = getMonitorFromString(rule.m_monitor); - if (!rule.monitor.empty() && !PMONITOR) + if (!rule.m_monitor.empty() && !PMONITOR) continue; // don't do anything yet, as the monitor is not yet present. if (!PWORKSPACE) { - WORKSPACEID id = rule.workspaceId; - std::string wsname = rule.workspaceName; + WORKSPACEID id = rule.m_workspaceId; + std::string wsname = rule.m_workspaceName; if (id == WORKSPACE_INVALID) { - const auto R = getWorkspaceIDNameFromString(rule.workspaceString); + const auto R = getWorkspaceIDNameFromString(rule.m_workspaceString); id = R.id; wsname = R.name; } if (id == WORKSPACE_INVALID) { - Debug::log(ERR, "ensurePersistentWorkspacesPresent: couldn't resolve id for workspace {}", rule.workspaceString); + Log::logger->log(Log::ERR, "ensurePersistentWorkspacesPresent: couldn't resolve id for workspace {}", rule.m_workspaceString); continue; } PWORKSPACE = getWorkspaceByID(id); @@ -2945,7 +3118,7 @@ void CCompositor::ensurePersistentWorkspacesPresent(const std::vectorlog(Log::ERR, "ensurePersistentWorkspacesPresent: couldn't resolve monitor for {}, skipping", rule.m_monitor); continue; } @@ -2957,12 +3130,12 @@ void CCompositor::ensurePersistentWorkspacesPresent(const std::vectorm_monitor == PMONITOR) { - Debug::log(LOG, "ensurePersistentWorkspacesPresent: workspace persistent {} already on {}", rule.workspaceString, PMONITOR->m_name); + Log::logger->log(Log::DEBUG, "ensurePersistentWorkspacesPresent: workspace persistent {} already on {}", rule.m_workspaceString, PMONITOR->m_name); continue; } - Debug::log(LOG, "ensurePersistentWorkspacesPresent: workspace persistent {} not on {}, moving", rule.workspaceString, PMONITOR->m_name); + Log::logger->log(Log::DEBUG, "ensurePersistentWorkspacesPresent: workspace persistent {} not on {}, moving", rule.m_workspaceString, PMONITOR->m_name); moveWorkspaceToMonitor(PWORKSPACE, PMONITOR); continue; } @@ -2987,6 +3160,27 @@ void CCompositor::ensurePersistentWorkspacesPresent(const std::vectorm_isSpecialWorkspace) + continue; + + const auto RULE = Config::workspaceRuleMgr()->getWorkspaceRuleFor(ws); + if (!RULE || RULE->m_monitor.empty()) + continue; + + const auto PMONITOR = getMonitorFromString(RULE->m_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; @@ -3007,3 +3201,7 @@ std::optional CCompositor::getVTNr() { return ttynum; } + +bool CCompositor::isVRRActiveOnAnyMonitor() const { + return std::ranges::any_of(m_monitors, [](const PHLMONITOR& m) { return m->m_vrrActive; }); +} diff --git a/src/Compositor.hpp b/src/Compositor.hpp index 3e1e37f2a..fe9ed2e93 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -4,11 +4,13 @@ #include +#include "helpers/math/Direction.hpp" #include "managers/XWaylandManager.hpp" #include "managers/KeybindManager.hpp" #include "managers/SessionLockManager.hpp" -#include "desktop/Window.hpp" -#include "protocols/types/ColorManagement.hpp" +#include "desktop/view/Window.hpp" +#include "helpers/cm/ColorManagement.hpp" +#include "config/shared/workspace/WorkspaceRule.hpp" #include #include @@ -40,6 +42,7 @@ class CCompositor { } m_drmRenderNode; bool m_initialized = false; + bool m_safeMode = false; SP m_aqBackend; std::string m_hyprTempDataRoot = ""; @@ -55,6 +58,7 @@ class CCompositor { std::vector m_layers; std::vector m_windowsFadingOut; std::vector m_surfacesFadingOut; + std::vector> m_otherViews; std::unordered_map m_monitorIDMap; std::unordered_map m_seenMonitorWorkspaceMap; // map of seen monitor names to workspace IDs @@ -65,6 +69,7 @@ class CCompositor { void cleanup(); void bumpNofile(); void restoreNofile(); + bool setWatchdogFd(int fd); bool m_readyToProcess = false; bool m_sessionActive = true; @@ -94,7 +99,7 @@ class CCompositor { PHLMONITOR getMonitorFromVector(const Vector2D&); void removeWindowFromVectorSafe(PHLWINDOW); bool monitorExists(PHLMONITOR); - PHLWINDOW vectorToWindowUnified(const Vector2D&, uint8_t properties, PHLWINDOW pIgnoreWindow = nullptr); + PHLWINDOW vectorToWindowUnified(const Vector2D&, uint16_t properties, PHLWINDOW pIgnoreWindow = nullptr); SP vectorToLayerSurface(const Vector2D&, std::vector*, Vector2D*, PHLLS*, bool aboveLockscreen = false); SP vectorToLayerPopupSurface(const Vector2D&, PHLMONITOR monitor, Vector2D*, PHLLS*); SP vectorWindowToSurface(const Vector2D&, PHLWINDOW, Vector2D& sl); @@ -110,16 +115,16 @@ class CCompositor { bool isWindowActive(PHLWINDOW); void changeWindowZOrder(PHLWINDOW, bool); void cleanupFadingOut(const MONITORID& monid); - PHLWINDOW getWindowInDirection(PHLWINDOW, char); - PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, char dir, PHLWINDOW ignoreWindow = nullptr, bool useVectorAngles = false); + PHLWINDOW getWindowInDirection(PHLWINDOW, Math::eDirection); + PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow = nullptr, bool useVectorAngles = false); PHLWINDOW getWindowCycle(PHLWINDOW cur, bool focusableOnly = false, std::optional floating = std::nullopt, bool visible = false, bool prev = false); PHLWINDOW getWindowCycleHist(PHLWINDOWREF cur, bool focusableOnly = false, std::optional floating = std::nullopt, bool visible = false, bool next = false); WORKSPACEID getNextAvailableNamedWorkspace(); bool isPointOnAnyMonitor(const Vector2D&); bool isPointOnReservedArea(const Vector2D& point, const PHLMONITOR monitor = nullptr); - CBox calculateX11WorkArea(); - PHLMONITOR getMonitorInDirection(const char&); - PHLMONITOR getMonitorInDirection(PHLMONITOR, const char&); + std::optional calculateX11WorkArea(); + PHLMONITOR getMonitorInDirection(Math::eDirection); + PHLMONITOR getMonitorInDirection(PHLMONITOR, Math::eDirection); void updateAllWindowsAnimatedDecorationValues(); MONITORID getNextAvailableMonitorID(std::string const& name); void moveWorkspaceToMonitor(PHLWORKSPACE, PHLMONITOR, bool noWarpCursor = false); @@ -128,7 +133,7 @@ class CCompositor { bool workspaceIDOutOfBounds(const WORKSPACEID&); void setWindowFullscreenInternal(const PHLWINDOW PWINDOW, const eFullscreenMode MODE); void setWindowFullscreenClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE); - void setWindowFullscreenState(const PHLWINDOW PWINDOW, const SFullscreenState state); + void setWindowFullscreenState(const PHLWINDOW PWINDOW, const Desktop::View::SFullscreenState state); void changeWindowFullscreenModeClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE, const bool ON); PHLWINDOW getX11Parent(PHLWINDOW); void scheduleFrameForMonitor(PHLMONITOR, Aquamarine::IOutput::scheduleFrameReason reason = Aquamarine::IOutput::AQ_SCHEDULE_CLIENT_UNKNOWN); @@ -156,31 +161,37 @@ class CCompositor { void setPreferredTransformForSurface(SP pSurface, wl_output_transform transform); void updateSuspendedStates(); void onNewMonitor(SP output); - void ensurePersistentWorkspacesPresent(const std::vector& rules, PHLWORKSPACE pWorkspace = nullptr); + void ensurePersistentWorkspacesPresent(const std::vector& rules, PHLWORKSPACE pWorkspace = nullptr); + void ensurePersistentWorkspacesPresent(PHLWORKSPACE pWorkspace = nullptr); + void ensureWorkspacesOnAssignedMonitors(); std::optional getVTNr(); + bool isVRRActiveOnAnyMonitor() const; - NColorManagement::SImageDescription getPreferredImageDescription(); + NColorManagement::PImageDescription getPreferredImageDescription(); + NColorManagement::PImageDescription getHDRImageDescription(); bool shouldChangePreferredImageDescription(); bool supportsDrmSyncobjTimeline() const; std::string m_explicitConfigPath; private: - void initAllSignals(); - void removeAllSignals(); - void cleanEnvironment(); - void setRandomSplash(); - void initManagers(eManagersInitStage stage); - void prepareFallbackOutput(); - void createLockFile(); - void removeLockFile(); - void setMallocThreshold(); + void initAllSignals(); + void removeAllSignals(); + void cleanEnvironment(); + void setRandomSplash(); + void initManagers(eManagersInitStage stage); + void prepareFallbackOutput(); + void createLockFile(); + void removeLockFile(); + void setMallocThreshold(); + void openSafeModeBox(); - uint64_t m_hyprlandPID = 0; - wl_event_source* m_critSigSource = nullptr; - rlimit m_originalNofile = {}; + uint64_t m_hyprlandPID = 0; + wl_event_source* m_critSigSource = nullptr; + rlimit m_originalNofile = {}; + Hyprutils::OS::CFileDescriptor m_watchdogWriteFd; - std::vector m_workspaces; + std::vector m_workspaces; }; inline UP g_pCompositor; diff --git a/src/SharedDefs.hpp b/src/SharedDefs.hpp index 639d160a5..bb7c601e3 100644 --- a/src/SharedDefs.hpp +++ b/src/SharedDefs.hpp @@ -38,10 +38,6 @@ enum eInputType : uint8_t { INPUT_TYPE_MOTION }; -struct SCallbackInfo { - bool cancelled = false; /* on cancellable events, will cancel the event. */ -}; - enum eHyprCtlOutputFormat : uint8_t { FORMAT_NORMAL = 0, FORMAT_JSON @@ -62,5 +58,3 @@ struct SDispatchResult { using WINDOWID = int64_t; using MONITORID = int64_t; using WORKSPACEID = int64_t; - -using HOOK_CALLBACK_FN = std::function; diff --git a/src/config/ConfigDataValues.hpp b/src/config/ConfigDataValues.hpp deleted file mode 100644 index 6461e9a58..000000000 --- a/src/config/ConfigDataValues.hpp +++ /dev/null @@ -1,180 +0,0 @@ -#pragma once -#include "../defines.hpp" -#include "../helpers/varlist/VarList.hpp" -#include -#include - -enum eConfigValueDataTypes : int8_t { - CVD_TYPE_INVALID = -1, - CVD_TYPE_GRADIENT = 0, - CVD_TYPE_CSS_VALUE = 1, - CVD_TYPE_FONT_WEIGHT = 2, -}; - -class ICustomConfigValueData { - public: - virtual ~ICustomConfigValueData() = default; - - virtual eConfigValueDataTypes getDataType() = 0; - - virtual std::string toString() = 0; -}; - -class CGradientValueData : public ICustomConfigValueData { - public: - CGradientValueData() = default; - CGradientValueData(CHyprColor col) { - m_colors.push_back(col); - updateColorsOk(); - }; - virtual ~CGradientValueData() = default; - - virtual eConfigValueDataTypes getDataType() { - return CVD_TYPE_GRADIENT; - } - - void reset(CHyprColor col) { - m_colors.clear(); - m_colors.emplace_back(col); - m_angle = 0; - updateColorsOk(); - } - - void updateColorsOk() { - m_colorsOkLabA.clear(); - for (auto& c : m_colors) { - const auto OKLAB = c.asOkLab(); - m_colorsOkLabA.emplace_back(OKLAB.l); - m_colorsOkLabA.emplace_back(OKLAB.a); - m_colorsOkLabA.emplace_back(OKLAB.b); - m_colorsOkLabA.emplace_back(c.a); - } - } - - /* Vector containing the colors */ - std::vector m_colors; - - /* Vector containing pure colors for shoving into opengl */ - std::vector m_colorsOkLabA; - - /* Float corresponding to the angle (rad) */ - float m_angle = 0; - - // - bool operator==(const CGradientValueData& other) const { - if (other.m_colors.size() != m_colors.size() || m_angle != other.m_angle) - return false; - - for (size_t i = 0; i < m_colors.size(); ++i) - if (m_colors[i] != other.m_colors[i]) - return false; - - return true; - } - - virtual std::string toString() { - std::string result; - for (auto& c : m_colors) { - result += std::format("{:x} ", c.getAsHex()); - } - - result += std::format("{}deg", sc(m_angle * 180.0 / M_PI)); - return result; - } -}; - -class CCssGapData : public ICustomConfigValueData { - public: - CCssGapData() : m_top(0), m_right(0), m_bottom(0), m_left(0) {}; - CCssGapData(int64_t global) : m_top(global), m_right(global), m_bottom(global), m_left(global) {}; - CCssGapData(int64_t vertical, int64_t horizontal) : m_top(vertical), m_right(horizontal), m_bottom(vertical), m_left(horizontal) {}; - CCssGapData(int64_t top, int64_t horizontal, int64_t bottom) : m_top(top), m_right(horizontal), m_bottom(bottom), m_left(horizontal) {}; - CCssGapData(int64_t top, int64_t right, int64_t bottom, int64_t left) : m_top(top), m_right(right), m_bottom(bottom), m_left(left) {}; - - /* Css like directions */ - int64_t m_top; - int64_t m_right; - int64_t m_bottom; - int64_t m_left; - - void parseGapData(CVarList2 varlist) { - const auto toInt = [](std::string_view string) -> int { return std::stoi(std::string(string)); }; - - switch (varlist.size()) { - case 1: { - *this = CCssGapData(toInt(varlist[0])); - break; - } - case 2: { - *this = CCssGapData(toInt(varlist[0]), toInt(varlist[1])); - break; - } - case 3: { - *this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]), toInt(varlist[2])); - break; - } - case 4: { - *this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]), toInt(varlist[2]), toInt(varlist[3])); - break; - } - default: { - Debug::log(WARN, "Too many arguments provided for gaps."); - *this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]), toInt(varlist[2]), toInt(varlist[3])); - break; - } - } - } - - void reset(int64_t global) { - m_top = global; - m_right = global; - m_bottom = global; - m_left = global; - } - - virtual eConfigValueDataTypes getDataType() { - return CVD_TYPE_CSS_VALUE; - } - - virtual std::string toString() { - return std::format("{} {} {} {}", m_top, m_right, m_bottom, m_left); - } -}; - -class CFontWeightConfigValueData : public ICustomConfigValueData { - public: - CFontWeightConfigValueData() = default; - CFontWeightConfigValueData(const char* weight) { - parseWeight(weight); - } - - int64_t m_value = 400; // default to normal weight - - virtual eConfigValueDataTypes getDataType() { - return CVD_TYPE_FONT_WEIGHT; - } - - virtual std::string toString() { - return std::format("{}", m_value); - } - - void parseWeight(const std::string& strWeight) { - auto lcWeight{strWeight}; - std::ranges::transform(strWeight, lcWeight.begin(), ::tolower); - - // values taken from Pango weight enums - const auto WEIGHTS = std::map{ - {"thin", 100}, {"ultralight", 200}, {"light", 300}, {"semilight", 350}, {"book", 380}, {"normal", 400}, - {"medium", 500}, {"semibold", 600}, {"bold", 700}, {"ultrabold", 800}, {"heavy", 900}, {"ultraheavy", 1000}, - }; - - auto weight = WEIGHTS.find(lcWeight); - if (weight != WEIGHTS.end()) - m_value = weight->second; - else { - int w_i = std::stoi(strWeight); - if (w_i < 100 || w_i > 1000) - m_value = 400; - } - } -}; diff --git a/src/config/ConfigDescriptions.hpp b/src/config/ConfigDescriptions.hpp deleted file mode 100644 index 6ef7c2634..000000000 --- a/src/config/ConfigDescriptions.hpp +++ /dev/null @@ -1,1996 +0,0 @@ -#pragma once - -#include -#include "ConfigManager.hpp" - -inline static const std::vector CONFIG_OPTIONS = { - - /* - * general: - */ - - SConfigOptionDescription{ - .value = "general:border_size", - .description = "size of the border around windows", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{1, 0, 20}, - }, - SConfigOptionDescription{ - .value = "general:gaps_in", - .description = "gaps between windows\n\nsupports css style gaps (top, right, bottom, left -> 5 10 15 20)", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{"5"}, - }, - SConfigOptionDescription{ - .value = "general:gaps_out", - .description = "gaps between windows and monitor edges\n\nsupports css style gaps (top, right, bottom, left -> 5 10 15 20)", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{"20"}, - }, - SConfigOptionDescription{ - .value = "general:float_gaps", - .description = "gaps between windows and monitor edges for floating windows\n\nsupports css style gaps (top, right, bottom, left -> 5 10 15 20). \n-1 means default " - "gaps_in/gaps_out\n0 means no gaps", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{"0"}, - }, - SConfigOptionDescription{ - .value = "general:gaps_workspaces", - .description = "gaps between workspaces. Stacks with gaps_out.", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{0, 0, 100}, - }, - SConfigOptionDescription{ - .value = "general:col.inactive_border", - .description = "border color for inactive windows", - .type = CONFIG_OPTION_GRADIENT, - .data = SConfigOptionDescription::SGradientData{"0xff444444"}, - }, - SConfigOptionDescription{ - .value = "general:col.active_border", - .description = "border color for the active window", - .type = CONFIG_OPTION_GRADIENT, - .data = SConfigOptionDescription::SGradientData{"0xffffffff"}, - }, - SConfigOptionDescription{ - .value = "general:col.nogroup_border", - .description = "inactive border color for window that cannot be added to a group (see denywindowfromgroup dispatcher)", - .type = CONFIG_OPTION_GRADIENT, - .data = SConfigOptionDescription::SGradientData{"0xffffaaff"}, - }, - SConfigOptionDescription{ - .value = "general:col.nogroup_border_active", - .description = "active border color for window that cannot be added to a group", - .type = CONFIG_OPTION_GRADIENT, - .data = SConfigOptionDescription::SGradientData{"0xffff00ff"}, - }, - SConfigOptionDescription{ - .value = "general:layout", - .description = "which layout to use. [dwindle/master]", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{"dwindle"}, - }, - SConfigOptionDescription{ - .value = "general:no_focus_fallback", - .description = "if true, will not fall back to the next available window when moving focus in a direction where no window was found", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "general:resize_on_border", - .description = "enables resizing windows by clicking and dragging on borders and gaps", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "general:extend_border_grab_area", - .description = "extends the area around the border where you can click and drag on, only used when general:resize_on_border is on.", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{15, 0, 100}, - }, - SConfigOptionDescription{ - .value = "general:hover_icon_on_border", - .description = "show a cursor icon when hovering over borders, only used when general:resize_on_border is on.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "general:allow_tearing", - .description = "master switch for allowing tearing to occur. See the Tearing page.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "general:resize_corner", - .description = "force floating windows to use a specific corner when being resized (1-4 going clockwise from top left, 0 to disable)", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{0, 0, 4}, - }, - SConfigOptionDescription{ - .value = "general:snap:enabled", - .description = "enable snapping for floating windows", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "general:snap:window_gap", - .description = "minimum gap in pixels between windows before snapping", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{10, 0, 100}, - }, - SConfigOptionDescription{ - .value = "general:snap:monitor_gap", - .description = "minimum gap in pixels between window and monitor edges before snapping", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{10, 0, 100}, - }, - SConfigOptionDescription{ - .value = "general:snap:border_overlap", - .description = "if true, windows snap such that only one border's worth of space is between them", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "general:snap:respect_gaps", - .description = "if true, snapping will respect gaps between windows", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "general:modal_parent_blocking", - .description = "if true, parent windows of modals will not be interactive.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "general:locale", - .description = "overrides the system locale", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{""}, - }, - - /* - * decoration: - */ - - SConfigOptionDescription{ - .value = "decoration:rounding", - .description = "rounded corners' radius (in layout px)", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{0, 0, 20}, - }, - SConfigOptionDescription{ - .value = "decoration:rounding_power", - .description = "rounding power of corners (2 is a circle)", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{2, 2, 10}, - }, - SConfigOptionDescription{ - .value = "decoration:active_opacity", - .description = "opacity of active windows. [0.0 - 1.0]", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{1, 0, 1}, - }, - SConfigOptionDescription{ - .value = "decoration:inactive_opacity", - .description = "opacity of inactive windows. [0.0 - 1.0]", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{1, 0, 1}, - }, - SConfigOptionDescription{ - .value = "decoration:fullscreen_opacity", - .description = "opacity of fullscreen windows. [0.0 - 1.0]", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{1, 0, 1}, - }, - SConfigOptionDescription{ - .value = "decoration:shadow:enabled", - .description = "enable drop shadows on windows", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "decoration:shadow:range", - .description = "Shadow range (size) in layout px", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{4, 0, 100}, - }, - SConfigOptionDescription{ - .value = "decoration:shadow:render_power", - .description = "in what power to render the falloff (more power, the faster the falloff) [1 - 4]", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{3, 1, 4}, - }, - SConfigOptionDescription{ - .value = "decoration:shadow:sharp", - .description = "whether the shadow should be sharp or not. Akin to an infinitely high render power.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "decoration:shadow:ignore_window", - .description = "if true, the shadow will not be rendered behind the window itself, only around it.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "decoration:shadow:color", - .description = "shadow's color. Alpha dictates shadow's opacity.", - .type = CONFIG_OPTION_COLOR, - .data = SConfigOptionDescription::SColorData{0xee1a1a1a}, - }, - SConfigOptionDescription{ - .value = "decoration:shadow:color_inactive", - .description = "inactive shadow color. (if not set, will fall back to col.shadow)", - .type = CONFIG_OPTION_COLOR, - .data = SConfigOptionDescription::SColorData{}, //TODO: UNSET? - }, - SConfigOptionDescription{ - .value = "decoration:shadow:offset", - .description = "shadow's rendering offset.", - .type = CONFIG_OPTION_VECTOR, - .data = SConfigOptionDescription::SVectorData{{}, {-250, -250}, {250, 250}}, - }, - SConfigOptionDescription{ - .value = "decoration:shadow:scale", - .description = "shadow's scale. [0.0 - 1.0]", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{1, 0, 1}, - }, - SConfigOptionDescription{ - .value = "decoration:dim_modal", - .description = "enables dimming of parents of modal windows", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "decoration:dim_inactive", - .description = "enables dimming of inactive windows", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "decoration:dim_strength", - .description = "how much inactive windows should be dimmed [0.0 - 1.0]", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{0.5, 0, 1}, - }, - SConfigOptionDescription{ - .value = "decoration:dim_special", - .description = "how much to dim the rest of the screen by when a special workspace is open. [0.0 - 1.0]", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{0.2, 0, 1}, - }, - SConfigOptionDescription{ - .value = "decoration:dim_around", - .description = "how much the dimaround window rule should dim by. [0.0 - 1.0]", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{0.4, 0, 1}, - }, - SConfigOptionDescription{ - .value = "decoration:screen_shader", - .description = "screen_shader a path to a custom shader to be applied at the end of rendering. See examples/screenShader.frag for an example.", - .type = CONFIG_OPTION_STRING_LONG, - .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? - }, - SConfigOptionDescription{ - .value = "decoration:border_part_of_window", - .description = "whether the border should be treated as a part of the window.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - - /* - * blur: - */ - - SConfigOptionDescription{ - .value = "decoration:blur:enabled", - .description = "enable kawase window background blur", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "decoration:blur:size", - .description = "blur size (distance)", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{8, 0, 100}, - }, - SConfigOptionDescription{ - .value = "decoration:blur:passes", - .description = "the amount of passes to perform", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{1, 0, 10}, - }, - SConfigOptionDescription{ - .value = "decoration:blur:ignore_opacity", - .description = "make the blur layer ignore the opacity of the window", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "decoration:blur:new_optimizations", - .description = "whether to enable further optimizations to the blur. Recommended to leave on, as it will massively improve performance.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "decoration:blur:xray", - .description = "if enabled, floating windows will ignore tiled windows in their blur. Only available if blur_new_optimizations is true. Will reduce overhead on floating " - "blur significantly.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "decoration:blur:noise", - .description = "how much noise to apply. [0.0 - 1.0]", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{0.0117, 0, 1}, - }, - SConfigOptionDescription{ - .value = "decoration:blur:contrast", - .description = "contrast modulation for blur. [0.0 - 2.0]", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{0.8916, 0, 2}, - }, - SConfigOptionDescription{ - .value = "decoration:blur:brightness", - .description = "brightness modulation for blur. [0.0 - 2.0]", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{0.8172, 0, 2}, - }, - SConfigOptionDescription{ - .value = "decoration:blur:vibrancy", - .description = "Increase saturation of blurred colors. [0.0 - 1.0]", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{0.1696, 0, 1}, - }, - SConfigOptionDescription{ - .value = "decoration:blur:vibrancy_darkness", - .description = "How strong the effect of vibrancy is on dark areas . [0.0 - 1.0]", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{0, 0, 1}, - }, - SConfigOptionDescription{ - .value = "decoration:blur:special", - .description = "whether to blur behind the special workspace (note: expensive)", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "decoration:blur:popups", - .description = "whether to blur popups (e.g. right-click menus)", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "decoration:blur:popups_ignorealpha", - .description = "works like ignorealpha in layer rules. If pixel opacity is below set value, will not blur. [0.0 - 1.0]", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{0.2, 0, 1}, - }, - SConfigOptionDescription{ - .value = "decoration:blur:input_methods", - .description = "whether to blur input methods (e.g. fcitx5)", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "decoration:blur:input_methods_ignorealpha", - .description = "works like ignorealpha in layer rules. If pixel opacity is below set value, will not blur. [0.0 - 1.0]", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{0.2, 0, 1}, - }, - - /* - * animations: - */ - - SConfigOptionDescription{ - .value = "animations:enabled", - .description = "enable animations", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "animations:workspace_wraparound", - .description = "changes the direction of slide animations between the first and last workspaces", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - - /* - * input: - */ - - SConfigOptionDescription{ - .value = "input:kb_model", - .description = "Appropriate XKB keymap parameter. See the note below.", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{STRVAL_EMPTY}, - }, - SConfigOptionDescription{ - .value = "input:kb_layout", - .description = "Appropriate XKB keymap parameter", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{"us"}, - }, - SConfigOptionDescription{ - .value = "input:kb_variant", - .description = "Appropriate XKB keymap parameter", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{""}, - }, - SConfigOptionDescription{ - .value = "input:kb_options", - .description = "Appropriate XKB keymap parameter", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{""}, - }, - SConfigOptionDescription{ - .value = "input:kb_rules", - .description = "Appropriate XKB keymap parameter", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{""}, - }, - SConfigOptionDescription{ - .value = "input:kb_file", - .description = "Appropriate XKB keymap file", - .type = CONFIG_OPTION_STRING_LONG, - .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? - }, - SConfigOptionDescription{ - .value = "input:numlock_by_default", - .description = "Engage numlock by default.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "input:resolve_binds_by_sym", - .description = "Determines how keybinds act when multiple layouts are used. If false, keybinds will always act as if the first specified layout is active. If true, " - "keybinds specified by symbols are activated when you type the respective symbol with the current layout.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "input:repeat_rate", - .description = "The repeat rate for held-down keys, in repeats per second.", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{25, 0, 200}, - }, - SConfigOptionDescription{ - .value = "input:repeat_delay", - .description = "Delay before a held-down key is repeated, in milliseconds.", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{600, 0, 2000}, - }, - SConfigOptionDescription{ - .value = "input:sensitivity", - .description = "Sets the mouse input sensitivity. Value is clamped to the range -1.0 to 1.0.", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{0, -1, 1}, - }, - SConfigOptionDescription{ - .value = "input:accel_profile", - .description = "Sets the cursor acceleration profile. Can be one of adaptive, flat. Can also be custom, see below. Leave empty to use libinput's default mode for your " - "input device. [adaptive/flat/custom]", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? - }, - SConfigOptionDescription{ - .value = "input:force_no_accel", - .description = "Force no cursor acceleration. This bypasses most of your pointer settings to get as raw of a signal as possible. Enabling this is not recommended due to " - "potential cursor desynchronization.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "input:rotation", - .description = "Sets the rotation of a device in degrees clockwise off the logical neutral position. Value is clamped to the range 0 to 359.", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{0, 0, 359}, - }, - SConfigOptionDescription{ - .value = "input:left_handed", - .description = "Switches RMB and LMB", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "input:scroll_points", - .description = "Sets the scroll acceleration profile, when accel_profile is set to custom. Has to be in the form . Leave empty to have a flat scroll curve.", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? - }, - SConfigOptionDescription{ - .value = "input:scroll_method", - .description = "Sets the scroll method. Can be one of 2fg (2 fingers), edge, on_button_down, no_scroll. [2fg/edge/on_button_down/no_scroll]", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? - }, - SConfigOptionDescription{ - .value = "input:scroll_button", - .description = "Sets the scroll button. Has to be an int, cannot be a string. Check wev if you have any doubts regarding the ID. 0 means default.", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{0, 0, 300}, - }, - SConfigOptionDescription{ - .value = "input:scroll_button_lock", - .description = "If the scroll button lock is enabled, the button does not need to be held down. Pressing and releasing the button toggles the button lock, which logically " - "holds the button down or releases it. While the button is logically held down, motion events are converted to scroll events.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "input:scroll_factor", - .description = "Multiplier added to scroll movement for external mice. Note that there is a separate setting for touchpad scroll_factor.", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{1, 0, 2}, - }, - SConfigOptionDescription{ - .value = "input:natural_scroll", - .description = "Inverts scrolling direction. When enabled, scrolling moves content directly, rather than manipulating a scrollbar.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "input:follow_mouse", - .description = "Specify if and how cursor movement should affect window focus. See the note below. [0/1/2/3]", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{1, 0, 3}, - }, - SConfigOptionDescription{ - .value = "input:follow_mouse_threshold", - .description = "The smallest distance in logical pixels the mouse needs to travel for the window under it to get focused. Works only with follow_mouse = 1.", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{}, - }, - SConfigOptionDescription{ - .value = "input:focus_on_close", - .description = "Controls the window focus behavior when a window is closed. When set to 0, focus will shift to the next window candidate. When set to 1, focus will shift " - "to the window under the cursor.", - .type = CONFIG_OPTION_CHOICE, - .data = SConfigOptionDescription::SChoiceData{0, "next,cursor"}, - }, - SConfigOptionDescription{ - .value = "input:mouse_refocus", - .description = "if disabled, mouse focus won't switch to the hovered window unless the mouse crosses a window boundary when follow_mouse=1.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "input:float_switch_override_focus", - .description = "If enabled (1 or 2), focus will change to the window under the cursor when changing from tiled-to-floating and vice versa. If 2, focus will also follow " - "mouse on float-to-float switches.", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{1, 0, 2}, - }, - SConfigOptionDescription{ - .value = "input:special_fallthrough", - .description = "if enabled, having only floating windows in the special workspace will not block focusing windows in the regular workspace.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "input:off_window_axis_events", - .description = "Handles axis events around (gaps/border for tiled, dragarea/border for floated) a focused window. 0 ignores axis events 1 sends out-of-bound coordinates 2 " - "fakes pointer coordinates to the closest point inside the window 3 warps the cursor to the closest point inside the window", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{1, 0, 3}, - }, - SConfigOptionDescription{ - .value = "input:emulate_discrete_scroll", - .description = "Emulates discrete scrolling from high resolution scrolling events. 0 disables it, 1 enables handling of non-standard events only, and 2 force enables all " - "scroll wheel events to be handled", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{1, 0, 2}, - }, - - /* - * input:touchpad: - */ - - SConfigOptionDescription{ - .value = "input:touchpad:disable_while_typing", - .description = "Disable the touchpad while typing.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "input:touchpad:natural_scroll", - .description = "Inverts scrolling direction. When enabled, scrolling moves content directly, rather than manipulating a scrollbar.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "input:touchpad:scroll_factor", - .description = "Multiplier applied to the amount of scroll movement.", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{1, 0, 2}, - }, - SConfigOptionDescription{ - .value = "input:touchpad:middle_button_emulation", - .description = - "Sending LMB and RMB simultaneously will be interpreted as a middle click. This disables any touchpad area that would normally send a middle click based on location.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "input:touchpad:tap_button_map", - .description = "Sets the tap button mapping for touchpad button emulation. Can be one of lrm (default) or lmr (Left, Middle, Right Buttons). [lrm/lmr]", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? - }, - SConfigOptionDescription{ - .value = "input:touchpad:clickfinger_behavior", - .description = - "Button presses with 1, 2, or 3 fingers will be mapped to LMB, RMB, and MMB respectively. This disables interpretation of clicks based on location on the touchpad.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "input:touchpad:tap-to-click", - .description = "Tapping on the touchpad with 1, 2, or 3 fingers will send LMB, RMB, and MMB respectively.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "input:touchpad:drag_lock", - .description = "When enabled, lifting the finger off while dragging will not drop the dragged item. 0 -> disabled, 1 -> enabled with timeout, 2 -> enabled sticky." - "dragging will not drop the dragged item.", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{0, 0, 2}, - }, - SConfigOptionDescription{ - .value = "input:touchpad:tap-and-drag", - .description = "Sets the tap and drag mode for the touchpad", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "input:touchpad:flip_x", - .description = "Inverts the horizontal movement of the touchpad", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "input:touchpad:flip_y", - .description = "Inverts the vertical movement of the touchpad", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "input:touchpad:drag_3fg", - .description = "Three Finger Drag 0 -> disabled, 1 -> 3 finger, 2 -> 4 finger", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{0, 0, 2}, - }, - - /* - * input:touchdevice: - */ - - SConfigOptionDescription{ - .value = "input:touchdevice:transform", - .description = "Transform the input from touchdevices. The possible transformations are the same as those of the monitors", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{0, 0, 6}, // ##TODO RANGE? - }, - SConfigOptionDescription{ - .value = "input:touchdevice:output", - .description = "The monitor to bind touch devices. The default is auto-detection. To stop auto-detection, use an empty string or the [[Empty]] value.", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? - }, - SConfigOptionDescription{ - .value = "input:touchdevice:enabled", - .description = "Whether input is enabled for touch devices.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - - /* - * input:virtualkeyboard: - */ - - SConfigOptionDescription{ - .value = "input:virtualkeyboard:share_states", - .description = "Unify key down states and modifier states with other keyboards. 0 -> no, 1 -> yes, 2 -> yes unless IME client", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{2, 0, 2}, - }, - SConfigOptionDescription{ - .value = "input:virtualkeyboard:release_pressed_on_close", - .description = "Release all pressed keys by virtual keyboard on close.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - - /* - * input:tablet: - */ - - SConfigOptionDescription{ - .value = "input:tablet:transform", - .description = "transform the input from tablets. The possible transformations are the same as those of the monitors", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{0, 0, 6}, // ##TODO RANGE? - }, - SConfigOptionDescription{ - .value = "input:tablet:output", - .description = "the monitor to bind tablets. Can be current or a monitor name. Leave empty to map across all monitors.", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? - }, - SConfigOptionDescription{ - .value = "input:tablet:region_position", - .description = "position of the mapped region in monitor layout relative to the top left corner of the bound monitor or all monitors.", - .type = CONFIG_OPTION_VECTOR, - .data = SConfigOptionDescription::SVectorData{{}, {-20000, -20000}, {20000, 20000}}, - }, - SConfigOptionDescription{ - .value = "input:tablet:absolute_region_position", - .description = "whether to treat the region_position as an absolute position in monitor layout. Only applies when output is empty.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "input:tablet:region_size", - .description = "size of the mapped region. When this variable is set, tablet input will be mapped to the region. [0, 0] or invalid size means unset.", - .type = CONFIG_OPTION_VECTOR, - .data = SConfigOptionDescription::SVectorData{{}, {-100, -100}, {4000, 4000}}, - }, - SConfigOptionDescription{ - .value = "input:tablet:relative_input", - .description = "whether the input should be relative", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "input:tablet:left_handed", - .description = "if enabled, the tablet will be rotated 180 degrees", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "input:tablet:active_area_size", - .description = "size of tablet's active area in mm", - .type = CONFIG_OPTION_VECTOR, - .data = SConfigOptionDescription::SVectorData{{}, {}, {500, 500}}, - }, - SConfigOptionDescription{ - .value = "input:tablet:active_area_position", - .description = "position of the active area in mm", - .type = CONFIG_OPTION_VECTOR, - .data = SConfigOptionDescription::SVectorData{{}, {}, {500, 500}}, - }, - - /* ##TODO - * - * PER DEVICE SETTINGS? - * - * */ - - /* - * gestures: - */ - - SConfigOptionDescription{ - .value = "gestures:workspace_swipe_distance", - .description = "in px, the distance of the touchpad gesture", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{300, 0, 2000}, //##TODO RANGE? - }, - SConfigOptionDescription{ - .value = "gestures:workspace_swipe_touch", - .description = "enable workspace swiping from the edge of a touchscreen", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "gestures:workspace_swipe_invert", - .description = "invert the direction (touchpad only)", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "gestures:workspace_swipe_touch_invert", - .description = "invert the direction (touchscreen only)", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "gestures:workspace_swipe_min_speed_to_force", - .description = "minimum speed in px per timepoint to force the change ignoring cancel_ratio. Setting to 0 will disable this mechanic.", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{30, 0, 200}, //##TODO RANGE? - }, - SConfigOptionDescription{ - .value = "gestures:workspace_swipe_cancel_ratio", - .description = "how much the swipe has to proceed in order to commence it. (0.7 -> if > 0.7 * distance, switch, if less, revert) [0.0 - 1.0]", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{0.5, 0, 1}, - }, - SConfigOptionDescription{ - .value = "gestures:workspace_swipe_create_new", - .description = "whether a swipe right on the last workspace should create a new one.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "gestures:workspace_swipe_direction_lock", - .description = "if enabled, switching direction will be locked when you swipe past the direction_lock_threshold (touchpad only).", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "gestures:workspace_swipe_direction_lock_threshold", - .description = "in px, the distance to swipe before direction lock activates (touchpad only).", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{10, 0, 200}, //##TODO RANGE? - }, - SConfigOptionDescription{ - .value = "gestures:workspace_swipe_forever", - .description = "if enabled, swiping will not clamp at the neighboring workspaces but continue to the further ones.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "gestures:workspace_swipe_use_r", - .description = "if enabled, swiping will use the r prefix instead of the m prefix for finding workspaces.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "gestures:close_max_timeout", - .description = "Timeout for closing windows with the close gesture, in ms.", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{1000, 10, 2000}, - }, - - /* - * group: - */ - - SConfigOptionDescription{ - .value = "group:insert_after_current", - .description = "whether new windows in a group spawn after current or at group tail", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "group:focus_removed_window", - .description = "whether Hyprland should focus on the window that has just been moved out of the group", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "group:merge_groups_on_drag", - .description = "whether window groups can be dragged into other groups", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "group:merge_groups_on_groupbar", - .description = "whether one group will be merged with another when dragged into its groupbar", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "group:col.border_active", - .description = "border color for inactive windows", - .type = CONFIG_OPTION_GRADIENT, - .data = SConfigOptionDescription::SGradientData{"0x66ffff00"}, - }, - SConfigOptionDescription{ - .value = "group:col.border_inactive", - .description = "border color for the active window", - .type = CONFIG_OPTION_GRADIENT, - .data = SConfigOptionDescription::SGradientData{"0x66777700"}, - }, - SConfigOptionDescription{ - .value = "group:col.border_locked_inactive", - .description = "inactive border color for window that cannot be added to a group (see denywindowfromgroup dispatcher)", - .type = CONFIG_OPTION_GRADIENT, - .data = SConfigOptionDescription::SGradientData{"0x66ff5500"}, - }, - SConfigOptionDescription{ - .value = "group:col.border_locked_active", - .description = "active border color for window that cannot be added to a group", - .type = CONFIG_OPTION_GRADIENT, - .data = SConfigOptionDescription::SGradientData{"0x66775500"}, - }, - SConfigOptionDescription{ - .value = "group:auto_group", - .description = "automatically group new windows", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "group:drag_into_group", - .description = "whether dragging a window into a unlocked group will merge them. Options: 0 (disabled), 1 (enabled), 2 (only when dragging into the groupbar)", - .type = CONFIG_OPTION_CHOICE, - .data = SConfigOptionDescription::SChoiceData{0, "disabled,enabled,only when dragging into the groupbar"}, - }, - SConfigOptionDescription{ - .value = "group:merge_floated_into_tiled_on_groupbar", - .description = "whether dragging a floating window into a tiled window groupbar will merge them", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "group:group_on_movetoworkspace", - .description = "whether using movetoworkspace[silent] will merge the window into the workspace's solitary unlocked group", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - - /* - * group:groupbar: - */ - - SConfigOptionDescription{ - .value = "group:groupbar:enabled", - .description = "enables groupbars", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:font_family", - .description = "font used to display groupbar titles, use misc:font_family if not specified", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{STRVAL_EMPTY}, //##TODO UNSET? - }, - SConfigOptionDescription{ - .value = "group:groupbar:font_weight_active", - .description = "weight of the font used to display active groupbar titles", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{"normal"}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:font_weight_inactive", - .description = "weight of the font used to display inactive groupbar titles", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{"normal"}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:font_size", - .description = "font size of groupbar title", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{8, 2, 64}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:gradients", - .description = "enables gradients", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:height", - .description = "height of the groupbar", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{14, 1, 64}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:indicator_gap", - .description = "height of the gap between the groupbar indicator and title", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{0, 0, 64}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:indicator_height", - .description = "height of the groupbar indicator", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{3, 1, 64}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:stacked", - .description = "render the groupbar as a vertical stack", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:priority", - .description = "sets the decoration priority for groupbars", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{3, 0, 6}, //##TODO RANGE? - }, - SConfigOptionDescription{ - .value = "group:groupbar:render_titles", - .description = "whether to render titles in the group bar decoration", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:scrolling", - .description = "whether scrolling in the groupbar changes group active window", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:rounding", - .description = "how much to round the groupbar", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{1, 0, 20}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:rounding_power", - .description = "rounding power of groupbar corners (2 is a circle)", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{2, 2, 10}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:gradient_rounding", - .description = "how much to round the groupbar gradient", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{1, 0, 20}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:gradient_rounding_power", - .description = "rounding power of groupbar gradient corners (2 is a circle)", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{2, 2, 10}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:round_only_edges", - .description = "if yes, will only round at the groupbar edges", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:gradient_round_only_edges", - .description = "if yes, will only round at the groupbar gradient edges", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:text_color", - .description = "color for window titles in the groupbar", - .type = CONFIG_OPTION_COLOR, - .data = SConfigOptionDescription::SColorData{0xffffffff}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:text_color_inactive", - .description = "color for inactive windows' titles in the groupbar (if unset, defaults to text_color)", - .type = CONFIG_OPTION_COLOR, - .data = SConfigOptionDescription::SColorData{}, //TODO: UNSET? - }, - SConfigOptionDescription{ - .value = "group:groupbar:text_color_locked_active", - .description = "color for the active window's title in a locked group (if unset, defaults to text_color)", - .type = CONFIG_OPTION_COLOR, - .data = SConfigOptionDescription::SColorData{}, //TODO: UNSET? - }, - SConfigOptionDescription{ - .value = "group:groupbar:text_color_locked_inactive", - .description = "color for inactive windows' titles in locked groups (if unset, defaults to text_color_inactive)", - .type = CONFIG_OPTION_COLOR, - .data = SConfigOptionDescription::SColorData{}, //TODO: UNSET? - }, - SConfigOptionDescription{ - .value = "group:groupbar:col.active", - .description = "active group border color", - .type = CONFIG_OPTION_COLOR, - .data = SConfigOptionDescription::SColorData{0x66ffff00}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:col.inactive", - .description = "inactive (out of focus) group border color", - .type = CONFIG_OPTION_COLOR, - .data = SConfigOptionDescription::SColorData{0x66777700}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:col.locked_active", - .description = "active locked group border color", - .type = CONFIG_OPTION_COLOR, - .data = SConfigOptionDescription::SColorData{0x66ff5500}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:col.locked_inactive", - .description = "controls the group bar text color", - .type = CONFIG_OPTION_COLOR, - .data = SConfigOptionDescription::SColorData{0x66775500}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:gaps_out", - .description = "gap between gradients and window", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{2, 0, 20}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:gaps_in", - .description = "gap between gradients", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{2, 0, 20}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:keep_upper_gap", - .description = "keep an upper gap above gradient", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:text_offset", - .description = "set an offset for a text", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SRangeData{0, -20, 20}, - }, - SConfigOptionDescription{ - .value = "group:groupbar:blur", - .description = "enable background blur for groupbars", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - - /* - * misc: - */ - - SConfigOptionDescription{ - .value = "misc:disable_hyprland_logo", - .description = "disables the random Hyprland logo / anime girl background. :(", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "misc:disable_splash_rendering", - .description = "disables the Hyprland splash rendering. (requires a monitor reload to take effect)", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "misc:col.splash", - .description = "Changes the color of the splash text (requires a monitor reload to take effect).", - .type = CONFIG_OPTION_COLOR, - .data = SConfigOptionDescription::SColorData{0xffffffff}, - }, - SConfigOptionDescription{ - .value = "misc:font_family", - .description = "Set the global default font to render the text including debug fps/notification, config error messages and etc., selected from system fonts.", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{"Sans"}, - }, - SConfigOptionDescription{ - .value = "misc:splash_font_family", - .description = "Changes the font used to render the splash text, selected from system fonts (requires a monitor reload to take effect).", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{STRVAL_EMPTY}, //##TODO UNSET? - }, - SConfigOptionDescription{ - .value = "misc:force_default_wallpaper", - .description = "Enforce any of the 3 default wallpapers. Setting this to 0 or 1 disables the anime background. -1 means “random”. [-1/0/1/2]", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{-1, -1, 2}, - }, - SConfigOptionDescription{ - .value = "misc:vfr", - .description = "controls the VFR status of Hyprland. Heavily recommended to leave enabled to conserve resources.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "misc:vrr", - .description = " controls the VRR (Adaptive Sync) of your monitors. 0 - off, 1 - on, 2 - fullscreen only, 3 - fullscreen with game or video content type [0/1/2/3]", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{.value = 0, .min = 0, .max = 3}, - }, - SConfigOptionDescription{ - .value = "misc:mouse_move_enables_dpms", - .description = "If DPMS is set to off, wake up the monitors if the mouse move", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "misc:key_press_enables_dpms", - .description = "If DPMS is set to off, wake up the monitors if a key is pressed.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "misc:name_vk_after_proc", - .description = "Name virtual keyboards after the processes that create them. E.g. /usr/bin/fcitx5 will have hl-virtual-keyboard-fcitx5.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "misc:always_follow_on_dnd", - .description = "Will make mouse focus follow the mouse when drag and dropping. Recommended to leave it enabled, especially for people using focus follows mouse at 0.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "misc:layers_hog_keyboard_focus", - .description = "If true, will make keyboard-interactive layers keep their focus on mouse move (e.g. wofi, bemenu)", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "misc:animate_manual_resizes", - .description = "If true, will animate manual window resizes/moves", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "misc:animate_mouse_windowdragging", - .description = "If true, will animate windows being dragged by mouse, note that this can cause weird behavior on some curves", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "misc:disable_autoreload", - .description = "If true, the config will not reload automatically on save, and instead needs to be reloaded with hyprctl reload. Might save on battery.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "misc:enable_swallow", - .description = "Enable window swallowing", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "misc:swallow_regex", - .description = - "The class regex to be used for windows that should be swallowed (usually, a terminal). To know more about the list of regex which can be used use this cheatsheet.", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? - }, - SConfigOptionDescription{ - .value = "misc:swallow_exception_regex", - .description = "The title regex to be used for windows that should not be swallowed by the windows specified in swallow_regex (e.g. wev). The regex is matched against the " - "parent (e.g. Kitty) window’s title on the assumption that it changes to whatever process it’s running.", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? - }, - SConfigOptionDescription{ - .value = "misc:focus_on_activate", - .description = "Whether Hyprland should focus an app that requests to be focused (an activate request)", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "misc:mouse_move_focuses_monitor", - .description = "Whether mouse moving into a different monitor should focus it", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "misc:allow_session_lock_restore", - .description = "if true, will allow you to restart a lockscreen app in case it crashes (red screen of death)", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "misc:session_lock_xray", - .description = "keep rendering workspaces below your lockscreen", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "misc:background_color", - .description = "change the background color. (requires enabled disable_hyprland_logo)", - .type = CONFIG_OPTION_COLOR, - .data = SConfigOptionDescription::SColorData{0x111111}, - }, - SConfigOptionDescription{ - .value = "misc:close_special_on_empty", - .description = "close the special workspace if the last window is removed", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "misc:on_focus_under_fullscreen", - .description = "if there is a fullscreen or maximized window, decide whether a tiled window requested to focus should replace it, stay behind or disable the " - "fullscreen/maximized state. 0 - ignore focus request (keep focus on fullscreen window), 1 - takes over, 2 - unfullscreen/unmaximize [0/1/2]", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{2, 0, 2}, - }, - SConfigOptionDescription{ - .value = "misc:exit_window_retains_fullscreen", - .description = "if true, closing a fullscreen window makes the next focused window fullscreen", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "misc:initial_workspace_tracking", - .description = "if enabled, windows will open on the workspace they were invoked on. 0 - disabled, 1 - single-shot, 2 - persistent (all children too)", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{1, 0, 2}, - }, - SConfigOptionDescription{ - .value = "misc:middle_click_paste", - .description = "whether to enable middle-click-paste (aka primary selection)", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "misc:render_unfocused_fps", - .description = "the maximum limit for renderunfocused windows' fps in the background", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{15, 1, 120}, - }, - SConfigOptionDescription{ - .value = "misc:disable_xdg_env_checks", - .description = "disable the warning if XDG environment is externally managed", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "misc:disable_hyprland_guiutils_check", - .description = "disable the warning if hyprland-guiutils is missing", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "misc:lockdead_screen_delay", - .description = "the delay in ms after the lockdead screen appears if the lock screen did not appear after a lock event occurred.", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{1000, 0, 5000}, - }, - SConfigOptionDescription{ - .value = "misc:enable_anr_dialog", - .description = "whether to enable the ANR (app not responding) dialog when your apps hang", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "misc:anr_missed_pings", - .description = "number of missed pings before showing the ANR dialog", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{5, 1, 20}, - }, - SConfigOptionDescription{ - .value = "misc:screencopy_force_8b", - .description = "forces 8 bit screencopy (fixes apps that don't understand 10bit)", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "misc:disable_scale_notification", - .description = "disables notification popup when a monitor fails to set a suitable scale and falls back to suggested", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "misc:size_limits_tiled", - .description = "whether to apply minsize and maxsize rules to tiled windows", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - - /* - * binds: - */ - - SConfigOptionDescription{ - .value = "binds:pass_mouse_when_bound", - .description = "if disabled, will not pass the mouse events to apps / dragging windows around if a keybind has been triggered.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "binds:scroll_event_delay", - .description = "in ms, how many ms to wait after a scroll event to allow passing another one for the binds.", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{300, 0, 2000}, - }, - SConfigOptionDescription{ - .value = "binds:workspace_back_and_forth", - .description = "If enabled, an attempt to switch to the currently focused workspace will instead switch to the previous workspace. Akin to i3’s auto_back_and_forth.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "binds:hide_special_on_workspace_change", - .description = "If enabled, changing the active workspace (including to itself) will hide the special workspace on the monitor where the newly active workspace resides.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "binds:allow_workspace_cycles", - .description = "If enabled, workspaces don’t forget their previous workspace, so cycles can be created by switching to the first workspace in a sequence, then endlessly " - "going to the previous workspace.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "binds:workspace_center_on", - .description = "Whether switching workspaces should center the cursor on the workspace (0) or on the last active window for that workspace (1)", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{0, 0, 1}, - }, - SConfigOptionDescription{ - .value = "binds:focus_preferred_method", - .description = "sets the preferred focus finding method when using focuswindow/movewindow/etc with a direction. 0 - history (recent have priority), 1 - length (longer " - "shared edges have priority)", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{0, 0, 1}, - }, - SConfigOptionDescription{ - .value = "binds:ignore_group_lock", - .description = "If enabled, dispatchers like moveintogroup, moveoutofgroup and movewindoworgroup will ignore lock per group.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "binds:movefocus_cycles_fullscreen", - .description = "If enabled, when on a fullscreen window, movefocus will cycle fullscreen, if not, it will move the focus in a direction.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "binds:movefocus_cycles_groupfirst", - .description = "If enabled, when in a grouped window, movefocus will cycle windows in the groups first, then at each ends of tabs, it'll move on to other windows/groups", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "binds:disable_keybind_grabbing", - .description = "If enabled, apps that request keybinds to be disabled (e.g. VMs) will not be able to do so.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "binds:window_direction_monitor_fallback", - .description = "If enabled, moving a window or focus over the edge of a monitor with a direction will move it to the next monitor in that direction.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "binds:allow_pin_fullscreen", - .description = "Allows fullscreen to pinned windows, and restore their pinned status afterwards", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "binds:drag_threshold", - .description = "Movement threshold in pixels for window dragging and c/g bind flags. 0 to disable and grab on mousedown.", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{0, 0, INT_MAX}, - }, - - /* - * xwayland: - */ - - SConfigOptionDescription{ - .value = "xwayland:enabled", - .description = "allow running applications using X11", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "xwayland:use_nearest_neighbor", - .description = "uses the nearest neighbor filtering for xwayland apps, making them pixelated rather than blurry", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "xwayland:force_zero_scaling", - .description = "forces a scale of 1 on xwayland windows on scaled displays.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "xwayland:create_abstract_socket", - .description = "Create the abstract Unix domain socket for XWayland", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - - /* - * opengl: - */ - - SConfigOptionDescription{ - .value = "opengl:nvidia_anti_flicker", - .description = "reduces flickering on nvidia at the cost of possible frame drops on lower-end GPUs. On non-nvidia, this is ignored.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - - /* - * render: - */ - - SConfigOptionDescription{ - .value = "render:direct_scanout", - .description = "Enables direct scanout. Direct scanout attempts to reduce lag when there is only one fullscreen application on a screen (e.g. game). It is also " - "recommended to set this to false if the fullscreen application shows graphical glitches. 0 - off, 1 - on, 2 - auto (on with content type 'game')", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{.value = 0, .min = 0, .max = 2}, - }, - SConfigOptionDescription{ - .value = "render:expand_undersized_textures", - .description = "Whether to expand textures that have not yet resized to be larger, or to just stretch them instead.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "render:xp_mode", - .description = "Disable back buffer and bottom layer rendering.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "render:ctm_animation", - .description = "Whether to enable a fade animation for CTM changes (hyprsunset). 2 means 'auto' (Yes on everything but Nvidia).", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{2, 0, 2}, - }, - SConfigOptionDescription{ - .value = "render:cm_fs_passthrough", - .description = "Passthrough color settings for fullscreen apps when possible", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{.value = 2, .min = 0, .max = 2}, - }, - SConfigOptionDescription{ - .value = "render:cm_enabled", - .description = "Enable Color Management pipelines (requires restart to fully take effect)", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "render:send_content_type", - .description = "Report content type to allow monitor profile autoswitch (may result in a black screen during the switch)", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "render:cm_auto_hdr", - .description = "Auto-switch to hdr mode when fullscreen app is in hdr, 0 - off, 1 - hdr, 2 - hdredid (cm_fs_passthrough can switch to hdr even when this setting is off)", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{.value = 1, .min = 0, .max = 2}, - }, - SConfigOptionDescription{ - .value = "render:new_render_scheduling", - .description = "enable new render scheduling, which should improve FPS on underpowered devices. This does not add latency when your PC can keep up.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "render:non_shader_cm", - .description = "Enable CM without shader. 0 - disable, 1 - whenever possible, 2 - DS and passthrough only, 3 - disable and ignore CM issues", - .type = CONFIG_OPTION_CHOICE, - .data = SConfigOptionDescription::SChoiceData{0, "disable,always,ondemand,ignore"}, - }, - SConfigOptionDescription{ - .value = "render:cm_sdr_eotf", - .description = "Default transfer function for displaying SDR apps. 0 - Treat unspecified as sRGB, 1 - Treat unspecified as Gamma 2.2, 2 - Treat " - "unspecified and sRGB as Gamma 2.2", - .type = CONFIG_OPTION_CHOICE, - .data = SConfigOptionDescription::SChoiceData{0, "srgb,gamma22,gamma22force"}, - }, - - /* - * cursor: - */ - - SConfigOptionDescription{ - .value = "cursor:invisible", - .description = "don't render cursors", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "cursor:no_hardware_cursors", - .description = "disables hardware cursors. Auto = disable when multi-gpu on nvidia", - .type = CONFIG_OPTION_CHOICE, - .data = SConfigOptionDescription::SChoiceData{0, "Disabled,Enabled,Auto"}, - }, - SConfigOptionDescription{ - .value = "cursor:no_break_fs_vrr", - .description = "disables scheduling new frames on cursor movement for fullscreen apps with VRR enabled to avoid framerate spikes (may require no_hardware_cursors = true) " - "0 - off, 1 - on, 2 - auto (on with content type 'game')", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{.value = 2, .min = 0, .max = 2}, - }, - SConfigOptionDescription{ - .value = "cursor:min_refresh_rate", - .description = "minimum refresh rate for cursor movement when no_break_fs_vrr is active. Set to minimum supported refresh rate or higher", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{24, 10, 500}, - }, - SConfigOptionDescription{ - .value = "cursor:hotspot_padding", - .description = "the padding, in logical px, between screen edges and the cursor", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{1, 0, 20}, - }, - SConfigOptionDescription{ - .value = "cursor:inactive_timeout", - .description = "in seconds, after how many seconds of cursor’s inactivity to hide it. Set to 0 for never.", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{0, 0, 20}, - }, - SConfigOptionDescription{ - .value = "cursor:no_warps", - .description = "if true, will not warp the cursor in many cases (focusing, keybinds, etc)", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "cursor:persistent_warps", - .description = "When a window is refocused, the cursor returns to its last position relative to that window, rather than to the centre.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "cursor:warp_on_change_workspace", - .description = "Move the cursor to the last focused window after changing the workspace. Options: 0 (Disabled), 1 (Enabled), 2 (Force - ignores cursor:no_warps option)", - .type = CONFIG_OPTION_CHOICE, - .data = SConfigOptionDescription::SChoiceData{0, "Disabled,Enabled,Force"}, - }, - SConfigOptionDescription{ - .value = "cursor:warp_on_toggle_special", - .description = "Move the cursor to the last focused window when toggling a special workspace. Options: 0 (Disabled), 1 (Enabled), " - "2 (Force - ignores cursor:no_warps option)", - .type = CONFIG_OPTION_CHOICE, - .data = SConfigOptionDescription::SChoiceData{0, "Disabled,Enabled,Force"}, - }, - SConfigOptionDescription{ - .value = "cursor:default_monitor", - .description = "the name of a default monitor for the cursor to be set to on startup (see hyprctl monitors for names)", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? - }, - SConfigOptionDescription{ - .value = "cursor:zoom_factor", - .description = "the factor to zoom by around the cursor. Like a magnifying glass. Minimum 1.0 (meaning no zoom)", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{1, 1, 10}, - }, - SConfigOptionDescription{ - .value = "cursor:zoom_rigid", - .description = "whether the zoom should follow the cursor rigidly (cursor is always centered if it can be) or loosely", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "cursor:zoom_disable_aa", - .description = "If enabled, when zooming, no antialiasing will be used (zoom will be pixelated)", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "cursor:enable_hyprcursor", - .description = "whether to enable hyprcursor support", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "cursor:hide_on_key_press", - .description = "Hides the cursor when you press any key until the mouse is moved.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "cursor:hide_on_touch", - .description = "Hides the cursor when the last input was a touch input until a mouse input is done.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "cursor:use_cpu_buffer", - .description = "Makes HW cursors use a CPU buffer. Required on Nvidia to have HW cursors. Experimental", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "cursor:sync_gsettings_theme", - .description = "sync xcursor theme with gsettings, it applies cursor-theme and cursor-size on theme load to gsettings making most CSD gtk based clients use same xcursor " - "theme and size.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "cursor:warp_back_after_non_mouse_input", - .description = "warp the cursor back to where it was after using a non-mouse input to move it, and then returning back to mouse.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - - /* - * ecosystem: - */ - SConfigOptionDescription{ - .value = "ecosystem:no_update_news", - .description = "disable the popup that shows up when you update hyprland to a new version.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "ecosystem:no_donation_nag", - .description = "disable the popup that shows up twice a year encouraging to donate.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "ecosystem:enforce_permissions", - .description = "whether to enable permission control (see https://wiki.hypr.land/Configuring/Permissions/).", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - - /* - * debug: - */ - - SConfigOptionDescription{ - .value = "debug:overlay", - .description = "print the debug performance overlay. Disable VFR for accurate results.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "debug:damage_blink", - .description = "disable logging to a file", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "debug:disable_logs", - .description = "disable logging to a file", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "debug:disable_time", - .description = "disables time logging", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "debug:damage_tracking", - .description = "redraw only the needed bits of the display. Do not change. (default: full - 2) monitor - 1, none - 0", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{2, 0, 2}, - }, - SConfigOptionDescription{ - .value = "debug:enable_stdout_logs", - .description = "enables logging to stdout", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "debug:manual_crash", - .description = "set to 1 and then back to 0 to crash Hyprland.", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{0, 0, 1}, - }, - SConfigOptionDescription{ - .value = "debug:suppress_errors", - .description = "if true, do not display config file parsing errors.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "debug:disable_scale_checks", - .description = "disables verification of the scale factors. Will result in pixel alignment and rounding errors.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "debug:error_limit", - .description = "limits the number of displayed config file parsing errors.", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{5, 0, 20}, - }, - SConfigOptionDescription{ - .value = "debug:error_position", - .description = "sets the position of the error bar. top - 0, bottom - 1", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{0, 0, 1}, - }, - SConfigOptionDescription{ - .value = "debug:colored_stdout_logs", - .description = "enables colors in the stdout logs.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "debug:log_damage", - .description = "enables logging the damage.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "debug:pass", - .description = "enables render pass debugging.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "debug:full_cm_proto", - .description = "claims support for all cm proto features (requires restart)", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - - /* - * dwindle: - */ - - SConfigOptionDescription{ - .value = "dwindle:pseudotile", - .description = "enable pseudotiling. Pseudotiled windows retain their floating size when tiled.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "dwindle:force_split", - .description = "0 -> split follows mouse, 1 -> always split to the left (new = left or top) 2 -> always split to the right (new = right or bottom)", - .type = CONFIG_OPTION_CHOICE, - .data = SConfigOptionDescription::SChoiceData{0, "follow mouse,left or top,right or bottom"}, - }, - SConfigOptionDescription{ - .value = "dwindle:preserve_split", - .description = "if enabled, the split (side/top) will not change regardless of what happens to the container.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "dwindle:smart_split", - .description = "if enabled, allows a more precise control over the window split direction based on the cursor's position. The window is conceptually divided into four " - "triangles, and cursor's triangle determines the split direction. This feature also turns on preserve_split.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "dwindle:smart_resizing", - .description = - "if enabled, resizing direction will be determined by the mouse's position on the window (nearest to which corner). Else, it is based on the window's tiling position.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "dwindle:permanent_direction_override", - .description = "if enabled, makes the preselect direction persist until either this mode is turned off, another direction is specified, or a non-direction is specified " - "(anything other than l,r,u/t,d/b)", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "dwindle:special_scale_factor", - .description = "specifies the scale factor of windows on the special workspace [0 - 1]", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{1, 0, 1}, - }, - SConfigOptionDescription{ - .value = "dwindle:split_width_multiplier", - .description = "specifies the auto-split width multiplier", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{1, 0.1, 3}, - }, - SConfigOptionDescription{ - .value = "dwindle:use_active_for_splits", - .description = "whether to prefer the active window or the mouse position for splits", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "dwindle:default_split_ratio", - .description = "the default split ratio on window open. 1 means even 50/50 split. [0.1 - 1.9]", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{1, 0.1, 1.9}, - }, - SConfigOptionDescription{ - .value = "dwindle:split_bias", - .description = "specifies which window will receive the split ratio. 0 -> directional (the top or left window), 1 -> the current window", - .type = CONFIG_OPTION_CHOICE, - .data = SConfigOptionDescription::SChoiceData{0, "directional,current"}, - }, - SConfigOptionDescription{ - .value = "dwindle:precise_mouse_move", - .description = "if enabled, bindm movewindow will drop the window more precisely depending on where your mouse is.", - .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: - */ - - SConfigOptionDescription{ - .value = "master:allow_small_split", - .description = "enable adding additional master windows in a horizontal split style", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "master:special_scale_factor", - .description = "the scale of the special workspace windows. [0.0 - 1.0]", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{1, 0, 1}, - }, - SConfigOptionDescription{ - .value = "master:mfact", - .description = - "the size as a percentage of the master window, for example `mfact = 0.70` would mean 70% of the screen will be the master window, and 30% the slave [0.0 - 1.0]", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{0.55, 0, 1}, - }, - SConfigOptionDescription{ - .value = "master:new_status", - .description = "`master`: new window becomes master; `slave`: new windows are added to slave stack; `inherit`: inherit from focused window", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{"slave"}, - }, - SConfigOptionDescription{ - .value = "master:new_on_top", - .description = "whether a newly open window should be on the top of the stack", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "master:new_on_active", - .description = "`before`, `after`: place new window relative to the focused window; `none`: place new window according to the value of `new_on_top`. ", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{"none"}, - }, - SConfigOptionDescription{ - .value = "master:orientation", - .description = "default placement of the master area, can be left, right, top, bottom or center", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{"left"}, - }, - SConfigOptionDescription{ - .value = "master:slave_count_for_center_master", - .description = "when using orientation=center, make the master window centered only when at least this many slave windows are open. (Set 0 to always_center_master)", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{2, 0, 10}, //##TODO RANGE? - }, - SConfigOptionDescription{.value = "master:center_master_fallback", - .description = "Set fallback for center master when slaves are less than slave_count_for_center_master, can be left ,right ,top ,bottom", - .type = CONFIG_OPTION_STRING_SHORT, - .data = SConfigOptionDescription::SStringData{"left"}}, - SConfigOptionDescription{ - .value = "master:center_ignores_reserved", - .description = "centers the master window on monitor ignoring reserved areas", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - SConfigOptionDescription{ - .value = "master:smart_resizing", - .description = - "if enabled, resizing direction will be determined by the mouse's position on the window (nearest to which corner). Else, it is based on the window's tiling position.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "master:drop_at_cursor", - .description = "when enabled, dragging and dropping windows will put them at the cursor position. Otherwise, when dropped at the stack side, they will go to the " - "top/bottom of the stack depending on new_on_top.", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, - SConfigOptionDescription{ - .value = "master:always_keep_position", - .description = "whether to keep the master window in its configured position when there are no slave windows", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, - - /* - * Experimental - */ - - SConfigOptionDescription{ - .value = "experimental:xx_color_management_v4", - .description = "enable color management protocol", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, -}; diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index b6b52d345..5238f9dd1 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -1,3090 +1,46 @@ -#include - #include "ConfigManager.hpp" -#include "ConfigWatcher.hpp" -#include "../managers/KeybindManager.hpp" -#include "../Compositor.hpp" +#include "supplementary/jeremy/Jeremy.hpp" +#include "legacy/ConfigManager.hpp" +#include "../debug/log/Logger.hpp" -#include "../render/decorations/CHyprGroupBarDecoration.hpp" -#include "config/ConfigDataValues.hpp" -#include "config/ConfigValue.hpp" -#include "../protocols/LayerShell.hpp" -#include "../xwayland/XWayland.hpp" -#include "../protocols/OutputManagement.hpp" -#include "../managers/animation/AnimationManager.hpp" -#include "../desktop/LayerSurface.hpp" -#include "../desktop/rule/Engine.hpp" -#include "../desktop/rule/windowRule/WindowRule.hpp" -#include "../desktop/rule/layerRule/LayerRule.hpp" -#include "../debug/HyprCtl.hpp" -#include "../desktop/state/FocusState.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" -#include "../plugins/PluginSystem.hpp" - -#include "../managers/input/trackpad/TrackpadGestures.hpp" -#include "../managers/input/trackpad/gestures/DispatcherGesture.hpp" -#include "../managers/input/trackpad/gestures/WorkspaceSwipeGesture.hpp" -#include "../managers/input/trackpad/gestures/ResizeGesture.hpp" -#include "../managers/input/trackpad/gestures/MoveGesture.hpp" -#include "../managers/input/trackpad/gestures/SpecialWorkspaceGesture.hpp" -#include "../managers/input/trackpad/gestures/CloseGesture.hpp" -#include "../managers/input/trackpad/gestures/FloatGesture.hpp" -#include "../managers/input/trackpad/gestures/FullscreenGesture.hpp" - -#include "../managers/HookSystemManager.hpp" -#include "../protocols/types/ContentType.hpp" -#include -#include #include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include #include -#include -using namespace Hyprutils::String; -using namespace Hyprutils::Animation; -using enum NContentType::eContentType; -//NOLINTNEXTLINE -extern "C" char** environ; +using namespace Config; -#include "ConfigDescriptions.hpp" +static UP g_mgr; -static Hyprlang::CParseResult configHandleGradientSet(const char* VALUE, void** data) { - std::string V = VALUE; - - if (!*data) - *data = new CGradientValueData(); - - const auto DATA = sc(*data); - - CVarList2 varlist(std::string(V), 0, ' '); - DATA->m_colors.clear(); - - std::string parseError = ""; - - for (auto const& var : varlist) { - if (var.find("deg") != std::string::npos) { - // last arg - try { - DATA->m_angle = std::stoi(std::string(var.substr(0, var.find("deg")))) * (PI / 180.0); // radians - } catch (...) { - Debug::log(WARN, "Error parsing gradient {}", V); - parseError = "Error parsing gradient " + V; - } - - break; - } - - if (DATA->m_colors.size() >= 10) { - Debug::log(WARN, "Error parsing gradient {}: max colors is 10.", V); - parseError = "Error parsing gradient " + V + ": max colors is 10."; - break; - } - - try { - const auto COL = configStringToInt(std::string(var)); - if (!COL) - throw std::runtime_error(std::format("failed to parse {} as a color", var)); - DATA->m_colors.emplace_back(COL.value()); - } catch (std::exception& e) { - Debug::log(WARN, "Error parsing gradient {}", V); - parseError = "Error parsing gradient " + V + ": " + e.what(); - } - } - - if (DATA->m_colors.empty()) { - Debug::log(WARN, "Error parsing gradient {}", V); - if (parseError.empty()) - parseError = "Error parsing gradient " + V + ": No colors?"; - - DATA->m_colors.emplace_back(0); // transparent - } - - DATA->updateColorsOk(); - - Hyprlang::CParseResult result; - if (!parseError.empty()) - result.setError(parseError.c_str()); - - return result; -} - -static void configHandleGradientDestroy(void** data) { - if (*data) - delete sc(*data); -} - -static Hyprlang::CParseResult configHandleGapSet(const char* VALUE, void** data) { - std::string V = VALUE; - - if (!*data) - *data = new CCssGapData(); - - const auto DATA = sc(*data); - CVarList2 varlist((std::string(V))); - Hyprlang::CParseResult result; - - try { - DATA->parseGapData(varlist); - } catch (...) { - std::string parseError = "Error parsing gaps " + V; - result.setError(parseError.c_str()); - } - - return result; -} - -static void configHandleGapDestroy(void** data) { - if (*data) - delete sc(*data); -} - -static Hyprlang::CParseResult configHandleFontWeightSet(const char* VALUE, void** data) { - if (!*data) - *data = new CFontWeightConfigValueData(); - - const auto DATA = sc(*data); - Hyprlang::CParseResult result; - - try { - DATA->parseWeight(VALUE); - } catch (...) { - std::string parseError = std::format("{} is not a valid font weight", VALUE); - result.setError(parseError.c_str()); - } - - return result; -} - -static void configHandleFontWeightDestroy(void** data) { - if (*data) - delete sc(*data); -} - -static Hyprlang::CParseResult handleExec(const char* c, const char* v) { - const std::string VALUE = v; - const std::string COMMAND = c; - - const auto RESULT = g_pConfigManager->handleExec(COMMAND, VALUE); - - Hyprlang::CParseResult result; - if (RESULT.has_value()) - result.setError(RESULT.value().c_str()); - return result; -} - -static Hyprlang::CParseResult handleRawExec(const char* c, const char* v) { - const std::string VALUE = v; - const std::string COMMAND = c; - - const auto RESULT = g_pConfigManager->handleRawExec(COMMAND, VALUE); - - Hyprlang::CParseResult result; - if (RESULT.has_value()) - result.setError(RESULT.value().c_str()); - return result; -} - -static Hyprlang::CParseResult handleExecOnce(const char* c, const char* v) { - const std::string VALUE = v; - const std::string COMMAND = c; - - const auto RESULT = g_pConfigManager->handleExecOnce(COMMAND, VALUE); - - Hyprlang::CParseResult result; - if (RESULT.has_value()) - result.setError(RESULT.value().c_str()); - return result; -} - -static Hyprlang::CParseResult handleExecRawOnce(const char* c, const char* v) { - const std::string VALUE = v; - const std::string COMMAND = c; - - const auto RESULT = g_pConfigManager->handleExecRawOnce(COMMAND, VALUE); - - Hyprlang::CParseResult result; - if (RESULT.has_value()) - result.setError(RESULT.value().c_str()); - return result; -} - -static Hyprlang::CParseResult handleExecShutdown(const char* c, const char* v) { - const std::string VALUE = v; - const std::string COMMAND = c; - - const auto RESULT = g_pConfigManager->handleExecShutdown(COMMAND, VALUE); - - Hyprlang::CParseResult result; - if (RESULT.has_value()) - result.setError(RESULT.value().c_str()); - return result; -} - -static Hyprlang::CParseResult handleMonitor(const char* c, const char* v) { - const std::string VALUE = v; - const std::string COMMAND = c; - - const auto RESULT = g_pConfigManager->handleMonitor(COMMAND, VALUE); - - Hyprlang::CParseResult result; - if (RESULT.has_value()) - result.setError(RESULT.value().c_str()); - return result; -} - -static Hyprlang::CParseResult handleBezier(const char* c, const char* v) { - const std::string VALUE = v; - const std::string COMMAND = c; - - const auto RESULT = g_pConfigManager->handleBezier(COMMAND, VALUE); - - Hyprlang::CParseResult result; - if (RESULT.has_value()) - result.setError(RESULT.value().c_str()); - return result; -} - -static Hyprlang::CParseResult handleAnimation(const char* c, const char* v) { - const std::string VALUE = v; - const std::string COMMAND = c; - - const auto RESULT = g_pConfigManager->handleAnimation(COMMAND, VALUE); - - Hyprlang::CParseResult result; - if (RESULT.has_value()) - result.setError(RESULT.value().c_str()); - return result; -} - -static Hyprlang::CParseResult handleBind(const char* c, const char* v) { - const std::string VALUE = v; - const std::string COMMAND = c; - - const auto RESULT = g_pConfigManager->handleBind(COMMAND, VALUE); - - Hyprlang::CParseResult result; - if (RESULT.has_value()) - result.setError(RESULT.value().c_str()); - return result; -} - -static Hyprlang::CParseResult handleUnbind(const char* c, const char* v) { - const std::string VALUE = v; - const std::string COMMAND = c; - - const auto RESULT = g_pConfigManager->handleUnbind(COMMAND, VALUE); - - Hyprlang::CParseResult result; - if (RESULT.has_value()) - result.setError(RESULT.value().c_str()); - return result; -} - -static Hyprlang::CParseResult handleWorkspaceRules(const char* c, const char* v) { - const std::string VALUE = v; - const std::string COMMAND = c; - - const auto RESULT = g_pConfigManager->handleWorkspaceRules(COMMAND, VALUE); - - Hyprlang::CParseResult result; - if (RESULT.has_value()) - result.setError(RESULT.value().c_str()); - return result; -} - -static Hyprlang::CParseResult handleSubmap(const char* c, const char* v) { - const std::string VALUE = v; - const std::string COMMAND = c; - - const auto RESULT = g_pConfigManager->handleSubmap(COMMAND, VALUE); - - Hyprlang::CParseResult result; - if (RESULT.has_value()) - result.setError(RESULT.value().c_str()); - return result; -} - -static Hyprlang::CParseResult handleSource(const char* c, const char* v) { - const std::string VALUE = v; - const std::string COMMAND = c; - - const auto RESULT = g_pConfigManager->handleSource(COMMAND, VALUE); - - Hyprlang::CParseResult result; - if (RESULT.has_value()) - result.setError(RESULT.value().c_str()); - return result; -} - -static Hyprlang::CParseResult handleEnv(const char* c, const char* v) { - const std::string VALUE = v; - const std::string COMMAND = c; - - const auto RESULT = g_pConfigManager->handleEnv(COMMAND, VALUE); - - Hyprlang::CParseResult result; - if (RESULT.has_value()) - result.setError(RESULT.value().c_str()); - return result; -} - -static Hyprlang::CParseResult handlePlugin(const char* c, const char* v) { - const std::string VALUE = v; - const std::string COMMAND = c; - - const auto RESULT = g_pConfigManager->handlePlugin(COMMAND, VALUE); - - Hyprlang::CParseResult result; - if (RESULT.has_value()) - result.setError(RESULT.value().c_str()); - return result; -} - -static Hyprlang::CParseResult handlePermission(const char* c, const char* v) { - const std::string VALUE = v; - const std::string COMMAND = c; - - const auto RESULT = g_pConfigManager->handlePermission(COMMAND, VALUE); - - Hyprlang::CParseResult result; - if (RESULT.has_value()) - result.setError(RESULT.value().c_str()); - return result; -} - -static Hyprlang::CParseResult handleGesture(const char* c, const char* v) { - const std::string VALUE = v; - const std::string COMMAND = c; - - const auto RESULT = g_pConfigManager->handleGesture(COMMAND, VALUE); - - Hyprlang::CParseResult result; - if (RESULT.has_value()) - result.setError(RESULT.value().c_str()); - return result; -} - -static Hyprlang::CParseResult handleWindowrule(const char* c, const char* v) { - const std::string VALUE = v; - const std::string COMMAND = c; - - const auto RESULT = g_pConfigManager->handleWindowrule(COMMAND, VALUE); - - Hyprlang::CParseResult result; - if (RESULT.has_value()) - result.setError(RESULT.value().c_str()); - return result; -} - -static Hyprlang::CParseResult handleLayerrule(const char* c, const char* v) { - const std::string VALUE = v; - const std::string COMMAND = c; - - const auto RESULT = g_pConfigManager->handleLayerrule(COMMAND, VALUE); - - Hyprlang::CParseResult result; - if (RESULT.has_value()) - result.setError(RESULT.value().c_str()); - return result; -} - -void CConfigManager::registerConfigVar(const char* name, const Hyprlang::INT& val) { - m_configValueNumber++; - m_config->addConfigValue(name, val); -} - -void CConfigManager::registerConfigVar(const char* name, const Hyprlang::FLOAT& val) { - m_configValueNumber++; - m_config->addConfigValue(name, val); -} - -void CConfigManager::registerConfigVar(const char* name, const Hyprlang::VEC2& val) { - m_configValueNumber++; - m_config->addConfigValue(name, val); -} - -void CConfigManager::registerConfigVar(const char* name, const Hyprlang::STRING& val) { - m_configValueNumber++; - m_config->addConfigValue(name, val); -} - -void CConfigManager::registerConfigVar(const char* name, Hyprlang::CUSTOMTYPE&& val) { - m_configValueNumber++; - m_config->addConfigValue(name, std::move(val)); -} - -CConfigManager::CConfigManager() { - const auto ERR = verifyConfigExists(); - - m_configPaths.emplace_back(getMainConfigPath()); - m_config = makeUnique(m_configPaths.begin()->c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = true}); - - registerConfigVar("general:border_size", Hyprlang::INT{1}); - registerConfigVar("general:gaps_in", Hyprlang::CConfigCustomValueType{configHandleGapSet, configHandleGapDestroy, "5"}); - registerConfigVar("general:gaps_out", Hyprlang::CConfigCustomValueType{configHandleGapSet, configHandleGapDestroy, "20"}); - registerConfigVar("general:float_gaps", Hyprlang::CConfigCustomValueType{configHandleGapSet, configHandleGapDestroy, "0"}); - registerConfigVar("general:gaps_workspaces", Hyprlang::INT{0}); - registerConfigVar("general:no_focus_fallback", Hyprlang::INT{0}); - registerConfigVar("general:resize_on_border", Hyprlang::INT{0}); - registerConfigVar("general:extend_border_grab_area", Hyprlang::INT{15}); - registerConfigVar("general:hover_icon_on_border", Hyprlang::INT{1}); - registerConfigVar("general:layout", {"dwindle"}); - registerConfigVar("general:allow_tearing", Hyprlang::INT{0}); - registerConfigVar("general:resize_corner", Hyprlang::INT{0}); - registerConfigVar("general:snap:enabled", Hyprlang::INT{0}); - registerConfigVar("general:snap:window_gap", Hyprlang::INT{10}); - registerConfigVar("general:snap:monitor_gap", Hyprlang::INT{10}); - registerConfigVar("general:snap:border_overlap", Hyprlang::INT{0}); - registerConfigVar("general:snap:respect_gaps", Hyprlang::INT{0}); - registerConfigVar("general:col.active_border", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0xffffffff"}); - registerConfigVar("general:col.inactive_border", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0xff444444"}); - registerConfigVar("general:col.nogroup_border", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0xffffaaff"}); - registerConfigVar("general:col.nogroup_border_active", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0xffff00ff"}); - registerConfigVar("general:modal_parent_blocking", Hyprlang::INT{1}); - registerConfigVar("general:locale", {""}); - - registerConfigVar("misc:disable_hyprland_logo", Hyprlang::INT{0}); - registerConfigVar("misc:disable_splash_rendering", Hyprlang::INT{0}); - registerConfigVar("misc:col.splash", Hyprlang::INT{0x55ffffff}); - registerConfigVar("misc:splash_font_family", {STRVAL_EMPTY}); - registerConfigVar("misc:font_family", {"Sans"}); - registerConfigVar("misc:force_default_wallpaper", Hyprlang::INT{-1}); - registerConfigVar("misc:vfr", Hyprlang::INT{1}); - registerConfigVar("misc:vrr", Hyprlang::INT{0}); - registerConfigVar("misc:mouse_move_enables_dpms", Hyprlang::INT{0}); - registerConfigVar("misc:key_press_enables_dpms", Hyprlang::INT{0}); - registerConfigVar("misc:name_vk_after_proc", Hyprlang::INT{1}); - registerConfigVar("misc:always_follow_on_dnd", Hyprlang::INT{1}); - registerConfigVar("misc:layers_hog_keyboard_focus", Hyprlang::INT{1}); - registerConfigVar("misc:animate_manual_resizes", Hyprlang::INT{0}); - registerConfigVar("misc:animate_mouse_windowdragging", Hyprlang::INT{0}); - registerConfigVar("misc:disable_autoreload", Hyprlang::INT{0}); - registerConfigVar("misc:enable_swallow", Hyprlang::INT{0}); - registerConfigVar("misc:swallow_regex", {STRVAL_EMPTY}); - registerConfigVar("misc:swallow_exception_regex", {STRVAL_EMPTY}); - registerConfigVar("misc:focus_on_activate", Hyprlang::INT{0}); - registerConfigVar("misc:mouse_move_focuses_monitor", Hyprlang::INT{1}); - registerConfigVar("misc:allow_session_lock_restore", Hyprlang::INT{0}); - registerConfigVar("misc:session_lock_xray", Hyprlang::INT{0}); - registerConfigVar("misc:close_special_on_empty", Hyprlang::INT{1}); - registerConfigVar("misc:background_color", Hyprlang::INT{0xff111111}); - registerConfigVar("misc:on_focus_under_fullscreen", Hyprlang::INT{2}); - registerConfigVar("misc:exit_window_retains_fullscreen", Hyprlang::INT{0}); - registerConfigVar("misc:initial_workspace_tracking", Hyprlang::INT{1}); - registerConfigVar("misc:middle_click_paste", Hyprlang::INT{1}); - registerConfigVar("misc:render_unfocused_fps", Hyprlang::INT{15}); - registerConfigVar("misc:disable_xdg_env_checks", Hyprlang::INT{0}); - registerConfigVar("misc:disable_hyprland_guiutils_check", Hyprlang::INT{0}); - registerConfigVar("misc:lockdead_screen_delay", Hyprlang::INT{1000}); - registerConfigVar("misc:enable_anr_dialog", Hyprlang::INT{1}); - registerConfigVar("misc:anr_missed_pings", Hyprlang::INT{5}); - registerConfigVar("misc:screencopy_force_8b", Hyprlang::INT{1}); - registerConfigVar("misc:disable_scale_notification", Hyprlang::INT{0}); - registerConfigVar("misc:size_limits_tiled", Hyprlang::INT{0}); - - registerConfigVar("group:insert_after_current", Hyprlang::INT{1}); - registerConfigVar("group:focus_removed_window", Hyprlang::INT{1}); - registerConfigVar("group:merge_groups_on_drag", Hyprlang::INT{1}); - registerConfigVar("group:merge_groups_on_groupbar", Hyprlang::INT{1}); - registerConfigVar("group:merge_floated_into_tiled_on_groupbar", Hyprlang::INT{0}); - registerConfigVar("group:auto_group", Hyprlang::INT{1}); - registerConfigVar("group:drag_into_group", Hyprlang::INT{1}); - registerConfigVar("group:group_on_movetoworkspace", Hyprlang::INT{0}); - registerConfigVar("group:groupbar:enabled", Hyprlang::INT{1}); - registerConfigVar("group:groupbar:font_family", {STRVAL_EMPTY}); - registerConfigVar("group:groupbar:font_weight_active", Hyprlang::CConfigCustomValueType{&configHandleFontWeightSet, configHandleFontWeightDestroy, "normal"}); - registerConfigVar("group:groupbar:font_weight_inactive", Hyprlang::CConfigCustomValueType{&configHandleFontWeightSet, configHandleFontWeightDestroy, "normal"}); - registerConfigVar("group:groupbar:font_size", Hyprlang::INT{8}); - registerConfigVar("group:groupbar:gradients", Hyprlang::INT{0}); - registerConfigVar("group:groupbar:height", Hyprlang::INT{14}); - registerConfigVar("group:groupbar:indicator_gap", Hyprlang::INT{0}); - registerConfigVar("group:groupbar:indicator_height", Hyprlang::INT{3}); - registerConfigVar("group:groupbar:priority", Hyprlang::INT{3}); - registerConfigVar("group:groupbar:render_titles", Hyprlang::INT{1}); - registerConfigVar("group:groupbar:scrolling", Hyprlang::INT{1}); - registerConfigVar("group:groupbar:text_color", Hyprlang::INT{0xffffffff}); - registerConfigVar("group:groupbar:text_color_inactive", Hyprlang::INT{-1}); - registerConfigVar("group:groupbar:text_color_locked_active", Hyprlang::INT{-1}); - registerConfigVar("group:groupbar:text_color_locked_inactive", Hyprlang::INT{-1}); - registerConfigVar("group:groupbar:stacked", Hyprlang::INT{0}); - registerConfigVar("group:groupbar:rounding", Hyprlang::INT{1}); - registerConfigVar("group:groupbar:rounding_power", {2.F}); - registerConfigVar("group:groupbar:gradient_rounding", Hyprlang::INT{2}); - registerConfigVar("group:groupbar:gradient_rounding_power", {2.F}); - registerConfigVar("group:groupbar:round_only_edges", Hyprlang::INT{1}); - registerConfigVar("group:groupbar:gradient_round_only_edges", Hyprlang::INT{1}); - registerConfigVar("group:groupbar:gaps_out", Hyprlang::INT{2}); - 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: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:disable_logs", Hyprlang::INT{1}); - registerConfigVar("debug:disable_time", Hyprlang::INT{1}); - registerConfigVar("debug:enable_stdout_logs", Hyprlang::INT{0}); - registerConfigVar("debug:damage_tracking", {sc(DAMAGE_TRACKING_FULL)}); - registerConfigVar("debug:manual_crash", Hyprlang::INT{0}); - registerConfigVar("debug:suppress_errors", Hyprlang::INT{0}); - registerConfigVar("debug:error_limit", Hyprlang::INT{5}); - registerConfigVar("debug:error_position", Hyprlang::INT{0}); - 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("decoration:rounding", Hyprlang::INT{0}); - registerConfigVar("decoration:rounding_power", {2.F}); - registerConfigVar("decoration:blur:enabled", Hyprlang::INT{1}); - registerConfigVar("decoration:blur:size", Hyprlang::INT{8}); - registerConfigVar("decoration:blur:passes", Hyprlang::INT{1}); - registerConfigVar("decoration:blur:ignore_opacity", Hyprlang::INT{1}); - registerConfigVar("decoration:blur:new_optimizations", Hyprlang::INT{1}); - registerConfigVar("decoration:blur:xray", Hyprlang::INT{0}); - registerConfigVar("decoration:blur:contrast", {0.8916F}); - registerConfigVar("decoration:blur:brightness", {1.0F}); - registerConfigVar("decoration:blur:vibrancy", {0.1696F}); - registerConfigVar("decoration:blur:vibrancy_darkness", {0.0F}); - registerConfigVar("decoration:blur:noise", {0.0117F}); - registerConfigVar("decoration:blur:special", Hyprlang::INT{0}); - registerConfigVar("decoration:blur:popups", Hyprlang::INT{0}); - registerConfigVar("decoration:blur:popups_ignorealpha", {0.2F}); - registerConfigVar("decoration:blur:input_methods", Hyprlang::INT{0}); - registerConfigVar("decoration:blur:input_methods_ignorealpha", {0.2F}); - registerConfigVar("decoration:active_opacity", {1.F}); - registerConfigVar("decoration:inactive_opacity", {1.F}); - registerConfigVar("decoration:fullscreen_opacity", {1.F}); - registerConfigVar("decoration:shadow:enabled", Hyprlang::INT{1}); - registerConfigVar("decoration:shadow:range", Hyprlang::INT{4}); - registerConfigVar("decoration:shadow:render_power", Hyprlang::INT{3}); - registerConfigVar("decoration:shadow:ignore_window", Hyprlang::INT{1}); - registerConfigVar("decoration:shadow:offset", Hyprlang::VEC2{0, 0}); - registerConfigVar("decoration:shadow:scale", {1.f}); - registerConfigVar("decoration:shadow:sharp", Hyprlang::INT{0}); - registerConfigVar("decoration:shadow:color", Hyprlang::INT{0xee1a1a1a}); - registerConfigVar("decoration:shadow:color_inactive", Hyprlang::INT{-1}); - registerConfigVar("decoration:dim_inactive", Hyprlang::INT{0}); - registerConfigVar("decoration:dim_modal", Hyprlang::INT{1}); - registerConfigVar("decoration:dim_strength", {0.5f}); - registerConfigVar("decoration:dim_special", {0.2f}); - registerConfigVar("decoration:dim_around", {0.4f}); - registerConfigVar("decoration:screen_shader", {STRVAL_EMPTY}); - registerConfigVar("decoration:border_part_of_window", Hyprlang::INT{1}); - - registerConfigVar("dwindle:pseudotile", Hyprlang::INT{0}); - registerConfigVar("dwindle:force_split", Hyprlang::INT{0}); - registerConfigVar("dwindle:permanent_direction_override", Hyprlang::INT{0}); - registerConfigVar("dwindle:preserve_split", Hyprlang::INT{0}); - registerConfigVar("dwindle:special_scale_factor", {1.f}); - registerConfigVar("dwindle:split_width_multiplier", {1.0f}); - registerConfigVar("dwindle:use_active_for_splits", Hyprlang::INT{1}); - registerConfigVar("dwindle:default_split_ratio", {1.f}); - registerConfigVar("dwindle:split_bias", Hyprlang::INT{0}); - 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}); - registerConfigVar("master:new_status", {"slave"}); - registerConfigVar("master:slave_count_for_center_master", Hyprlang::INT{2}); - registerConfigVar("master:center_master_fallback", {"left"}); - registerConfigVar("master:center_ignores_reserved", Hyprlang::INT{0}); - registerConfigVar("master:new_on_active", {"none"}); - registerConfigVar("master:new_on_top", Hyprlang::INT{0}); - registerConfigVar("master:orientation", {"left"}); - registerConfigVar("master:allow_small_split", Hyprlang::INT{0}); - registerConfigVar("master:smart_resizing", Hyprlang::INT{1}); - registerConfigVar("master:drop_at_cursor", Hyprlang::INT{1}); - registerConfigVar("master:always_keep_position", Hyprlang::INT{0}); - - registerConfigVar("animations:enabled", Hyprlang::INT{1}); - registerConfigVar("animations:workspace_wraparound", Hyprlang::INT{0}); - - registerConfigVar("input:follow_mouse", Hyprlang::INT{1}); - registerConfigVar("input:follow_mouse_threshold", Hyprlang::FLOAT{0}); - registerConfigVar("input:focus_on_close", Hyprlang::INT{0}); - registerConfigVar("input:mouse_refocus", Hyprlang::INT{1}); - registerConfigVar("input:special_fallthrough", Hyprlang::INT{0}); - registerConfigVar("input:off_window_axis_events", Hyprlang::INT{1}); - registerConfigVar("input:sensitivity", {0.f}); - registerConfigVar("input:accel_profile", {STRVAL_EMPTY}); - registerConfigVar("input:rotation", Hyprlang::INT{0}); - registerConfigVar("input:kb_file", {STRVAL_EMPTY}); - registerConfigVar("input:kb_layout", {"us"}); - registerConfigVar("input:kb_variant", {STRVAL_EMPTY}); - registerConfigVar("input:kb_options", {STRVAL_EMPTY}); - registerConfigVar("input:kb_rules", {STRVAL_EMPTY}); - registerConfigVar("input:kb_model", {STRVAL_EMPTY}); - registerConfigVar("input:repeat_rate", Hyprlang::INT{25}); - registerConfigVar("input:repeat_delay", Hyprlang::INT{600}); - registerConfigVar("input:natural_scroll", Hyprlang::INT{0}); - registerConfigVar("input:numlock_by_default", Hyprlang::INT{0}); - registerConfigVar("input:resolve_binds_by_sym", Hyprlang::INT{0}); - registerConfigVar("input:force_no_accel", Hyprlang::INT{0}); - registerConfigVar("input:float_switch_override_focus", Hyprlang::INT{1}); - registerConfigVar("input:left_handed", Hyprlang::INT{0}); - registerConfigVar("input:scroll_method", {STRVAL_EMPTY}); - registerConfigVar("input:scroll_button", Hyprlang::INT{0}); - registerConfigVar("input:scroll_button_lock", Hyprlang::INT{0}); - registerConfigVar("input:scroll_factor", {1.f}); - registerConfigVar("input:scroll_points", {STRVAL_EMPTY}); - registerConfigVar("input:emulate_discrete_scroll", Hyprlang::INT{1}); - registerConfigVar("input:touchpad:natural_scroll", Hyprlang::INT{0}); - registerConfigVar("input:touchpad:disable_while_typing", Hyprlang::INT{1}); - registerConfigVar("input:touchpad:clickfinger_behavior", Hyprlang::INT{0}); - registerConfigVar("input:touchpad:tap_button_map", {STRVAL_EMPTY}); - registerConfigVar("input:touchpad:middle_button_emulation", Hyprlang::INT{0}); - registerConfigVar("input:touchpad:tap-to-click", Hyprlang::INT{1}); - registerConfigVar("input:touchpad:tap-and-drag", Hyprlang::INT{1}); - registerConfigVar("input:touchpad:drag_lock", Hyprlang::INT{0}); - registerConfigVar("input:touchpad:scroll_factor", {1.f}); - registerConfigVar("input:touchpad:flip_x", Hyprlang::INT{0}); - registerConfigVar("input:touchpad:flip_y", Hyprlang::INT{0}); - registerConfigVar("input:touchpad:drag_3fg", Hyprlang::INT{0}); - registerConfigVar("input:touchdevice:transform", Hyprlang::INT{-1}); - registerConfigVar("input:touchdevice:output", {"[[Auto]]"}); - registerConfigVar("input:touchdevice:enabled", Hyprlang::INT{1}); - registerConfigVar("input:virtualkeyboard:share_states", Hyprlang::INT{2}); - registerConfigVar("input:virtualkeyboard:release_pressed_on_close", Hyprlang::INT{0}); - registerConfigVar("input:tablet:transform", Hyprlang::INT{0}); - registerConfigVar("input:tablet:output", {STRVAL_EMPTY}); - registerConfigVar("input:tablet:region_position", Hyprlang::VEC2{0, 0}); - registerConfigVar("input:tablet:absolute_region_position", Hyprlang::INT{0}); - registerConfigVar("input:tablet:region_size", Hyprlang::VEC2{0, 0}); - registerConfigVar("input:tablet:relative_input", Hyprlang::INT{0}); - registerConfigVar("input:tablet:left_handed", Hyprlang::INT{0}); - registerConfigVar("input:tablet:active_area_position", Hyprlang::VEC2{0, 0}); - registerConfigVar("input:tablet:active_area_size", Hyprlang::VEC2{0, 0}); - - registerConfigVar("binds:pass_mouse_when_bound", Hyprlang::INT{0}); - registerConfigVar("binds:scroll_event_delay", Hyprlang::INT{300}); - registerConfigVar("binds:workspace_back_and_forth", Hyprlang::INT{0}); - registerConfigVar("binds:hide_special_on_workspace_change", Hyprlang::INT{0}); - registerConfigVar("binds:allow_workspace_cycles", Hyprlang::INT{0}); - registerConfigVar("binds:workspace_center_on", Hyprlang::INT{1}); - registerConfigVar("binds:focus_preferred_method", Hyprlang::INT{0}); - registerConfigVar("binds:ignore_group_lock", Hyprlang::INT{0}); - 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("gestures:workspace_swipe_distance", Hyprlang::INT{300}); - registerConfigVar("gestures:workspace_swipe_invert", Hyprlang::INT{1}); - registerConfigVar("gestures:workspace_swipe_min_speed_to_force", Hyprlang::INT{30}); - registerConfigVar("gestures:workspace_swipe_cancel_ratio", {0.5f}); - registerConfigVar("gestures:workspace_swipe_create_new", Hyprlang::INT{1}); - registerConfigVar("gestures:workspace_swipe_direction_lock", Hyprlang::INT{1}); - registerConfigVar("gestures:workspace_swipe_direction_lock_threshold", Hyprlang::INT{10}); - registerConfigVar("gestures:workspace_swipe_forever", Hyprlang::INT{0}); - registerConfigVar("gestures:workspace_swipe_use_r", Hyprlang::INT{0}); - registerConfigVar("gestures:workspace_swipe_touch", Hyprlang::INT{0}); - registerConfigVar("gestures:workspace_swipe_touch_invert", Hyprlang::INT{0}); - registerConfigVar("gestures:close_max_timeout", Hyprlang::INT{1000}); - - registerConfigVar("xwayland:enabled", Hyprlang::INT{1}); - registerConfigVar("xwayland:use_nearest_neighbor", Hyprlang::INT{1}); - registerConfigVar("xwayland:force_zero_scaling", Hyprlang::INT{0}); - registerConfigVar("xwayland:create_abstract_socket", Hyprlang::INT{0}); - - registerConfigVar("opengl:nvidia_anti_flicker", Hyprlang::INT{1}); - - registerConfigVar("cursor:invisible", Hyprlang::INT{0}); - registerConfigVar("cursor:no_hardware_cursors", Hyprlang::INT{2}); - registerConfigVar("cursor:no_break_fs_vrr", Hyprlang::INT{2}); - registerConfigVar("cursor:min_refresh_rate", Hyprlang::INT{24}); - registerConfigVar("cursor:hotspot_padding", Hyprlang::INT{0}); - registerConfigVar("cursor:inactive_timeout", {0.f}); - registerConfigVar("cursor:no_warps", Hyprlang::INT{0}); - registerConfigVar("cursor:persistent_warps", Hyprlang::INT{0}); - registerConfigVar("cursor:warp_on_change_workspace", Hyprlang::INT{0}); - registerConfigVar("cursor:warp_on_toggle_special", Hyprlang::INT{0}); - registerConfigVar("cursor:default_monitor", {STRVAL_EMPTY}); - registerConfigVar("cursor:zoom_factor", {1.f}); - registerConfigVar("cursor:zoom_rigid", Hyprlang::INT{0}); - registerConfigVar("cursor:zoom_disable_aa", Hyprlang::INT{0}); - registerConfigVar("cursor:enable_hyprcursor", Hyprlang::INT{1}); - registerConfigVar("cursor:sync_gsettings_theme", Hyprlang::INT{1}); - registerConfigVar("cursor:hide_on_key_press", Hyprlang::INT{0}); - registerConfigVar("cursor:hide_on_touch", Hyprlang::INT{1}); - registerConfigVar("cursor:use_cpu_buffer", Hyprlang::INT{2}); - registerConfigVar("cursor:warp_back_after_non_mouse_input", Hyprlang::INT{0}); - - registerConfigVar("autogenerated", Hyprlang::INT{0}); - - registerConfigVar("group:col.border_active", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66ffff00"}); - registerConfigVar("group:col.border_inactive", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66777700"}); - registerConfigVar("group:col.border_locked_active", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66ff5500"}); - registerConfigVar("group:col.border_locked_inactive", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66775500"}); - - registerConfigVar("group:groupbar:col.active", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66ffff00"}); - registerConfigVar("group:groupbar:col.inactive", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66777700"}); - registerConfigVar("group:groupbar:col.locked_active", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66ff5500"}); - registerConfigVar("group:groupbar:col.locked_inactive", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66775500"}); - - registerConfigVar("render:direct_scanout", Hyprlang::INT{0}); - registerConfigVar("render:expand_undersized_textures", Hyprlang::INT{1}); - registerConfigVar("render:xp_mode", Hyprlang::INT{0}); - registerConfigVar("render:ctm_animation", Hyprlang::INT{2}); - registerConfigVar("render:cm_fs_passthrough", Hyprlang::INT{2}); - registerConfigVar("render:cm_enabled", Hyprlang::INT{1}); - registerConfigVar("render:send_content_type", Hyprlang::INT{1}); - registerConfigVar("render:cm_auto_hdr", Hyprlang::INT{1}); - registerConfigVar("render:new_render_scheduling", Hyprlang::INT{0}); - registerConfigVar("render:non_shader_cm", Hyprlang::INT{3}); - registerConfigVar("render:cm_sdr_eotf", Hyprlang::INT{0}); - - registerConfigVar("ecosystem:no_update_news", Hyprlang::INT{0}); - registerConfigVar("ecosystem:no_donation_nag", Hyprlang::INT{0}); - registerConfigVar("ecosystem:enforce_permissions", Hyprlang::INT{0}); - - registerConfigVar("experimental:xx_color_management_v4", Hyprlang::INT{0}); - - // devices - m_config->addSpecialCategory("device", {"name"}); - m_config->addSpecialConfigValue("device", "sensitivity", {0.F}); - m_config->addSpecialConfigValue("device", "accel_profile", {STRVAL_EMPTY}); - m_config->addSpecialConfigValue("device", "rotation", Hyprlang::INT{0}); - m_config->addSpecialConfigValue("device", "kb_file", {STRVAL_EMPTY}); - m_config->addSpecialConfigValue("device", "kb_layout", {"us"}); - m_config->addSpecialConfigValue("device", "kb_variant", {STRVAL_EMPTY}); - m_config->addSpecialConfigValue("device", "kb_options", {STRVAL_EMPTY}); - m_config->addSpecialConfigValue("device", "kb_rules", {STRVAL_EMPTY}); - m_config->addSpecialConfigValue("device", "kb_model", {STRVAL_EMPTY}); - m_config->addSpecialConfigValue("device", "repeat_rate", Hyprlang::INT{25}); - m_config->addSpecialConfigValue("device", "repeat_delay", Hyprlang::INT{600}); - m_config->addSpecialConfigValue("device", "natural_scroll", Hyprlang::INT{0}); - m_config->addSpecialConfigValue("device", "tap_button_map", {STRVAL_EMPTY}); - m_config->addSpecialConfigValue("device", "numlock_by_default", Hyprlang::INT{0}); - m_config->addSpecialConfigValue("device", "resolve_binds_by_sym", Hyprlang::INT{0}); - m_config->addSpecialConfigValue("device", "disable_while_typing", Hyprlang::INT{1}); - m_config->addSpecialConfigValue("device", "clickfinger_behavior", Hyprlang::INT{0}); - m_config->addSpecialConfigValue("device", "middle_button_emulation", Hyprlang::INT{0}); - m_config->addSpecialConfigValue("device", "tap-to-click", Hyprlang::INT{1}); - m_config->addSpecialConfigValue("device", "tap-and-drag", Hyprlang::INT{1}); - m_config->addSpecialConfigValue("device", "drag_lock", Hyprlang::INT{0}); - m_config->addSpecialConfigValue("device", "left_handed", Hyprlang::INT{0}); - m_config->addSpecialConfigValue("device", "scroll_method", {STRVAL_EMPTY}); - m_config->addSpecialConfigValue("device", "scroll_button", Hyprlang::INT{0}); - m_config->addSpecialConfigValue("device", "scroll_button_lock", Hyprlang::INT{0}); - m_config->addSpecialConfigValue("device", "scroll_points", {STRVAL_EMPTY}); - m_config->addSpecialConfigValue("device", "scroll_factor", Hyprlang::FLOAT{-1}); - m_config->addSpecialConfigValue("device", "transform", Hyprlang::INT{-1}); - m_config->addSpecialConfigValue("device", "output", {STRVAL_EMPTY}); - m_config->addSpecialConfigValue("device", "enabled", Hyprlang::INT{1}); // only for mice, touchpads, and touchdevices - m_config->addSpecialConfigValue("device", "region_position", Hyprlang::VEC2{0, 0}); // only for tablets - m_config->addSpecialConfigValue("device", "absolute_region_position", Hyprlang::INT{0}); // only for tablets - m_config->addSpecialConfigValue("device", "region_size", Hyprlang::VEC2{0, 0}); // only for tablets - m_config->addSpecialConfigValue("device", "relative_input", Hyprlang::INT{0}); // only for tablets - m_config->addSpecialConfigValue("device", "active_area_position", Hyprlang::VEC2{0, 0}); // only for tablets - m_config->addSpecialConfigValue("device", "active_area_size", Hyprlang::VEC2{0, 0}); // only for tablets - m_config->addSpecialConfigValue("device", "flip_x", Hyprlang::INT{0}); // only for touchpads - m_config->addSpecialConfigValue("device", "flip_y", Hyprlang::INT{0}); // only for touchpads - m_config->addSpecialConfigValue("device", "drag_3fg", Hyprlang::INT{0}); // only for touchpads - m_config->addSpecialConfigValue("device", "keybinds", Hyprlang::INT{1}); // enable/disable keybinds - m_config->addSpecialConfigValue("device", "share_states", Hyprlang::INT{0}); // only for virtualkeyboards - m_config->addSpecialConfigValue("device", "release_pressed_on_close", Hyprlang::INT{0}); // only for virtualkeyboards - - m_config->addSpecialCategory("monitorv2", {.key = "output"}); - m_config->addSpecialConfigValue("monitorv2", "disabled", Hyprlang::INT{0}); - m_config->addSpecialConfigValue("monitorv2", "mode", {"preferred"}); - m_config->addSpecialConfigValue("monitorv2", "position", {"auto"}); - m_config->addSpecialConfigValue("monitorv2", "scale", {"auto"}); - m_config->addSpecialConfigValue("monitorv2", "addreserved", {STRVAL_EMPTY}); - m_config->addSpecialConfigValue("monitorv2", "mirror", {STRVAL_EMPTY}); - m_config->addSpecialConfigValue("monitorv2", "bitdepth", {STRVAL_EMPTY}); // TODO use correct type - m_config->addSpecialConfigValue("monitorv2", "cm", {"auto"}); - m_config->addSpecialConfigValue("monitorv2", "sdr_eotf", Hyprlang::INT{0}); - m_config->addSpecialConfigValue("monitorv2", "sdrbrightness", Hyprlang::FLOAT{1.0}); - m_config->addSpecialConfigValue("monitorv2", "sdrsaturation", Hyprlang::FLOAT{1.0}); - m_config->addSpecialConfigValue("monitorv2", "vrr", Hyprlang::INT{0}); - m_config->addSpecialConfigValue("monitorv2", "transform", {STRVAL_EMPTY}); // TODO use correct type - m_config->addSpecialConfigValue("monitorv2", "supports_wide_color", Hyprlang::INT{0}); - m_config->addSpecialConfigValue("monitorv2", "supports_hdr", Hyprlang::INT{0}); - m_config->addSpecialConfigValue("monitorv2", "sdr_min_luminance", Hyprlang::FLOAT{0.2}); - m_config->addSpecialConfigValue("monitorv2", "sdr_max_luminance", Hyprlang::INT{80}); - 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}); - - // windowrule v3 - m_config->addSpecialCategory("windowrule", {.key = "name"}); - m_config->addSpecialConfigValue("windowrule", "enable", Hyprlang::INT{1}); - - // layerrule v2 - m_config->addSpecialCategory("layerrule", {.key = "name"}); - m_config->addSpecialConfigValue("layerrule", "enable", Hyprlang::INT{1}); - - reloadRuleConfigs(); - - // keywords - m_config->registerHandler(&::handleExec, "exec", {false}); - m_config->registerHandler(&::handleRawExec, "execr", {false}); - m_config->registerHandler(&::handleExecOnce, "exec-once", {false}); - m_config->registerHandler(&::handleExecRawOnce, "execr-once", {false}); - m_config->registerHandler(&::handleExecShutdown, "exec-shutdown", {false}); - m_config->registerHandler(&::handleMonitor, "monitor", {false}); - m_config->registerHandler(&::handleBind, "bind", {true}); - m_config->registerHandler(&::handleUnbind, "unbind", {false}); - m_config->registerHandler(&::handleWorkspaceRules, "workspace", {false}); - m_config->registerHandler(&::handleWindowrule, "windowrule", {false}); - m_config->registerHandler(&::handleLayerrule, "layerrule", {false}); - m_config->registerHandler(&::handleBezier, "bezier", {false}); - m_config->registerHandler(&::handleAnimation, "animation", {false}); - m_config->registerHandler(&::handleSource, "source", {false}); - m_config->registerHandler(&::handleSubmap, "submap", {false}); - m_config->registerHandler(&::handlePlugin, "plugin", {false}); - m_config->registerHandler(&::handlePermission, "permission", {false}); - m_config->registerHandler(&::handleGesture, "gesture", {false}); - m_config->registerHandler(&::handleEnv, "env", {true}); - - // pluginza - m_config->addSpecialCategory("plugin", {nullptr, true}); - - m_config->commence(); - - resetHLConfig(); - - if (CONFIG_OPTIONS.size() != m_configValueNumber - 1 /* autogenerated is special */) - Debug::log(LOG, "Warning: config descriptions have {} entries, but there are {} config values. This should fail tests!!", CONFIG_OPTIONS.size(), m_configValueNumber); - - if (!g_pCompositor->m_onlyConfigVerification) { - Debug::log( - INFO, - "!!!!HEY YOU, YES YOU!!!!: further logs to stdout / logfile are disabled by default. BEFORE SENDING THIS LOG, ENABLE THEM. Use debug:disable_logs = false to do so: " - "https://wiki.hypr.land/Configuring/Variables/#debug"); - } - - Debug::m_disableLogs = rc(m_config->getConfigValuePtr("debug:disable_logs")->getDataStaticPtr()); - Debug::m_disableTime = rc(m_config->getConfigValuePtr("debug:disable_time")->getDataStaticPtr()); - - if (g_pEventLoopManager && ERR.has_value()) - g_pEventLoopManager->doLater([ERR] { g_pHyprError->queueCreate(ERR.value(), CHyprColor{1.0, 0.1, 0.1, 1.0}); }); -} - -void CConfigManager::reloadRuleConfigs() { - // FIXME: this should also remove old values if they are removed - - for (const auto& r : Desktop::Rule::allMatchPropStrings()) { - m_config->addSpecialConfigValue("windowrule", ("match:" + r).c_str(), Hyprlang::STRING{""}); - } - - for (const auto& r : Desktop::Rule::windowEffects()->allEffectStrings()) { - m_config->addSpecialConfigValue("windowrule", r.c_str(), Hyprlang::STRING{""}); - } - - for (const auto& r : Desktop::Rule::allMatchPropStrings()) { - m_config->addSpecialConfigValue("layerrule", ("match:" + r).c_str(), Hyprlang::STRING{""}); - } - - for (const auto& r : Desktop::Rule::layerEffects()->allEffectStrings()) { - m_config->addSpecialConfigValue("layerrule", r.c_str(), Hyprlang::STRING{""}); - } -} - -std::optional CConfigManager::generateConfig(std::string configPath) { - std::string parentPath = std::filesystem::path(configPath).parent_path(); - - if (!parentPath.empty()) { - std::error_code ec; - bool created = std::filesystem::create_directories(parentPath, ec); - if (ec) { - Debug::log(ERR, "Couldn't create config home directory ({}): {}", ec.message(), parentPath); - return "Config could not be generated."; - } - if (created) - Debug::log(WARN, "Creating config home directory"); - } - - Debug::log(WARN, "No config file found; attempting to generate."); - std::ofstream ofs; - ofs.open(configPath, std::ios::trunc); - ofs << AUTOGENERATED_PREFIX << EXAMPLE_CONFIG; - ofs.close(); - - if (ofs.fail()) - return "Config could not be generated."; - - return configPath; -} - -std::string CConfigManager::getMainConfigPath() { - static std::string CONFIG_PATH = [this]() -> std::string { - if (!g_pCompositor->m_explicitConfigPath.empty()) - return g_pCompositor->m_explicitConfigPath; - - if (const auto CFG_ENV = getenv("HYPRLAND_CONFIG"); CFG_ENV) - return CFG_ENV; - - const auto PATHS = Hyprutils::Path::findConfig(ISDEBUG ? "hyprlandd" : "hyprland"); - if (PATHS.first.has_value()) { - return PATHS.first.value(); - } else if (PATHS.second.has_value()) { - const auto CONFIGPATH = Hyprutils::Path::fullConfigPath(PATHS.second.value(), ISDEBUG ? "hyprlandd" : "hyprland"); - return generateConfig(CONFIGPATH).value(); - } else - throw std::runtime_error("Neither HOME nor XDG_CONFIG_HOME are set in the environment. Could not find config in XDG_CONFIG_DIRS or /etc/xdg."); - }(); - - return CONFIG_PATH; -} - -std::optional CConfigManager::verifyConfigExists() { - std::string mainConfigPath = getMainConfigPath(); - - if (!std::filesystem::exists(mainConfigPath)) - return "broken config dir?"; - - return {}; -} - -std::string CConfigManager::getConfigString() { - std::string configString; - std::string currFileContent; - - for (const auto& path : m_configPaths) { - std::ifstream configFile(path); - configString += ("\n\nConfig File: " + path + ": "); - if (!configFile.is_open()) { - Debug::log(LOG, "Config file not readable/found!"); - configString += "Read Failed\n"; - continue; - } - configString += "Read Succeeded\n"; - currFileContent.assign(std::istreambuf_iterator(configFile), std::istreambuf_iterator()); - configString.append(currFileContent); - } - return configString; -} - -std::string CConfigManager::getErrors() { - return m_configErrors; -} - -static std::vector HL_VERSION_VARS = { - "HYPRLAND_V_0_53", -}; - -static void exportHlVersionVars() { - for (const auto& v : HL_VERSION_VARS) { - setenv(v, "1", 1); - } -} - -static void clearHlVersionVars() { - for (const auto& v : HL_VERSION_VARS) { - unsetenv(v); - } -} - -void CConfigManager::reload() { - EMIT_HOOK_EVENT("preConfigReload", nullptr); - setDefaultAnimationVars(); - resetHLConfig(); - m_configCurrentPath = getMainConfigPath(); - - exportHlVersionVars(); - - const auto ERR = m_config->parse(); - - clearHlVersionVars(); - - const auto monitorError = handleMonitorv2(); - const auto ruleError = reloadRules(); - m_lastConfigVerificationWasSuccessful = !ERR.error && !monitorError.error; - postConfigReload(ERR.error || !monitorError.error ? ERR : monitorError); -} - -std::string CConfigManager::verify() { - setDefaultAnimationVars(); - resetHLConfig(); - m_configCurrentPath = getMainConfigPath(); - const auto ERR = m_config->parse(); - m_lastConfigVerificationWasSuccessful = !ERR.error; - if (ERR.error) - return ERR.getError(); - return "config ok"; -} - -void CConfigManager::setDefaultAnimationVars() { - m_animationTree.createNode("__internal_fadeCTM"); - m_animationTree.createNode("global"); - - // global - m_animationTree.createNode("windows", "global"); - m_animationTree.createNode("layers", "global"); - m_animationTree.createNode("fade", "global"); - m_animationTree.createNode("border", "global"); - m_animationTree.createNode("borderangle", "global"); - m_animationTree.createNode("workspaces", "global"); - m_animationTree.createNode("zoomFactor", "global"); - m_animationTree.createNode("monitorAdded", "global"); - - // layer - m_animationTree.createNode("layersIn", "layers"); - m_animationTree.createNode("layersOut", "layers"); - - // windows - m_animationTree.createNode("windowsIn", "windows"); - m_animationTree.createNode("windowsOut", "windows"); - m_animationTree.createNode("windowsMove", "windows"); - - // fade - m_animationTree.createNode("fadeIn", "fade"); - m_animationTree.createNode("fadeOut", "fade"); - m_animationTree.createNode("fadeSwitch", "fade"); - m_animationTree.createNode("fadeShadow", "fade"); - m_animationTree.createNode("fadeDim", "fade"); - m_animationTree.createNode("fadeLayers", "fade"); - m_animationTree.createNode("fadeLayersIn", "fadeLayers"); - m_animationTree.createNode("fadeLayersOut", "fadeLayers"); - m_animationTree.createNode("fadePopups", "fade"); - m_animationTree.createNode("fadePopupsIn", "fadePopups"); - m_animationTree.createNode("fadePopupsOut", "fadePopups"); - m_animationTree.createNode("fadeDpms", "fade"); - - // workspaces - m_animationTree.createNode("workspacesIn", "workspaces"); - m_animationTree.createNode("workspacesOut", "workspaces"); - m_animationTree.createNode("specialWorkspace", "workspaces"); - m_animationTree.createNode("specialWorkspaceIn", "specialWorkspace"); - m_animationTree.createNode("specialWorkspaceOut", "specialWorkspace"); - - // init the root nodes - m_animationTree.setConfigForNode("global", 1, 8.f, "default"); - m_animationTree.setConfigForNode("__internal_fadeCTM", 1, 5.f, "linear"); - m_animationTree.setConfigForNode("borderangle", 0, 1, "default"); -} - -std::optional CConfigManager::resetHLConfig() { - m_monitorRules.clear(); - g_pKeybindManager->clearKeybinds(); - g_pAnimationManager->removeAllBeziers(); - g_pAnimationManager->addBezierWithName("linear", Vector2D(0.0, 0.0), Vector2D(1.0, 1.0)); - g_pTrackpadGestures->clearGestures(); - - m_mAdditionalReservedAreas.clear(); - m_workspaceRules.clear(); - setDefaultAnimationVars(); // reset anims - m_declaredPlugins.clear(); - m_failedPluginConfigValues.clear(); - m_finalExecRequests.clear(); - m_keywordRules.clear(); - - // paths - m_configPaths.clear(); - std::string mainConfigPath = getMainConfigPath(); - Debug::log(LOG, "Using config: {}", mainConfigPath); - m_configPaths.emplace_back(mainConfigPath); - - const auto RET = verifyConfigExists(); - - reloadRuleConfigs(); - - return RET; -} - -void CConfigManager::updateWatcher() { - static const auto PDISABLEAUTORELOAD = CConfigValue("misc:disable_autoreload"); - g_pConfigWatcher->setWatchList(*PDISABLEAUTORELOAD ? std::vector{} : m_configPaths); -} - -std::optional CConfigManager::handleMonitorv2(const std::string& output) { - auto parser = CMonitorRuleParser(output); - auto VAL = m_config->getSpecialConfigValuePtr("monitorv2", "disabled", output.c_str()); - if (VAL && VAL->m_bSetByUser && std::any_cast(VAL->getValue())) - parser.setDisabled(); - VAL = m_config->getSpecialConfigValuePtr("monitorv2", "mode", output.c_str()); - if (VAL && VAL->m_bSetByUser) - parser.parseMode(std::any_cast(VAL->getValue())); - VAL = m_config->getSpecialConfigValuePtr("monitorv2", "position", output.c_str()); - if (VAL && VAL->m_bSetByUser) - parser.parsePosition(std::any_cast(VAL->getValue())); - VAL = m_config->getSpecialConfigValuePtr("monitorv2", "scale", output.c_str()); - if (VAL && VAL->m_bSetByUser) - parser.parseScale(std::any_cast(VAL->getValue())); - VAL = m_config->getSpecialConfigValuePtr("monitorv2", "addreserved", output.c_str()); - if (VAL && VAL->m_bSetByUser) { - const auto ARGS = CVarList(std::any_cast(VAL->getValue())); - try { - parser.setReserved({.top = std::stoi(ARGS[0]), .bottom = std::stoi(ARGS[1]), .left = std::stoi(ARGS[2]), .right = std::stoi(ARGS[3])}); - } catch (...) { return "parse error: invalid reserved area"; } - } - VAL = m_config->getSpecialConfigValuePtr("monitorv2", "mirror", output.c_str()); - if (VAL && VAL->m_bSetByUser) - parser.setMirror(std::any_cast(VAL->getValue())); - VAL = m_config->getSpecialConfigValuePtr("monitorv2", "bitdepth", output.c_str()); - if (VAL && VAL->m_bSetByUser) - parser.parseBitdepth(std::any_cast(VAL->getValue())); - VAL = m_config->getSpecialConfigValuePtr("monitorv2", "cm", output.c_str()); - if (VAL && VAL->m_bSetByUser) - parser.parseCM(std::any_cast(VAL->getValue())); - VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdr_eotf", output.c_str()); - if (VAL && VAL->m_bSetByUser) - parser.rule().sdrEotf = std::any_cast(VAL->getValue()); - VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdrbrightness", output.c_str()); - if (VAL && VAL->m_bSetByUser) - parser.rule().sdrBrightness = std::any_cast(VAL->getValue()); - VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdrsaturation", output.c_str()); - if (VAL && VAL->m_bSetByUser) - parser.rule().sdrSaturation = std::any_cast(VAL->getValue()); - VAL = m_config->getSpecialConfigValuePtr("monitorv2", "vrr", output.c_str()); - if (VAL && VAL->m_bSetByUser) - parser.rule().vrr = std::any_cast(VAL->getValue()); - VAL = m_config->getSpecialConfigValuePtr("monitorv2", "transform", output.c_str()); - if (VAL && VAL->m_bSetByUser) - parser.parseTransform(std::any_cast(VAL->getValue())); - - VAL = m_config->getSpecialConfigValuePtr("monitorv2", "supports_wide_color", output.c_str()); - if (VAL && VAL->m_bSetByUser) - parser.rule().supportsWideColor = std::any_cast(VAL->getValue()); - VAL = m_config->getSpecialConfigValuePtr("monitorv2", "supports_hdr", output.c_str()); - if (VAL && VAL->m_bSetByUser) - parser.rule().supportsHDR = std::any_cast(VAL->getValue()); - VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdr_min_luminance", output.c_str()); - if (VAL && VAL->m_bSetByUser) - parser.rule().sdrMinLuminance = std::any_cast(VAL->getValue()); - VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdr_max_luminance", output.c_str()); - if (VAL && VAL->m_bSetByUser) - parser.rule().sdrMaxLuminance = std::any_cast(VAL->getValue()); - - VAL = m_config->getSpecialConfigValuePtr("monitorv2", "min_luminance", output.c_str()); - if (VAL && VAL->m_bSetByUser) - parser.rule().minLuminance = std::any_cast(VAL->getValue()); - VAL = m_config->getSpecialConfigValuePtr("monitorv2", "max_luminance", output.c_str()); - if (VAL && VAL->m_bSetByUser) - parser.rule().maxLuminance = std::any_cast(VAL->getValue()); - VAL = m_config->getSpecialConfigValuePtr("monitorv2", "max_avg_luminance", output.c_str()); - if (VAL && VAL->m_bSetByUser) - parser.rule().maxAvgLuminance = std::any_cast(VAL->getValue()); - - auto newrule = parser.rule(); - - std::erase_if(m_monitorRules, [&](const auto& other) { return other.name == newrule.name; }); - - m_monitorRules.push_back(newrule); - - return parser.getError(); -} - -Hyprlang::CParseResult CConfigManager::handleMonitorv2() { - Hyprlang::CParseResult result; - for (const auto& output : m_config->listKeysForSpecialCategory("monitorv2")) { - const auto error = handleMonitorv2(output); - if (error.has_value()) { - result.setError(error.value().c_str()); - return result; - } - } - return result; -} - -std::optional CConfigManager::addRuleFromConfigKey(const std::string& name) { - const auto ENABLED = m_config->getSpecialConfigValuePtr("windowrule", "enable", name.c_str()); - if (ENABLED && ENABLED->m_bSetByUser && std::any_cast(ENABLED->getValue()) == 0) - return std::nullopt; - - SP rule = makeShared(name); - - for (const auto& r : Desktop::Rule::allMatchPropStrings()) { - auto VAL = m_config->getSpecialConfigValuePtr("windowrule", ("match:" + r).c_str(), name.c_str()); - if (VAL && VAL->m_bSetByUser) - rule->registerMatch(Desktop::Rule::matchPropFromString(r).value_or(Desktop::Rule::RULE_PROP_NONE), std::any_cast(VAL->getValue())); - } - - for (const auto& e : Desktop::Rule::windowEffects()->allEffectStrings()) { - auto VAL = m_config->getSpecialConfigValuePtr("windowrule", e.c_str(), name.c_str()); - if (VAL && VAL->m_bSetByUser) - rule->addEffect(Desktop::Rule::windowEffects()->get(e).value_or(Desktop::Rule::WINDOW_RULE_EFFECT_NONE), std::any_cast(VAL->getValue())); - } - - Desktop::Rule::ruleEngine()->registerRule(std::move(rule)); - return std::nullopt; -} - -std::optional CConfigManager::addLayerRuleFromConfigKey(const std::string& name) { - - const auto ENABLED = m_config->getSpecialConfigValuePtr("layerrule", "enable", name.c_str()); - if (ENABLED && ENABLED->m_bSetByUser && std::any_cast(ENABLED->getValue()) != 0) - return std::nullopt; - - SP rule = makeShared(name); - - for (const auto& r : Desktop::Rule::allMatchPropStrings()) { - auto VAL = m_config->getSpecialConfigValuePtr("layerrule", ("match:" + r).c_str(), name.c_str()); - if (VAL && VAL->m_bSetByUser) - rule->registerMatch(Desktop::Rule::matchPropFromString(r).value_or(Desktop::Rule::RULE_PROP_NONE), std::any_cast(VAL->getValue())); - } - - for (const auto& e : Desktop::Rule::layerEffects()->allEffectStrings()) { - auto VAL = m_config->getSpecialConfigValuePtr("layerrule", e.c_str(), name.c_str()); - if (VAL && VAL->m_bSetByUser) - rule->addEffect(Desktop::Rule::layerEffects()->get(e).value_or(Desktop::Rule::LAYER_RULE_EFFECT_NONE), std::any_cast(VAL->getValue())); - } - - Desktop::Rule::ruleEngine()->registerRule(std::move(rule)); - return std::nullopt; -} - -Hyprlang::CParseResult CConfigManager::reloadRules() { - Desktop::Rule::ruleEngine()->clearAllRules(); - - Hyprlang::CParseResult result; - for (const auto& name : m_config->listKeysForSpecialCategory("windowrule")) { - const auto error = addRuleFromConfigKey(name); - if (error.has_value()) - result.setError(error.value().c_str()); - } - for (const auto& name : m_config->listKeysForSpecialCategory("layerrule")) { - const auto error = addLayerRuleFromConfigKey(name); - if (error.has_value()) - result.setError(error.value().c_str()); - } - - for (auto& rule : m_keywordRules) { - Desktop::Rule::ruleEngine()->registerRule(SP{rule}); - } - - Desktop::Rule::ruleEngine()->updateAllRules(); - - return result; -} - -void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) { - updateWatcher(); - - for (auto const& w : g_pCompositor->m_windows) { - w->uncacheWindowDecos(); - } - - static auto PZOOMFACTOR = CConfigValue("cursor:zoom_factor"); - for (auto const& m : g_pCompositor->m_monitors) { - *(m->m_cursorZoom) = *PZOOMFACTOR; - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id); - } - - // Update the keyboard layout to the cfg'd one if this is not the first launch - if (!m_isFirstLaunch) { - g_pInputManager->setKeyboardLayout(); - g_pInputManager->setPointerConfigs(); - g_pInputManager->setTouchDeviceConfigs(); - g_pInputManager->setTabletConfigs(); - - g_pHyprOpenGL->m_reloadScreenShader = true; - } - - // parseError will be displayed next frame - - if (result.error) - m_configErrors = result.getError(); - else - m_configErrors = ""; - - if (result.error && !std::any_cast(m_config->getConfigValue("debug:suppress_errors"))) - g_pHyprError->queueCreate(result.getError(), CHyprColor(1.0, 50.0 / 255.0, 50.0 / 255.0, 1.0)); - else if (std::any_cast(m_config->getConfigValue("autogenerated")) == 1) - g_pHyprError->queueCreate( - "Warning: You're using an autogenerated config! Edit the config file to get rid of this message. (config file: " + getMainConfigPath() + - " )\nSUPER+Q -> kitty (if it doesn't launch, make sure it's installed or choose a different terminal in the config)\nSUPER+M -> exit Hyprland", - CHyprColor(1.0, 1.0, 70.0 / 255.0, 1.0)); - else - g_pHyprError->destroy(); - - // Set the modes for all monitors as we configured them - // not on first launch because monitors might not exist yet - // and they'll be taken care of in the newMonitor event - // ignore if nomonitorreload is set - if (!m_isFirstLaunch && !m_noMonitorReload) { - // check - performMonitorReload(); - ensureMonitorStatus(); - ensureVRR(); - } - -#ifndef NO_XWAYLAND - const auto PENABLEXWAYLAND = std::any_cast(m_config->getConfigValue("xwayland:enabled")); - g_pCompositor->m_wantsXwayland = PENABLEXWAYLAND; - // enable/disable xwayland usage - if (!m_isFirstLaunch && - g_pXWayland /* XWayland has to be initialized by CCompositor::initManagers for this to make sense, and it doesn't have to be (e.g. very early plugin load) */) { - bool prevEnabledXwayland = g_pXWayland->enabled(); - if (g_pCompositor->m_wantsXwayland != prevEnabledXwayland) - g_pXWayland = makeUnique(g_pCompositor->m_wantsXwayland); - } else - g_pCompositor->m_wantsXwayland = PENABLEXWAYLAND; -#endif - - if (!m_isFirstLaunch && !g_pCompositor->m_unsafeState) - refreshGroupBarGradients(); - - // Updates dynamic window and workspace rules - for (auto const& w : g_pCompositor->getWorkspaces()) { - if (w->inert()) - continue; - w->updateWindows(); - w->updateWindowData(); - } - - // 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; - g_pHyprNotificationOverlay->addNotification("Manual crash has been set up. Set debug:manual_crash back to 0 in order to crash the compositor.", CHyprColor(0), 5000, - ICON_INFO); - } else if (m_manualCrashInitiated && !std::any_cast(m_config->getConfigValue("debug:manual_crash"))) { - // cowabunga it is - g_pHyprRenderer->initiateManualCrash(); - } - - Debug::m_disableStdout = !std::any_cast(m_config->getConfigValue("debug:enable_stdout_logs")); - if (Debug::m_disableStdout && m_isFirstLaunch) - Debug::log(LOG, "Disabling stdout logs! Check the log for further logs."); - - Debug::m_coloredLogs = rc(m_config->getConfigValuePtr("debug:colored_stdout_logs")->getDataStaticPtr()); - - for (auto const& m : g_pCompositor->m_monitors) { - // mark blur dirty - g_pHyprOpenGL->markBlurDirtyForMonitor(m); - - g_pCompositor->scheduleFrameForMonitor(m); - - // Force the compositor to fully re-render all monitors - m->m_forceFullFrames = 2; - - // also force mirrors, as the aspect ratio could've changed - for (auto const& mirror : m->m_mirrors) - mirror->m_forceFullFrames = 3; - } - - // Reset no monitor reload - m_noMonitorReload = false; - - // update plugins - handlePluginLoads(); - - // update persistent workspaces - if (!m_isFirstLaunch) - ensurePersistentWorkspacesPresent(); - - EMIT_HOOK_EVENT("configReloaded", nullptr); - if (g_pEventManager) - g_pEventManager->postEvent(SHyprIPCEvent{"configreloaded", ""}); -} - -void CConfigManager::init() { - - g_pConfigWatcher->setOnChange([this](const CConfigWatcher::SConfigWatchEvent& e) { - Debug::log(LOG, "CConfigManager: file {} modified, reloading", e.file); - reload(); - }); - - const std::string CONFIGPATH = getMainConfigPath(); - reload(); - - m_isFirstLaunch = false; -} - -std::string CConfigManager::parseKeyword(const std::string& COMMAND, const std::string& VALUE) { - const auto RET = m_config->parseDynamic(COMMAND.c_str(), VALUE.c_str()); - - // invalidate layouts if they changed - if (COMMAND == "monitor" || COMMAND.contains("gaps_") || COMMAND.starts_with("dwindle:") || COMMAND.starts_with("master:")) { - for (auto const& m : g_pCompositor->m_monitors) - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id); - } - - // Update window border colors - g_pCompositor->updateAllWindowsAnimatedDecorationValues(); - - // manual crash - if (std::any_cast(m_config->getConfigValue("debug:manual_crash")) && !m_manualCrashInitiated) { - m_manualCrashInitiated = true; - if (g_pHyprNotificationOverlay) { - g_pHyprNotificationOverlay->addNotification("Manual crash has been set up. Set debug:manual_crash back to 0 in order to crash the compositor.", CHyprColor(0), 5000, - ICON_INFO); - } - } else if (m_manualCrashInitiated && !std::any_cast(m_config->getConfigValue("debug:manual_crash"))) { - // cowabunga it is - g_pHyprRenderer->initiateManualCrash(); - } - - return RET.error ? RET.getError() : ""; -} - -Hyprlang::CConfigValue* CConfigManager::getConfigValueSafeDevice(const std::string& dev, const std::string& val, const std::string& fallback) { - - const auto VAL = m_config->getSpecialConfigValuePtr("device", val.c_str(), dev.c_str()); - - if ((!VAL || !VAL->m_bSetByUser) && !fallback.empty()) - return m_config->getConfigValuePtr(fallback.c_str()); - - return VAL; -} - -bool CConfigManager::deviceConfigExplicitlySet(const std::string& dev, const std::string& val) { - const auto VAL = m_config->getSpecialConfigValuePtr("device", val.c_str(), dev.c_str()); - - return VAL && VAL->m_bSetByUser; -} - -int CConfigManager::getDeviceInt(const std::string& dev, const std::string& v, const std::string& fallback) { - return std::any_cast(getConfigValueSafeDevice(dev, v, fallback)->getValue()); -} - -float CConfigManager::getDeviceFloat(const std::string& dev, const std::string& v, const std::string& fallback) { - return std::any_cast(getConfigValueSafeDevice(dev, v, fallback)->getValue()); -} - -Vector2D CConfigManager::getDeviceVec(const std::string& dev, const std::string& v, const std::string& fallback) { - auto vec = std::any_cast(getConfigValueSafeDevice(dev, v, fallback)->getValue()); - return {vec.x, vec.y}; -} - -std::string CConfigManager::getDeviceString(const std::string& dev, const std::string& v, const std::string& fallback) { - auto VAL = std::string{std::any_cast(getConfigValueSafeDevice(dev, v, fallback)->getValue())}; - - if (VAL == STRVAL_EMPTY) - return ""; - - return VAL; -} - -SMonitorRule CConfigManager::getMonitorRuleFor(const PHLMONITOR PMONITOR) { - auto applyWlrOutputConfig = [PMONITOR](SMonitorRule rule) -> SMonitorRule { - const auto CONFIG = PROTO::outputManagement->getOutputStateFor(PMONITOR); - - if (!CONFIG) - return rule; - - Debug::log(LOG, "CConfigManager::getMonitorRuleFor: found a wlr_output_manager override for {}", PMONITOR->m_name); - - Debug::log(LOG, " > overriding enabled: {} -> {}", !rule.disabled, !CONFIG->enabled); - rule.disabled = !CONFIG->enabled; - - if ((CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_MODE) || (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_CUSTOM_MODE)) { - Debug::log(LOG, " > overriding mode: {:.0f}x{:.0f}@{:.2f}Hz -> {:.0f}x{:.0f}@{:.2f}Hz", rule.resolution.x, rule.resolution.y, rule.refreshRate, CONFIG->resolution.x, - CONFIG->resolution.y, CONFIG->refresh / 1000.F); - rule.resolution = CONFIG->resolution; - rule.refreshRate = CONFIG->refresh / 1000.F; - } - - if (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_POSITION) { - Debug::log(LOG, " > overriding offset: {:.0f}, {:.0f} -> {:.0f}, {:.0f}", rule.offset.x, rule.offset.y, CONFIG->position.x, CONFIG->position.y); - rule.offset = CONFIG->position; - } - - if (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_TRANSFORM) { - Debug::log(LOG, " > overriding transform: {} -> {}", sc(rule.transform), sc(CONFIG->transform)); - rule.transform = CONFIG->transform; - } - - if (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_SCALE) { - Debug::log(LOG, " > overriding scale: {} -> {}", sc(rule.scale), sc(CONFIG->scale)); - rule.scale = CONFIG->scale; - } - - if (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC) { - Debug::log(LOG, " > overriding vrr: {} -> {}", rule.vrr.value_or(0), CONFIG->adaptiveSync); - rule.vrr = sc(CONFIG->adaptiveSync); - } - - return rule; - }; - - for (auto const& r : m_monitorRules | std::views::reverse) { - if (PMONITOR->matchesStaticSelector(r.name)) { - return applyWlrOutputConfig(r); - } - } - - Debug::log(WARN, "No rule found for {}, trying to use the first.", PMONITOR->m_name); - - for (auto const& r : m_monitorRules) { - if (r.name.empty()) { - return applyWlrOutputConfig(r); - } - } - - Debug::log(WARN, "No rules configured. Using the default hardcoded one."); - - return applyWlrOutputConfig(SMonitorRule{.autoDir = eAutoDirs::DIR_AUTO_RIGHT, - .name = "", - .resolution = Vector2D(0, 0), - .offset = Vector2D(-INT32_MAX, -INT32_MAX), - .scale = -1}); // 0, 0 is preferred and -1, -1 is auto -} - -SWorkspaceRule CConfigManager::getWorkspaceRuleFor(PHLWORKSPACE pWorkspace) { - SWorkspaceRule mergedRule{}; - for (auto const& rule : m_workspaceRules) { - if (!pWorkspace->matchesStaticSelector(rule.workspaceString)) - continue; - - mergedRule = mergeWorkspaceRules(mergedRule, rule); - } - - return mergedRule; -} - -SWorkspaceRule CConfigManager::mergeWorkspaceRules(const SWorkspaceRule& rule1, const SWorkspaceRule& rule2) { - SWorkspaceRule mergedRule = rule1; - - if (rule1.monitor.empty()) - mergedRule.monitor = rule2.monitor; - if (rule1.workspaceString.empty()) - mergedRule.workspaceString = rule2.workspaceString; - if (rule1.workspaceName.empty()) - mergedRule.workspaceName = rule2.workspaceName; - if (rule1.workspaceId == WORKSPACE_INVALID) - mergedRule.workspaceId = rule2.workspaceId; - - if (rule2.isDefault) - mergedRule.isDefault = true; - if (rule2.isPersistent) - mergedRule.isPersistent = true; - if (rule2.gapsIn.has_value()) - mergedRule.gapsIn = rule2.gapsIn; - if (rule2.gapsOut.has_value()) - mergedRule.gapsOut = rule2.gapsOut; - if (rule2.floatGaps) - mergedRule.floatGaps = rule2.floatGaps; - if (rule2.borderSize.has_value()) - mergedRule.borderSize = rule2.borderSize; - if (rule2.noBorder.has_value()) - mergedRule.noBorder = rule2.noBorder; - if (rule2.noRounding.has_value()) - mergedRule.noRounding = rule2.noRounding; - if (rule2.decorate.has_value()) - mergedRule.decorate = rule2.decorate; - if (rule2.noShadow.has_value()) - mergedRule.noShadow = rule2.noShadow; - if (rule2.onCreatedEmptyRunCmd.has_value()) - mergedRule.onCreatedEmptyRunCmd = rule2.onCreatedEmptyRunCmd; - if (rule2.defaultName.has_value()) - mergedRule.defaultName = rule2.defaultName; - if (!rule2.layoutopts.empty()) { - for (const auto& layoutopt : rule2.layoutopts) { - mergedRule.layoutopts[layoutopt.first] = layoutopt.second; - } - } - return mergedRule; -} - -void CConfigManager::dispatchExecOnce() { - if (m_firstExecDispatched || m_isFirstLaunch) - return; - - // update dbus env - if (g_pCompositor->m_aqBackend->hasSession()) - handleRawExec("", -#ifdef USES_SYSTEMD - "systemctl --user import-environment DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP QT_QPA_PLATFORMTHEME PATH XDG_DATA_DIRS && hash " - "dbus-update-activation-environment 2>/dev/null && " -#endif - "dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP HYPRLAND_INSTANCE_SIGNATURE QT_QPA_PLATFORMTHEME PATH XDG_DATA_DIRS"); - - m_firstExecDispatched = true; - m_isLaunchingExecOnce = true; - - for (auto const& c : m_firstExecRequests) { - c.withRules ? handleExec("", c.exec) : handleRawExec("", c.exec); - } - - m_firstExecRequests.clear(); // free some kb of memory :P - m_isLaunchingExecOnce = false; - - // set input, fixes some certain issues - g_pInputManager->setKeyboardLayout(); - g_pInputManager->setPointerConfigs(); - g_pInputManager->setTouchDeviceConfigs(); - g_pInputManager->setTabletConfigs(); - - // check for user's possible errors with their setup and notify them if needed - g_pCompositor->performUserChecks(); -} - -void CConfigManager::dispatchExecShutdown() { - if (m_finalExecRequests.empty()) { - g_pCompositor->m_finalRequests = false; - return; - } - - g_pCompositor->m_finalRequests = true; - - for (auto const& c : m_finalExecRequests) { - handleExecShutdown("", c); - } - - m_finalExecRequests.clear(); - - // Actually exit now - handleExecShutdown("", "hyprctl dispatch exit"); -} - -void CConfigManager::performMonitorReload() { - handleMonitorv2(); - - bool overAgain = false; - - for (auto const& m : g_pCompositor->m_realMonitors) { - if (!m->m_output || m->m_isUnsafeFallback) - continue; - - auto rule = getMonitorRuleFor(m); - - if (!m->applyMonitorRule(&rule)) { - overAgain = true; - break; - } - - // ensure mirror - m->setMirror(rule.mirrorOf); - - g_pHyprRenderer->arrangeLayersForMonitor(m->m_id); - } - - if (overAgain) - performMonitorReload(); - - m_wantsMonitorReload = false; - - EMIT_HOOK_EVENT("monitorLayoutChanged", nullptr); -} - -void* const* CConfigManager::getConfigValuePtr(const std::string& val) { - const auto VAL = m_config->getConfigValuePtr(val.c_str()); - if (!VAL) - return nullptr; - return VAL->getDataStaticPtr(); -} - -Hyprlang::CConfigValue* CConfigManager::getHyprlangConfigValuePtr(const std::string& name, const std::string& specialCat) { - if (!specialCat.empty()) - return m_config->getSpecialConfigValuePtr(specialCat.c_str(), name.c_str(), nullptr); - - if (name.starts_with("plugin:")) - return m_config->getSpecialConfigValuePtr("plugin", name.substr(7).c_str(), nullptr); - - return m_config->getConfigValuePtr(name.c_str()); -} - -bool CConfigManager::deviceConfigExists(const std::string& dev) { - auto copy = dev; - std::ranges::replace(copy, ' ', '-'); - - return m_config->specialCategoryExistsForKey("device", copy.c_str()); -} - -void CConfigManager::ensureMonitorStatus() { - for (auto const& rm : g_pCompositor->m_realMonitors) { - if (!rm->m_output || rm->m_isUnsafeFallback) - continue; - - auto rule = getMonitorRuleFor(rm); - - if (rule.disabled == rm->m_enabled) - rm->applyMonitorRule(&rule); - } -} - -void CConfigManager::ensureVRR(PHLMONITOR pMonitor) { - static auto PVRR = rc(getConfigValuePtr("misc:vrr")); - - static auto ensureVRRForDisplay = [&](PHLMONITOR m) -> void { - if (!m->m_output || m->m_createdByUser) - return; - - const auto USEVRR = m->m_activeMonitorRule.vrr.has_value() ? m->m_activeMonitorRule.vrr.value() : **PVRR; - - if (USEVRR == 0) { - if (m->m_vrrActive) { - m->m_output->state->resetExplicitFences(); - m->m_output->state->setAdaptiveSync(false); - - if (!m->m_state.commit()) - Debug::log(ERR, "Couldn't commit output {} in ensureVRR -> false", m->m_output->name); - } - m->m_vrrActive = false; - return; - } else if (USEVRR == 1) { - if (!m->m_vrrActive) { - m->m_output->state->resetExplicitFences(); - m->m_output->state->setAdaptiveSync(true); - - if (!m->m_state.test()) { - Debug::log(LOG, "Pending output {} does not accept VRR.", m->m_output->name); - m->m_output->state->setAdaptiveSync(false); - } - - if (!m->m_state.commit()) - Debug::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; // ??? - - bool wantVRR = PWORKSPACE->m_hasFullscreenWindow && (PWORKSPACE->m_fullscreenMode & FSMODE_FULLSCREEN); - if (wantVRR && PWORKSPACE->getFullscreenWindow()->m_ruleApplicator->noVRR().valueOrDefault()) - wantVRR = false; - - if (wantVRR && USEVRR == 3) { - const auto contentType = PWORKSPACE->getFullscreenWindow()->getContentType(); - wantVRR = contentType == CONTENT_TYPE_GAME || contentType == CONTENT_TYPE_VIDEO; - } - - if (wantVRR) { - /* fullscreen */ - m->m_vrrActive = true; - - if (!m->m_output->state->state().adaptiveSync) { - m->m_output->state->setAdaptiveSync(true); - - if (!m->m_state.test()) { - Debug::log(LOG, "Pending output {} does not accept VRR.", m->m_output->name); - m->m_output->state->setAdaptiveSync(false); - } - } - } else { - m->m_vrrActive = false; - - m->m_output->state->setAdaptiveSync(false); - } - } - }; - - if (pMonitor) { - ensureVRRForDisplay(pMonitor); - return; - } - - for (auto const& m : g_pCompositor->m_monitors) { - ensureVRRForDisplay(m); - } -} - -SP CConfigManager::getAnimationPropertyConfig(const std::string& name) { - return m_animationTree.getConfig(name); -} - -void CConfigManager::addParseError(const std::string& err) { - g_pHyprError->queueCreate(err + "\nHyprland may not work correctly.", CHyprColor(1.0, 50.0 / 255.0, 50.0 / 255.0, 1.0)); -} - -PHLMONITOR CConfigManager::getBoundMonitorForWS(const std::string& wsname) { - auto monitor = getBoundMonitorStringForWS(wsname); - if (monitor.starts_with("desc:")) - return g_pCompositor->getMonitorFromDesc(trim(monitor.substr(5))); - else - return g_pCompositor->getMonitorFromName(monitor); -} - -std::string CConfigManager::getBoundMonitorStringForWS(const std::string& wsname) { - for (auto const& wr : m_workspaceRules) { - const auto WSNAME = wr.workspaceName.starts_with("name:") ? wr.workspaceName.substr(5) : wr.workspaceName; - - if (WSNAME == wsname) - return wr.monitor; - } - - return ""; -} - -const std::vector& CConfigManager::getAllWorkspaceRules() { - return m_workspaceRules; -} - -void CConfigManager::handlePluginLoads() { - if (!g_pPluginSystem) - return; - - bool pluginsChanged = false; - g_pPluginSystem->updateConfigPlugins(m_declaredPlugins, pluginsChanged); - - if (pluginsChanged) { - g_pHyprError->destroy(); - reload(); - } -} - -const std::unordered_map>& CConfigManager::getAnimationConfig() { - return m_animationTree.getFullConfig(); -} - -void CConfigManager::addPluginConfigVar(HANDLE handle, const std::string& name, const Hyprlang::CConfigValue& value) { - if (!name.starts_with("plugin:")) - return; - - std::string field = name.substr(7); - - m_config->addSpecialConfigValue("plugin", field.c_str(), value); - m_pluginVariables.push_back({handle, field}); -} - -void CConfigManager::addPluginKeyword(HANDLE handle, const std::string& name, Hyprlang::PCONFIGHANDLERFUNC fn, Hyprlang::SHandlerOptions opts) { - m_pluginKeywords.emplace_back(SPluginKeyword{handle, name, fn}); - m_config->registerHandler(fn, name.c_str(), opts); -} - -void CConfigManager::removePluginConfig(HANDLE handle) { - for (auto const& k : m_pluginKeywords) { - if (k.handle != handle) - continue; - - m_config->unregisterHandler(k.name.c_str()); - } - - std::erase_if(m_pluginKeywords, [&](const auto& other) { return other.handle == handle; }); - for (auto const& [h, n] : m_pluginVariables) { - if (h != handle) - continue; - - m_config->removeSpecialConfigValue("plugin", n.c_str()); - } - std::erase_if(m_pluginVariables, [handle](const auto& other) { return other.handle == handle; }); -} - -std::string CConfigManager::getDefaultWorkspaceFor(const std::string& name) { - for (auto other = m_workspaceRules.begin(); other != m_workspaceRules.end(); ++other) { - if (other->isDefault) { - if (other->monitor == name) - return other->workspaceString; - if (other->monitor.starts_with("desc:")) { - auto const monitor = g_pCompositor->getMonitorFromDesc(trim(other->monitor.substr(5))); - if (monitor && monitor->m_name == name) - return other->workspaceString; - } - } - } - return ""; -} - -std::optional CConfigManager::handleRawExec(const std::string& command, const std::string& args) { - if (m_isFirstLaunch) { - m_firstExecRequests.push_back({args, false}); - return {}; - } - - g_pKeybindManager->spawnRaw(args); - return {}; -} - -std::optional CConfigManager::handleExec(const std::string& command, const std::string& args) { - if (m_isFirstLaunch) { - m_firstExecRequests.push_back({args, true}); - return {}; - } - - g_pKeybindManager->spawn(args); - return {}; -} - -std::optional CConfigManager::handleExecOnce(const std::string& command, const std::string& args) { - if (m_isFirstLaunch) - m_firstExecRequests.push_back({args, true}); - - return {}; -} - -std::optional CConfigManager::handleExecRawOnce(const std::string& command, const std::string& args) { - if (m_isFirstLaunch) - m_firstExecRequests.push_back({args, false}); - - return {}; -} - -std::optional CConfigManager::handleExecShutdown(const std::string& command, const std::string& args) { - if (g_pCompositor->m_finalRequests) { - g_pKeybindManager->spawn(args); - return {}; - } - - m_finalExecRequests.push_back(args); - return {}; -} - -static bool parseModeLine(const std::string& modeline, drmModeModeInfo& mode) { - auto args = CVarList(modeline, 0, 's'); - - auto keyword = args[0]; - std::ranges::transform(keyword, keyword.begin(), ::tolower); - - if (keyword != "modeline") - return false; - - if (args.size() < 10) { - Debug::log(ERR, "modeline parse error: expected at least 9 arguments, got {}", args.size() - 1); - return false; - } - - int argno = 1; - - try { - mode.type = DRM_MODE_TYPE_USERDEF; - mode.clock = std::stof(args[argno++]) * 1000; - mode.hdisplay = std::stoi(args[argno++]); - mode.hsync_start = std::stoi(args[argno++]); - mode.hsync_end = std::stoi(args[argno++]); - mode.htotal = std::stoi(args[argno++]); - mode.vdisplay = std::stoi(args[argno++]); - mode.vsync_start = std::stoi(args[argno++]); - mode.vsync_end = std::stoi(args[argno++]); - mode.vtotal = std::stoi(args[argno++]); - mode.vrefresh = mode.clock * 1000.0 * 1000.0 / mode.htotal / mode.vtotal; - } catch (const std::exception& e) { - Debug::log(ERR, "modeline parse error: invalid numeric value: {}", e.what()); - return false; - } - - // clang-format off - static std::unordered_map flagsmap = { - {"+hsync", DRM_MODE_FLAG_PHSYNC}, - {"-hsync", DRM_MODE_FLAG_NHSYNC}, - {"+vsync", DRM_MODE_FLAG_PVSYNC}, - {"-vsync", DRM_MODE_FLAG_NVSYNC}, - {"Interlace", DRM_MODE_FLAG_INTERLACE}, - }; - // clang-format on - - for (; argno < sc(args.size()); argno++) { - auto key = args[argno]; - std::ranges::transform(key, key.begin(), ::tolower); - - auto it = flagsmap.find(key); - - if (it != flagsmap.end()) - mode.flags |= it->second; - else - Debug::log(ERR, "Invalid flag {} in modeline", key); - } - - snprintf(mode.name, sizeof(mode.name), "%dx%d@%d", mode.hdisplay, mode.vdisplay, mode.vrefresh / 1000); - - return true; -} - -CMonitorRuleParser::CMonitorRuleParser(const std::string& name) { - m_rule.name = name; -} - -const std::string& CMonitorRuleParser::name() { - return m_rule.name; -} - -SMonitorRule& CMonitorRuleParser::rule() { - return m_rule; -} - -std::optional CMonitorRuleParser::getError() { - if (m_error.empty()) - return {}; - return m_error; -} - -bool CMonitorRuleParser::parseMode(const std::string& value) { - if (value.starts_with("pref")) - m_rule.resolution = Vector2D(); - else if (value.starts_with("highrr")) - m_rule.resolution = Vector2D(-1, -1); - else if (value.starts_with("highres")) - m_rule.resolution = Vector2D(-1, -2); - else if (value.starts_with("maxwidth")) - m_rule.resolution = Vector2D(-1, -3); - else if (parseModeLine(value, m_rule.drmMode)) { - m_rule.resolution = Vector2D(m_rule.drmMode.hdisplay, m_rule.drmMode.vdisplay); - m_rule.refreshRate = sc(m_rule.drmMode.vrefresh) / 1000; - } else { - - if (!value.contains("x")) { - m_error += "invalid resolution "; - m_rule.resolution = Vector2D(); - return false; - } else { - try { - m_rule.resolution.x = stoi(value.substr(0, value.find_first_of('x'))); - m_rule.resolution.y = stoi(value.substr(value.find_first_of('x') + 1, value.find_first_of('@'))); - - if (value.contains("@")) - m_rule.refreshRate = stof(value.substr(value.find_first_of('@') + 1)); - } catch (...) { - m_error += "invalid resolution "; - m_rule.resolution = Vector2D(); - return false; - } - } - } - return true; -} - -bool CMonitorRuleParser::parsePosition(const std::string& value, bool isFirst) { - if (value.starts_with("auto")) { - m_rule.offset = Vector2D(-INT32_MAX, -INT32_MAX); - // If this is the first monitor rule needs to be on the right. - if (value == "auto-right" || value == "auto" || isFirst) - m_rule.autoDir = eAutoDirs::DIR_AUTO_RIGHT; - else if (value == "auto-left") - m_rule.autoDir = eAutoDirs::DIR_AUTO_LEFT; - else if (value == "auto-up") - m_rule.autoDir = eAutoDirs::DIR_AUTO_UP; - else if (value == "auto-down") - m_rule.autoDir = eAutoDirs::DIR_AUTO_DOWN; - else if (value == "auto-center-right") - m_rule.autoDir = eAutoDirs::DIR_AUTO_CENTER_RIGHT; - else if (value == "auto-center-left") - m_rule.autoDir = eAutoDirs::DIR_AUTO_CENTER_LEFT; - else if (value == "auto-center-up") - m_rule.autoDir = eAutoDirs::DIR_AUTO_CENTER_UP; - else if (value == "auto-center-down") - m_rule.autoDir = eAutoDirs::DIR_AUTO_CENTER_DOWN; - else { - Debug::log(WARN, - "Invalid auto direction. Valid options are 'auto'," - "'auto-up', 'auto-down', 'auto-left', 'auto-right'," - "'auto-center-up', 'auto-center-down'," - "'auto-center-left', and 'auto-center-right'."); - m_error += "invalid auto direction "; - return false; - } - } else { - if (!value.contains("x")) { - m_error += "invalid offset "; - m_rule.offset = Vector2D(-INT32_MAX, -INT32_MAX); - return false; - } else { - try { - m_rule.offset.x = stoi(value.substr(0, value.find_first_of('x'))); - m_rule.offset.y = stoi(value.substr(value.find_first_of('x') + 1)); - } catch (...) { - m_error += "invalid offset "; - m_rule.offset = Vector2D(-INT32_MAX, -INT32_MAX); - return false; - } - } - } - return true; -} - -bool CMonitorRuleParser::parseScale(const std::string& value) { - if (value.starts_with("auto")) - m_rule.scale = -1; - else { - if (!isNumber(value, true)) { - m_error += "invalid scale "; - return false; - } else { - m_rule.scale = stof(value); - - if (m_rule.scale < 0.25f) { - m_error += "invalid scale "; - m_rule.scale = 1; - return false; - } - } - } - return true; -} - -bool CMonitorRuleParser::parseTransform(const std::string& value) { - if (!isNumber(value)) { - m_error += "invalid transform "; - return false; - } - - const auto TSF = std::stoi(value); - if (std::clamp(TSF, 0, 7) != TSF) { - Debug::log(ERR, "Invalid transform {} in monitor", TSF); - m_error += "invalid transform "; - return false; - } - m_rule.transform = sc(TSF); - return true; -} - -bool CMonitorRuleParser::parseBitdepth(const std::string& value) { - m_rule.enable10bit = value == "10"; - return true; -} - -bool CMonitorRuleParser::parseCM(const std::string& value) { - auto parsedCM = NCMType::fromString(value); - if (!parsedCM.has_value()) { - m_error += "invalid cm "; - return false; - } - m_rule.cmType = parsedCM.value(); - return true; -} - -bool CMonitorRuleParser::parseSDRBrightness(const std::string& value) { - try { - m_rule.sdrBrightness = stof(value); - } catch (...) { - m_error += "invalid sdrbrightness "; - return false; - } - return true; -} - -bool CMonitorRuleParser::parseSDRSaturation(const std::string& value) { - try { - m_rule.sdrSaturation = stof(value); - } catch (...) { - m_error += "invalid sdrsaturation "; - return false; - } - return true; -} - -bool CMonitorRuleParser::parseVRR(const std::string& value) { - if (!isNumber(value)) { - m_error += "invalid vrr "; - return false; - } - - m_rule.vrr = std::stoi(value); - return true; -} - -void CMonitorRuleParser::setDisabled() { - m_rule.disabled = true; -} - -void CMonitorRuleParser::setMirror(const std::string& value) { - m_rule.mirrorOf = value; -} - -bool CMonitorRuleParser::setReserved(const SMonitorAdditionalReservedArea& value) { - g_pConfigManager->m_mAdditionalReservedAreas[name()] = value; - return true; -} - -std::optional CConfigManager::handleMonitor(const std::string& command, const std::string& args) { - // get the monitor config - const auto ARGS = CVarList2(std::string(args)); - - auto parser = CMonitorRuleParser(std::string(ARGS[0])); - - if (ARGS[1] == "disable" || ARGS[1] == "disabled" || ARGS[1] == "addreserved" || ARGS[1] == "transform") { - if (ARGS[1] == "disable" || ARGS[1] == "disabled") - parser.setDisabled(); - else if (ARGS[1] == "transform") { - if (!parser.parseTransform(std::string(ARGS[2]))) - return parser.getError(); - - const auto TRANSFORM = parser.rule().transform; - - // overwrite if exists - for (auto& r : m_monitorRules) { - if (r.name == parser.name()) { - r.transform = TRANSFORM; - return {}; - } - } - - return {}; - } else if (ARGS[1] == "addreserved") { - try { - parser.setReserved({.top = std::stoi(std::string(ARGS[2])), - .bottom = std::stoi(std::string(ARGS[3])), - .left = std::stoi(std::string(ARGS[4])), - .right = std::stoi(std::string(ARGS[5]))}); - } catch (...) { return "parse error: invalid reserved area"; } - return {}; - } else { - Debug::log(ERR, "ConfigManager parseMonitor, curitem bogus???"); - return "parse error: curitem bogus"; - } - - std::erase_if(m_monitorRules, [&](const auto& other) { return other.name == parser.name(); }); - - m_monitorRules.push_back(parser.rule()); - - return {}; - } - - parser.parseMode(std::string(ARGS[1])); - parser.parsePosition(std::string(ARGS[2])); - parser.parseScale(std::string(ARGS[3])); - - int argno = 4; - - while (!ARGS[argno].empty()) { - if (ARGS[argno] == "mirror") { - parser.setMirror(std::string(ARGS[argno + 1])); - argno++; - } else if (ARGS[argno] == "bitdepth") { - parser.parseBitdepth(std::string(ARGS[argno + 1])); - argno++; - } else if (ARGS[argno] == "cm") { - parser.parseCM(std::string(ARGS[argno + 1])); - argno++; - } else if (ARGS[argno] == "sdrsaturation") { - parser.parseSDRSaturation(std::string(ARGS[argno + 1])); - argno++; - } else if (ARGS[argno] == "sdrbrightness") { - parser.parseSDRBrightness(std::string(ARGS[argno + 1])); - argno++; - } else if (ARGS[argno] == "transform") { - parser.parseTransform(std::string(ARGS[argno + 1])); - argno++; - } else if (ARGS[argno] == "vrr") { - parser.parseVRR(std::string(ARGS[argno + 1])); - argno++; - } else if (ARGS[argno] == "workspace") { - const auto& [id, name, isAutoID] = getWorkspaceIDNameFromString(std::string(ARGS[argno + 1])); - - SWorkspaceRule wsRule; - wsRule.monitor = parser.name(); - wsRule.workspaceString = ARGS[argno + 1]; - wsRule.workspaceId = isAutoID ? WORKSPACE_INVALID : id; - wsRule.workspaceName = name; - - m_workspaceRules.emplace_back(wsRule); - argno++; - } else { - Debug::log(ERR, "Config error: invalid monitor syntax at \"{}\"", ARGS[argno]); - return "invalid syntax at \"" + std::string(ARGS[argno]) + "\""; - } - - argno++; - } - - auto newrule = parser.rule(); - - std::erase_if(m_monitorRules, [&](const auto& other) { return other.name == newrule.name; }); - - m_monitorRules.push_back(newrule); - - return parser.getError(); -} - -std::optional CConfigManager::handleBezier(const std::string& command, const std::string& args) { - const auto ARGS = CVarList(args); - - std::string bezierName = ARGS[0]; - - if (ARGS[1].empty()) - return "too few arguments"; - else if (!isNumber(ARGS[1], true)) - return "invalid point"; - float p1x = std::stof(ARGS[1]); - - if (ARGS[2].empty()) - return "too few arguments"; - else if (!isNumber(ARGS[2], true)) - return "invalid point"; - float p1y = std::stof(ARGS[2]); - - if (ARGS[3].empty()) - return "too few arguments"; - else if (!isNumber(ARGS[3], true)) - return "invalid point"; - float p2x = std::stof(ARGS[3]); - - if (ARGS[4].empty()) - return "too few arguments"; - else if (!isNumber(ARGS[4], true)) - return "invalid point"; - float p2y = std::stof(ARGS[4]); - - if (!ARGS[5].empty()) - return "too many arguments"; - - g_pAnimationManager->addBezierWithName(bezierName, Vector2D(p1x, p1y), Vector2D(p2x, p2y)); - - return {}; -} - -std::optional CConfigManager::handleAnimation(const std::string& command, const std::string& args) { - const auto ARGS = CVarList(args); - - // Master on/off - - // anim name - const auto ANIMNAME = ARGS[0]; - - if (!m_animationTree.nodeExists(ANIMNAME)) - return "no such animation"; - - // This helper casts strings like "1", "true", "off", "yes"... to int. - int64_t enabledInt = configStringToInt(ARGS[1]).value_or(0) == 1; - - // Checking that the int is 1 or 0 because the helper can return integers out of range. - if (enabledInt != 0 && enabledInt != 1) - return "invalid animation on/off state"; - - if (!enabledInt) { - m_animationTree.setConfigForNode(ANIMNAME, enabledInt, 1, "default"); - return {}; - } - - float speed = -1; - - // speed - if (isNumber(ARGS[2], true)) { - speed = std::stof(ARGS[2]); - - if (speed <= 0) { - speed = 1.f; - return "invalid speed"; - } - } else { - speed = 10.f; - return "invalid speed"; - } - - std::string bezierName = ARGS[3]; - m_animationTree.setConfigForNode(ANIMNAME, enabledInt, speed, ARGS[3], ARGS[4]); - - if (!g_pAnimationManager->bezierExists(bezierName)) { - const auto PANIMNODE = m_animationTree.getConfig(ANIMNAME); - PANIMNODE->internalBezier = "default"; - return "no such bezier"; - } - - if (!ARGS[4].empty()) { - auto ERR = g_pAnimationManager->styleValidInConfigVar(ANIMNAME, ARGS[4]); - - if (!ERR.empty()) - return ERR; - } - - return {}; -} - -SParsedKey parseKey(const std::string& key) { - if (isNumber(key) && std::stoi(key) > 9) - return {.keycode = std::stoi(key)}; - else if (key.starts_with("code:") && isNumber(key.substr(5))) - return {.keycode = std::stoi(key.substr(5))}; - else if (key == "catchall") - return {.catchAll = true}; - else - return {.key = key}; -} - -std::optional CConfigManager::handleBind(const std::string& command, const std::string& value) { - // example: - // bind[fl]=SUPER,G,exec,dmenu_run - - // flags - bool locked = false; - bool release = false; - bool repeat = false; - bool mouse = false; - bool nonConsuming = false; - bool transparent = false; - bool ignoreMods = false; - bool multiKey = false; - bool longPress = false; - bool hasDescription = false; - bool dontInhibit = false; - bool click = false; - bool drag = false; - bool submapUniversal = false; - const auto BINDARGS = command.substr(4); - - for (auto const& arg : BINDARGS) { - switch (arg) { - case 'l': locked = true; break; - case 'r': release = true; break; - case 'e': repeat = true; break; - case 'm': mouse = true; break; - case 'n': nonConsuming = true; break; - case 't': transparent = true; break; - case 'i': ignoreMods = true; break; - case 's': multiKey = true; break; - case 'o': longPress = true; break; - case 'd': hasDescription = true; break; - case 'p': dontInhibit = true; break; - case 'c': - click = true; - release = true; - break; - case 'g': - drag = true; - release = true; - break; - case 'u': submapUniversal = true; break; - default: return "bind: invalid flag"; - } - } - - if ((longPress || release) && repeat) - return "flags e is mutually exclusive with r and o"; - - if (mouse && (repeat || release || locked)) - return "flag m is exclusive"; - - if (click && drag) - return "flags c and g are mutually exclusive"; - - const int numbArgs = hasDescription ? 5 : 4; - const auto ARGS = CVarList(value, numbArgs); - - const int DESCR_OFFSET = hasDescription ? 1 : 0; - if ((ARGS.size() < 3 && !mouse) || (ARGS.size() < 3 && mouse)) - return "bind: too few args"; - else if ((ARGS.size() > sc(4) + DESCR_OFFSET && !mouse) || (ARGS.size() > sc(3) + DESCR_OFFSET && mouse)) - return "bind: too many args"; - - std::set KEYSYMS; - std::set MODS; - - if (multiKey) { - for (const auto& splitKey : CVarList(ARGS[1], 8, '&')) { - KEYSYMS.insert(xkb_keysym_from_name(splitKey.c_str(), XKB_KEYSYM_CASE_INSENSITIVE)); - } - for (const auto& splitMod : CVarList(ARGS[0], 8, '&')) { - MODS.insert(xkb_keysym_from_name(splitMod.c_str(), XKB_KEYSYM_CASE_INSENSITIVE)); - } - } - const auto MOD = g_pKeybindManager->stringToModMask(ARGS[0]); - const auto MODSTR = ARGS[0]; - - const auto KEY = multiKey ? "" : ARGS[1]; - - const auto DESCRIPTION = hasDescription ? ARGS[2] : ""; - - auto HANDLER = ARGS[2 + DESCR_OFFSET]; - - const auto COMMAND = mouse ? HANDLER : ARGS[3 + DESCR_OFFSET]; - - if (mouse) - HANDLER = "mouse"; - - // to lower - std::ranges::transform(HANDLER, HANDLER.begin(), ::tolower); - - const auto DISPATCHER = g_pKeybindManager->m_dispatchers.find(HANDLER); - - if (DISPATCHER == g_pKeybindManager->m_dispatchers.end()) { - Debug::log(ERR, "Invalid dispatcher: {}", HANDLER); - return "Invalid dispatcher, requested \"" + HANDLER + "\" does not exist"; - } - - if (MOD == 0 && !MODSTR.empty()) { - Debug::log(ERR, "Invalid mod: {}", MODSTR); - return "Invalid mod, requested mod \"" + MODSTR + "\" is not a valid mod."; - } - - if ((!KEY.empty()) || multiKey) { - SParsedKey parsedKey = parseKey(KEY); - - if (parsedKey.catchAll && m_currentSubmap.name.empty()) { - Debug::log(ERR, "Catchall not allowed outside of submap!"); - return "Invalid catchall, catchall keybinds are only allowed in submaps."; - } - - g_pKeybindManager->addKeybind(SKeybind{parsedKey.key, KEYSYMS, parsedKey.keycode, parsedKey.catchAll, MOD, MODS, HANDLER, - COMMAND, locked, m_currentSubmap, DESCRIPTION, release, repeat, longPress, - mouse, nonConsuming, transparent, ignoreMods, multiKey, hasDescription, dontInhibit, - click, drag, submapUniversal}); - } - - return {}; -} - -std::optional CConfigManager::handleUnbind(const std::string& command, const std::string& value) { - const auto ARGS = CVarList(value); - - if (ARGS.size() == 1 && ARGS[0] == "all") { - g_pKeybindManager->m_keybinds.clear(); - g_pKeybindManager->m_activeKeybinds.clear(); - g_pKeybindManager->m_lastLongPressKeybind.reset(); - return {}; - } - - const auto MOD = g_pKeybindManager->stringToModMask(ARGS[0]); - - const auto KEY = parseKey(ARGS[1]); - - g_pKeybindManager->removeKeybind(MOD, KEY); - - return {}; -} - -std::optional CConfigManager::handleWorkspaceRules(const std::string& command, const std::string& value) { - // This can either be the monitor or the workspace identifier - const auto FIRST_DELIM = value.find_first_of(','); - - auto first_ident = trim(value.substr(0, FIRST_DELIM)); - - const auto& [id, name, isAutoID] = getWorkspaceIDNameFromString(first_ident); - - auto rules = value.substr(FIRST_DELIM + 1); - SWorkspaceRule wsRule; - wsRule.workspaceString = first_ident; - // if (id == WORKSPACE_INVALID) { - // // it could be the monitor. If so, second value MUST be - // // the workspace. - // const auto WORKSPACE_DELIM = value.find_first_of(',', FIRST_DELIM + 1); - // auto wsIdent = removeBeginEndSpacesTabs(value.substr(FIRST_DELIM + 1, (WORKSPACE_DELIM - FIRST_DELIM - 1))); - // id = getWorkspaceIDFromString(wsIdent, name); - // if (id == WORKSPACE_INVALID) { - // Debug::log(ERR, "Invalid workspace identifier found: {}", wsIdent); - // return "Invalid workspace identifier found: " + wsIdent; - // } - // wsRule.monitor = first_ident; - // wsRule.workspaceString = wsIdent; - // wsRule.isDefault = true; // backwards compat - // rules = value.substr(WORKSPACE_DELIM + 1); - // } - - const static std::string ruleOnCreatedEmpty = "on-created-empty:"; - const static auto ruleOnCreatedEmptyLen = ruleOnCreatedEmpty.length(); - -#define CHECK_OR_THROW(expr) \ - \ - auto X = expr; \ - if (!X) { \ - return "Failed parsing a workspace rule"; \ - } - - auto assignRule = [&](std::string rule) -> std::optional { - size_t delim = std::string::npos; - if ((delim = rule.find("gapsin:")) != std::string::npos) { - CVarList2 varlist(rule.substr(delim + 7), 0, ' '); - wsRule.gapsIn = CCssGapData(); - try { - wsRule.gapsIn->parseGapData(varlist); - } catch (...) { return "Error parsing workspace rule gaps: {}", rule.substr(delim + 7); } - } else if ((delim = rule.find("gapsout:")) != std::string::npos) { - CVarList2 varlist(rule.substr(delim + 8), 0, ' '); - wsRule.gapsOut = CCssGapData(); - try { - wsRule.gapsOut->parseGapData(varlist); - } catch (...) { return "Error parsing workspace rule gaps: {}", rule.substr(delim + 8); } - } else if ((delim = rule.find("bordersize:")) != std::string::npos) - try { - wsRule.borderSize = std::stoi(rule.substr(delim + 11)); - } catch (...) { return "Error parsing workspace rule bordersize: {}", rule.substr(delim + 11); } - else if ((delim = rule.find("border:")) != std::string::npos) { - CHECK_OR_THROW(configStringToInt(rule.substr(delim + 7))) - wsRule.noBorder = !*X; - } else if ((delim = rule.find("shadow:")) != std::string::npos) { - CHECK_OR_THROW(configStringToInt(rule.substr(delim + 7))) - wsRule.noShadow = !*X; - } else if ((delim = rule.find("rounding:")) != std::string::npos) { - CHECK_OR_THROW(configStringToInt(rule.substr(delim + 9))) - wsRule.noRounding = !*X; - } else if ((delim = rule.find("decorate:")) != std::string::npos) { - CHECK_OR_THROW(configStringToInt(rule.substr(delim + 9))) - wsRule.decorate = *X; - } else if ((delim = rule.find("monitor:")) != std::string::npos) - wsRule.monitor = rule.substr(delim + 8); - else if ((delim = rule.find("default:")) != std::string::npos) { - CHECK_OR_THROW(configStringToInt(rule.substr(delim + 8))) - wsRule.isDefault = *X; - } else if ((delim = rule.find("persistent:")) != std::string::npos) { - CHECK_OR_THROW(configStringToInt(rule.substr(delim + 11))) - wsRule.isPersistent = *X; - } else if ((delim = rule.find("defaultName:")) != std::string::npos) - wsRule.defaultName = trim(rule.substr(delim + 12)); - else if ((delim = rule.find(ruleOnCreatedEmpty)) != std::string::npos) { - CHECK_OR_THROW(cleanCmdForWorkspace(name, rule.substr(delim + ruleOnCreatedEmptyLen))) - wsRule.onCreatedEmptyRunCmd = *X; - } else if ((delim = rule.find("layoutopt:")) != std::string::npos) { - std::string opt = rule.substr(delim + 10); - if (!opt.contains(":")) { - // invalid - Debug::log(ERR, "Invalid workspace rule found: {}", rule); - return "Invalid workspace rule found: " + rule; - } - - std::string val = opt.substr(opt.find(':') + 1); - opt = opt.substr(0, opt.find(':')); - - wsRule.layoutopts[opt] = val; - } - - return {}; - }; - -#undef CHECK_OR_THROW - - CVarList2 rulesList(std::string(rules), 0, ',', true); - for (auto const& r : rulesList) { - const auto R = assignRule(std::string(r)); - if (R.has_value()) - return R; - } - - wsRule.workspaceName = name; - wsRule.workspaceId = isAutoID ? WORKSPACE_INVALID : id; - - const auto IT = std::ranges::find_if(m_workspaceRules, [&](const auto& other) { return other.workspaceString == wsRule.workspaceString; }); - - if (IT == m_workspaceRules.end()) - m_workspaceRules.emplace_back(wsRule); - else - *IT = mergeWorkspaceRules(*IT, wsRule); - - return {}; -} - -std::optional CConfigManager::handleSubmap(const std::string&, const std::string& submap) { - CVarList2 data((std::string(submap))); - m_currentSubmap.name = (data[0] == "reset") ? "" : data[0]; - m_currentSubmap.reset = data[1]; - return {}; -} - -std::optional CConfigManager::handleSource(const std::string& command, const std::string& rawpath) { - if (rawpath.length() < 2) { - Debug::log(ERR, "source= path garbage"); - return "source= path " + rawpath + " bogus!"; - } - - std::unique_ptr glob_buf{sc(calloc(1, sizeof(glob_t))), // allocate and zero-initialize NOLINT(cppcoreguidelines-no-malloc) - [](glob_t* g) { - if (g) { - globfree(g); // free internal resources allocated by glob() - free(g); // free the memory for the glob_t structure NOLINT(cppcoreguidelines-no-malloc) - } - }}; - - if (auto r = glob(absolutePath(rawpath, m_configCurrentPath).c_str(), GLOB_TILDE, nullptr, glob_buf.get()); r != 0) { - std::string err = std::format("source= globbing error: {}", r == GLOB_NOMATCH ? "found no match" : GLOB_ABORTED ? "read error" : "out of memory"); - Debug::log(ERR, "{}", err); - return err; - } - - std::string errorsFromParsing; - - for (size_t i = 0; i < glob_buf->gl_pathc; i++) { - auto value = absolutePath(glob_buf->gl_pathv[i], m_configCurrentPath); - - std::error_code ec; - auto file_status = std::filesystem::status(value, ec); - - if (ec) { - Debug::log(ERR, "source= file from glob result is inaccessible ({}): {}", ec.message(), value); - return "source= file " + value + " is inaccessible!"; - } - - if (std::filesystem::is_regular_file(file_status)) { - m_configPaths.emplace_back(value); - auto configCurrentPathBackup = m_configCurrentPath; - m_configCurrentPath = value; - const auto THISRESULT = m_config->parseFile(value.c_str()); - m_configCurrentPath = configCurrentPathBackup; - if (THISRESULT.error && errorsFromParsing.empty()) - errorsFromParsing += THISRESULT.getError(); - } else if (std::filesystem::is_directory(file_status)) { - Debug::log(WARN, "source= skipping directory {}", value); - continue; - } else { - Debug::log(WARN, "source= skipping non-regular-file {}", value); - continue; - } - } - - if (errorsFromParsing.empty()) - return {}; - return errorsFromParsing; -} - -std::optional CConfigManager::handleEnv(const std::string& command, const std::string& value) { - const auto ARGS = CVarList(value, 2); - - if (ARGS[0].empty()) - return "env empty"; - - if (!m_isFirstLaunch) { - // check if env changed at all. If it didn't, ignore. If it did, update it. - const auto* ENV = getenv(ARGS[0].c_str()); - if (ENV && ENV == ARGS[1]) - return {}; // env hasn't changed - } - - setenv(ARGS[0].c_str(), ARGS[1].c_str(), 1); - - if (command.back() == 'd') { - // dbus - const auto CMD = -#ifdef USES_SYSTEMD - "systemctl --user import-environment " + ARGS[0] + - " && hash dbus-update-activation-environment 2>/dev/null && " -#endif - "dbus-update-activation-environment --systemd " + - ARGS[0]; - handleRawExec("", CMD); - } - - return {}; -} - -std::optional CConfigManager::handlePlugin(const std::string& command, const std::string& path) { - if (std::ranges::find(m_declaredPlugins, path) != m_declaredPlugins.end()) - return "plugin '" + path + "' declared twice"; - - m_declaredPlugins.push_back(path); - - return {}; -} - -std::optional CConfigManager::handlePermission(const std::string& command, const std::string& value) { - CVarList2 data((std::string(value))); - - eDynamicPermissionType type = PERMISSION_TYPE_UNKNOWN; - eDynamicPermissionAllowMode mode = PERMISSION_RULE_ALLOW_MODE_UNKNOWN; - - if (data[1] == "screencopy") - type = PERMISSION_TYPE_SCREENCOPY; - else if (data[1] == "plugin") - type = PERMISSION_TYPE_PLUGIN; - else if (data[1] == "keyboard" || data[1] == "keeb") - type = PERMISSION_TYPE_KEYBOARD; - - if (data[2] == "ask") - mode = PERMISSION_RULE_ALLOW_MODE_ASK; - else if (data[2] == "allow") - mode = PERMISSION_RULE_ALLOW_MODE_ALLOW; - else if (data[2] == "deny") - mode = PERMISSION_RULE_ALLOW_MODE_DENY; - - if (type == PERMISSION_TYPE_UNKNOWN) - return "unknown permission type"; - if (mode == PERMISSION_RULE_ALLOW_MODE_UNKNOWN) - return "unknown permission allow mode"; - - if (m_isFirstLaunch) - g_pDynamicPermissionManager->addConfigPermissionRule(std::string(data[0]), type, mode); - - return {}; -} - -std::optional CConfigManager::handleGesture(const std::string& command, const std::string& value) { - CVarList2 data((std::string(value))); - - size_t fingerCount = 0; - eTrackpadGestureDirection direction = TRACKPAD_GESTURE_DIR_NONE; - - try { - fingerCount = std::stoul(std::string{data[0]}); - } catch (...) { return std::format("Invalid value {} for finger count", data[0]); } - - if (fingerCount <= 1 || fingerCount >= 10) - return std::format("Invalid value {} for finger count", data[0]); - - direction = g_pTrackpadGestures->dirForString(data[1]); - - if (direction == TRACKPAD_GESTURE_DIR_NONE) - return std::format("Invalid direction: {}", data[1]); - - int startDataIdx = 2; - uint32_t modMask = 0; - float deltaScale = 1.F; - - while (true) { - - if (data[startDataIdx].starts_with("mod:")) { - modMask = g_pKeybindManager->stringToModMask(std::string{data[startDataIdx].substr(4)}); - startDataIdx++; - continue; - } else if (data[startDataIdx].starts_with("scale:")) { - try { - deltaScale = std::clamp(std::stof(std::string{data[startDataIdx].substr(6)}), 0.1F, 10.F); - startDataIdx++; - continue; - } catch (...) { return std::format("Invalid delta scale: {}", std::string{data[startDataIdx].substr(6)}); } - } - - break; - } - - std::expected result; - - if (data[startDataIdx] == "dispatcher") - result = g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}, data.join(",", startDataIdx + 2)), fingerCount, - direction, modMask, deltaScale); - else if (data[startDataIdx] == "workspace") - result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale); - else if (data[startDataIdx] == "resize") - result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale); - else if (data[startDataIdx] == "move") - 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); - else if (data[startDataIdx] == "close") - 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); - else if (data[startDataIdx] == "fullscreen") - result = g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale); - else if (data[startDataIdx] == "unset") - result = g_pTrackpadGestures->removeGesture(fingerCount, direction, modMask, deltaScale); - else - return std::format("Invalid gesture: {}", data[startDataIdx]); - - if (!result) - return result.error(); - - return std::nullopt; -} - -std::optional CConfigManager::handleWindowrule(const std::string& command, const std::string& value) { - CVarList2 data((std::string(value))); - - SP rule = makeShared(); - - const auto& PROPS = Desktop::Rule::allMatchPropStrings(); - const auto& EFFECTS = Desktop::Rule::windowEffects()->allEffectStrings(); - - for (const auto& el : data) { - // split on space, no need for a CVarList here - size_t spacePos = el.find(' '); - if (spacePos == std::string::npos) - return std::format("invalid field {}: missing a value", el); - - const bool FIRST_IS_PROP = el.starts_with("match:"); - const auto FIRST = FIRST_IS_PROP ? el.substr(6, spacePos - 6) : el.substr(0, spacePos); - if (FIRST_IS_PROP && std::ranges::contains(PROPS, FIRST)) { - // it's a prop - const auto PROP = Desktop::Rule::matchPropFromString(FIRST); - if (!PROP.has_value()) - return std::format("invalid prop {}", el); - rule->registerMatch(*PROP, std::string{el.substr(spacePos + 1)}); - } else if (!FIRST_IS_PROP && std::ranges::contains(EFFECTS, FIRST)) { - // it's an effect - const auto EFFECT = Desktop::Rule::windowEffects()->get(FIRST); - if (!EFFECT.has_value()) - return std::format("invalid effect {}", el); - rule->addEffect(*EFFECT, std::string{el.substr(spacePos + 1)}); - } else - return std::format("invalid field type {}", FIRST); - } - - m_keywordRules.emplace_back(std::move(rule)); - if (g_pHyprCtl && g_pHyprCtl->m_currentRequestParams.isDynamicKeyword) - Desktop::Rule::ruleEngine()->registerRule(SP{m_keywordRules.back()}); - - return std::nullopt; -} - -std::optional CConfigManager::handleLayerrule(const std::string& command, const std::string& value) { - CVarList2 data((std::string(value))); - - SP rule = makeShared(); - - const auto& PROPS = Desktop::Rule::allMatchPropStrings(); - const auto& EFFECTS = Desktop::Rule::layerEffects()->allEffectStrings(); - - for (const auto& el : data) { - // split on space, no need for a CVarList here - size_t spacePos = el.find(' '); - if (spacePos == std::string::npos) - return std::format("invalid field {}: missing a value", el); - - const bool FIRST_IS_PROP = el.starts_with("match:"); - const auto FIRST = FIRST_IS_PROP ? el.substr(6, spacePos - 6) : el.substr(0, spacePos); - if (FIRST_IS_PROP && std::ranges::contains(PROPS, FIRST)) { - // it's a prop - const auto PROP = Desktop::Rule::matchPropFromString(FIRST); - if (!PROP.has_value()) - return std::format("invalid prop {}", el); - rule->registerMatch(*PROP, std::string{el.substr(spacePos + 1)}); - } else if (!FIRST_IS_PROP && std::ranges::contains(EFFECTS, FIRST)) { - // it's an effect - const auto EFFECT = Desktop::Rule::layerEffects()->get(FIRST); - if (!EFFECT.has_value()) - return std::format("invalid effect {}", el); - rule->addEffect(*EFFECT, std::string{el.substr(spacePos + 1)}); - } else - return std::format("invalid field type {}", FIRST); - } - - m_keywordRules.emplace_back(std::move(rule)); - - return std::nullopt; -} - -const std::vector& CConfigManager::getAllDescriptions() { - return CONFIG_OPTIONS; -} - -bool CConfigManager::shouldUseSoftwareCursors(PHLMONITOR pMonitor) { - static auto PNOHW = CConfigValue("cursor:no_hardware_cursors"); - static auto PINVISIBLE = CConfigValue("cursor:invisible"); - - if (pMonitor->m_tearingState.activelyTearing) +// +bool Config::initConfigManager() { + if (mgr()) return true; - if (*PINVISIBLE != 0) - return true; + // run this bitch + const auto CFG_PATH = Supplementary::Jeremy::getMainConfigPath(); - switch (*PNOHW) { - case 0: return false; - case 1: return true; - case 2: return g_pHyprRenderer->isNvidia() && g_pHyprRenderer->isMgpu(); - default: break; + if (!CFG_PATH) { + Log::logger->log(Log::CRIT, "Couldn't load config: {}", CFG_PATH.error()); + return false; + } + + std::filesystem::path filePath = *CFG_PATH; + + // TODO: + // filePath.replace_extension(".lua"); + + g_mgr = makeUnique(); + + std::error_code ec; + if (!std::filesystem::exists(filePath, ec) || ec) { + if (ec) { + Log::logger->log(Log::CRIT, "Couldn't load config: {}", ec.message()); + return false; + } } return true; } -std::string SConfigOptionDescription::jsonify() const { - auto parseData = [this]() -> std::string { - return std::visit( - [this](auto&& val) { - const auto PTR = g_pConfigManager->m_config->getConfigValuePtr(value.c_str()); - if (!PTR) { - Debug::log(ERR, "invalid SConfigOptionDescription: no config option {} exists", value); - return std::string{""}; - } - const char* const EXPLICIT = PTR->m_bSetByUser ? "true" : "false"; - - std::string currentValue = "undefined"; - - const auto CONFIGVALUE = PTR->getValue(); - - if (typeid(Hyprlang::INT) == std::type_index(CONFIGVALUE.type())) - currentValue = std::format("{}", std::any_cast(CONFIGVALUE)); - else if (typeid(Hyprlang::FLOAT) == std::type_index(CONFIGVALUE.type())) - currentValue = std::format("{:.2f}", std::any_cast(CONFIGVALUE)); - else if (typeid(Hyprlang::STRING) == std::type_index(CONFIGVALUE.type())) - currentValue = std::any_cast(CONFIGVALUE); - else if (typeid(Hyprlang::VEC2) == std::type_index(CONFIGVALUE.type())) { - const auto V = std::any_cast(CONFIGVALUE); - currentValue = std::format("{}, {}", V.x, V.y); - } else if (typeid(void*) == std::type_index(CONFIGVALUE.type())) { - const auto DATA = sc(std::any_cast(CONFIGVALUE)); - currentValue = DATA->toString(); - } - - try { - using T = std::decay_t; - if constexpr (std::is_same_v) { - return std::format(R"#( "value": "{}", - "current": "{}", - "explicit": {})#", - val.value, currentValue, EXPLICIT); - } else if constexpr (std::is_same_v) { - return std::format(R"#( "value": {}, - "min": {}, - "max": {}, - "current": {}, - "explicit": {})#", - val.value, val.min, val.max, currentValue, EXPLICIT); - } else if constexpr (std::is_same_v) { - return std::format(R"#( "value": {}, - "min": {}, - "max": {}, - "current": {}, - "explicit": {})#", - val.value, val.min, val.max, currentValue, EXPLICIT); - } else if constexpr (std::is_same_v) { - return std::format(R"#( "value": "{}", - "current": "{}", - "explicit": {})#", - val.color.getAsHex(), currentValue, EXPLICIT); - } else if constexpr (std::is_same_v) { - return std::format(R"#( "value": {}, - "current": {}, - "explicit": {})#", - val.value, currentValue, EXPLICIT); - } else if constexpr (std::is_same_v) { - return std::format(R"#( "value": "{}", - "firstIndex": {}, - "current": {}, - "explicit": {})#", - val.choices, val.firstIndex, currentValue, EXPLICIT); - } else if constexpr (std::is_same_v) { - return std::format(R"#( "x": {}, - "y": {}, - "min_x": {}, - "min_y": {}, - "max_x": {}, - "max_y": {}, - "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": "{}", - "explicit": {})#", - val.gradient, currentValue, EXPLICIT); - } - - } catch (std::bad_any_cast& e) { Debug::log(ERR, "Bad any_cast on value {} in descriptions", value); } - return std::string{""}; - }, - data); - }; - - std::string json = std::format(R"#({{ - "value": "{}", - "description": "{}", - "type": {}, - "flags": {}, - "data": {{ - {} - }} -}})#", - value, escapeJSONStrings(description), sc(type), sc(flags), parseData()); - - return json; -} - -void CConfigManager::ensurePersistentWorkspacesPresent() { - g_pCompositor->ensurePersistentWorkspacesPresent(m_workspaceRules); -} - -void CConfigManager::storeFloatingSize(PHLWINDOW window, const Vector2D& size) { - Debug::log(LOG, "storing floating size {}x{} for window {}::{}", size.x, size.y, window->m_initialClass, window->m_initialTitle); - // true -> use m_initialClass and m_initialTitle - SFloatCache id{window, true}; - m_mStoredFloatingSizes[id] = size; -} - -std::optional CConfigManager::getStoredFloatingSize(PHLWINDOW window) { - // At startup, m_initialClass and m_initialTitle are undefined - // and m_class and m_title are just "initial" ones. - // false -> use m_class and m_title - SFloatCache id{window, false}; - Debug::log(LOG, "Hash for window {}::{} = {}", window->m_class, window->m_title, id.hash); - if (m_mStoredFloatingSizes.contains(id)) { - Debug::log(LOG, "got stored size {}x{} for window {}::{}", m_mStoredFloatingSizes[id].x, m_mStoredFloatingSizes[id].y, window->m_class, window->m_title); - return m_mStoredFloatingSizes[id]; - } - return std::nullopt; -} +UP& Config::mgr() { + return g_mgr; +} \ No newline at end of file diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp index 599ee8e71..c9898e138 100644 --- a/src/config/ConfigManager.hpp +++ b/src/config/ConfigManager.hpp @@ -1,347 +1,63 @@ #pragma once -#include -#define CONFIG_MANAGER_H +#include +#include +#include -#include -#include -#include "../defines.hpp" -#include -#include -#include -#include -#include -#include "../helpers/Monitor.hpp" -#include "../desktop/Window.hpp" - -#include "ConfigDataValues.hpp" -#include "../SharedDefs.hpp" -#include "../helpers/Color.hpp" -#include "../desktop/DesktopTypes.hpp" +#include "./shared/Types.hpp" #include "../helpers/memory/Memory.hpp" -#include "../managers/XWaylandManager.hpp" -#include "../managers/KeybindManager.hpp" -#include +namespace Config { -#define HANDLE void* - -class CConfigManager; - -struct SWorkspaceRule { - std::string monitor = ""; - std::string workspaceString = ""; - std::string workspaceName = ""; - WORKSPACEID workspaceId = -1; - bool isDefault = false; - bool isPersistent = false; - std::optional gapsIn; - std::optional gapsOut; - std::optional floatGaps = gapsOut; - std::optional borderSize; - std::optional decorate; - std::optional noRounding; - std::optional noBorder; - std::optional noShadow; - std::optional onCreatedEmptyRunCmd; - std::optional defaultName; - std::map layoutopts; -}; - -struct SMonitorAdditionalReservedArea { - int top = 0; - int bottom = 0; - int left = 0; - int right = 0; -}; - -struct SPluginKeyword { - HANDLE handle = nullptr; - std::string name = ""; - Hyprlang::PCONFIGHANDLERFUNC fn = nullptr; -}; - -struct SPluginVariable { - HANDLE handle = nullptr; - std::string name = ""; -}; - -enum eConfigOptionType : uint8_t { - CONFIG_OPTION_BOOL = 0, - CONFIG_OPTION_INT = 1, /* e.g. 0/1/2*/ - CONFIG_OPTION_FLOAT = 2, - CONFIG_OPTION_STRING_SHORT = 3, /* e.g. "auto" */ - CONFIG_OPTION_STRING_LONG = 4, /* e.g. a command */ - CONFIG_OPTION_COLOR = 5, - CONFIG_OPTION_CHOICE = 6, /* e.g. "one", "two", "three" */ - CONFIG_OPTION_GRADIENT = 7, - CONFIG_OPTION_VECTOR = 8, -}; - -enum eConfigOptionFlags : uint8_t { - CONFIG_OPTION_FLAG_PERCENTAGE = (1 << 0), -}; - -struct SConfigOptionDescription { - - struct SBoolData { - bool value = false; + struct SConfigOptionReply { + // * const* + void* const* dataptr = nullptr; + const std::type_info* type = nullptr; + bool setByUser = false; }; - struct SRangeData { - int value = 0, min = 0, max = 2; + enum eConfigManagerType : uint8_t { + CONFIG_LEGACY = 0, + CONFIG_LUA }; - struct SFloatData { - float value = 0, min = 0, max = 100; + class IConfigManager { + protected: + IConfigManager() = default; + + public: + virtual ~IConfigManager() = default; + + virtual eConfigManagerType type() = 0; + + virtual void init() = 0; + virtual void reload() = 0; + virtual std::string verify() = 0; + + virtual int getDeviceInt(const std::string&, const std::string&, const std::string& fallback = "") = 0; + virtual float getDeviceFloat(const std::string&, const std::string&, const std::string& fallback = "") = 0; + virtual Vector2D getDeviceVec(const std::string&, const std::string&, const std::string& fallback = "") = 0; + virtual std::string getDeviceString(const std::string&, const std::string&, const std::string& fallback = "") = 0; + virtual bool deviceConfigExplicitlySet(const std::string&, const std::string&) = 0; + virtual bool deviceConfigExists(const std::string&) = 0; + + virtual SConfigOptionReply getConfigValue(const std::string&) = 0; + + virtual std::string getMainConfigPath() = 0; + virtual std::string currentConfigPath() = 0; + virtual std::string getConfigString() = 0; + virtual const std::vector& getConfigPaths() = 0; + + virtual bool configVerifPassed() = 0; + + virtual std::string getErrors() = 0; + + virtual std::expected generateDefaultConfig(const std::filesystem::path&, bool safeMode = false) = 0; + + virtual void handlePluginLoads() = 0; }; - struct SStringData { - std::string value; - }; + bool initConfigManager(); - struct SColorData { - CHyprColor color; - }; - - struct SChoiceData { - int firstIndex = 0; - std::string choices; // comma-separated - }; - - struct SGradientData { - std::string gradient; - }; - - struct SVectorData { - Vector2D vec, min, max; - }; - - std::string value; // e.g. general:gaps_in - std::string description; - std::string specialCategory; // if value is special (e.g. device:abc) value will be abc and special device - bool specialKey = false; - eConfigOptionType type = CONFIG_OPTION_BOOL; - uint32_t flags = 0; // eConfigOptionFlags - - std::string jsonify() const; - - // - std::variant data; -}; - -struct SFirstExecRequest { - std::string exec = ""; - bool withRules = false; -}; - -struct SFloatCache { - size_t hash; - - SFloatCache(PHLWINDOW window, bool initial) { - // Base hash from class/title - size_t baseHash = initial ? (std::hash{}(window->m_initialClass) ^ (std::hash{}(window->m_initialTitle) << 1)) : - (std::hash{}(window->m_class) ^ (std::hash{}(window->m_title) << 1)); - - // Use empty string as default tag value - std::string tagValue = ""; - if (auto xdgTag = window->xdgTag()) - tagValue = xdgTag.value(); - - // Combine hashes - hash = baseHash ^ (std::hash{}(tagValue) << 2); - } - - bool operator==(const SFloatCache& other) const { - return hash == other.hash; - } -}; - -namespace std { - template <> - struct hash { - size_t operator()(const SFloatCache& id) const { - return id.hash; - } - }; -} - -class CMonitorRuleParser { - public: - CMonitorRuleParser(const std::string& name); - - const std::string& name(); - SMonitorRule& rule(); - std::optional getError(); - bool parseMode(const std::string& value); - bool parsePosition(const std::string& value, bool isFirst = false); - bool parseScale(const std::string& value); - bool parseTransform(const std::string& value); - bool parseBitdepth(const std::string& value); - bool parseCM(const std::string& value); - bool parseSDRBrightness(const std::string& value); - bool parseSDRSaturation(const std::string& value); - bool parseVRR(const std::string& value); - - void setDisabled(); - void setMirror(const std::string& value); - bool setReserved(const SMonitorAdditionalReservedArea& value); - - private: - SMonitorRule m_rule; - std::string m_error = ""; -}; - -class CConfigManager { - public: - CConfigManager(); - - void init(); - void reload(); - std::string verify(); - - int getDeviceInt(const std::string&, const std::string&, const std::string& fallback = ""); - float getDeviceFloat(const std::string&, const std::string&, const std::string& fallback = ""); - Vector2D getDeviceVec(const std::string&, const std::string&, const std::string& fallback = ""); - std::string getDeviceString(const std::string&, const std::string&, const std::string& fallback = ""); - bool deviceConfigExplicitlySet(const std::string&, const std::string&); - bool deviceConfigExists(const std::string&); - Hyprlang::CConfigValue* getConfigValueSafeDevice(const std::string& dev, const std::string& val, const std::string& fallback); - - void* const* getConfigValuePtr(const std::string&); - Hyprlang::CConfigValue* getHyprlangConfigValuePtr(const std::string& name, const std::string& specialCat = ""); - std::string getMainConfigPath(); - std::string getConfigString(); - - SMonitorRule getMonitorRuleFor(const PHLMONITOR); - SWorkspaceRule getWorkspaceRuleFor(PHLWORKSPACE workspace); - std::string getDefaultWorkspaceFor(const std::string&); - - PHLMONITOR getBoundMonitorForWS(const std::string&); - std::string getBoundMonitorStringForWS(const std::string&); - const std::vector& getAllWorkspaceRules(); - - void ensurePersistentWorkspacesPresent(); - - const std::vector& getAllDescriptions(); - - std::unordered_map m_mAdditionalReservedAreas; - - const std::unordered_map>& getAnimationConfig(); - - void addPluginConfigVar(HANDLE handle, const std::string& name, const Hyprlang::CConfigValue& value); - void addPluginKeyword(HANDLE handle, const std::string& name, Hyprlang::PCONFIGHANDLERFUNC fun, Hyprlang::SHandlerOptions opts = {}); - void removePluginConfig(HANDLE handle); - - // no-op when done. - void dispatchExecOnce(); - void dispatchExecShutdown(); - - void performMonitorReload(); - void ensureMonitorStatus(); - void ensureVRR(PHLMONITOR pMonitor = nullptr); - - bool shouldUseSoftwareCursors(PHLMONITOR pMonitor); - void updateWatcher(); - - std::string parseKeyword(const std::string&, const std::string&); - - void addParseError(const std::string&); - - SP getAnimationPropertyConfig(const std::string&); - - void handlePluginLoads(); - std::string getErrors(); - - // keywords - std::optional handleRawExec(const std::string&, const std::string&); - std::optional handleExec(const std::string&, const std::string&); - std::optional handleExecOnce(const std::string&, const std::string&); - std::optional handleExecRawOnce(const std::string&, const std::string&); - std::optional handleExecShutdown(const std::string&, const std::string&); - std::optional handleMonitor(const std::string&, const std::string&); - std::optional handleBind(const std::string&, const std::string&); - std::optional handleUnbind(const std::string&, const std::string&); - std::optional handleWorkspaceRules(const std::string&, const std::string&); - std::optional handleBezier(const std::string&, const std::string&); - std::optional handleAnimation(const std::string&, const std::string&); - std::optional handleSource(const std::string&, const std::string&); - std::optional handleSubmap(const std::string&, const std::string&); - std::optional handleBindWS(const std::string&, const std::string&); - std::optional handleEnv(const std::string&, const std::string&); - std::optional handlePlugin(const std::string&, const std::string&); - std::optional handlePermission(const std::string&, const std::string&); - std::optional handleGesture(const std::string&, const std::string&); - std::optional handleWindowrule(const std::string&, const std::string&); - std::optional handleLayerrule(const std::string&, const std::string&); - - std::optional handleMonitorv2(const std::string& output); - Hyprlang::CParseResult handleMonitorv2(); - std::optional addRuleFromConfigKey(const std::string& name); - std::optional addLayerRuleFromConfigKey(const std::string& name); - Hyprlang::CParseResult reloadRules(); - - std::string m_configCurrentPath; - - bool m_wantsMonitorReload = false; - bool m_noMonitorReload = false; - bool m_isLaunchingExecOnce = false; // For exec-once to skip initial ws tracking - bool m_lastConfigVerificationWasSuccessful = true; - - void storeFloatingSize(PHLWINDOW window, const Vector2D& size); - std::optional getStoredFloatingSize(PHLWINDOW window); - - private: - UP m_config; - - std::vector m_configPaths; - - Hyprutils::Animation::CAnimationConfigTree m_animationTree; - - SSubmap m_currentSubmap; - - std::vector m_declaredPlugins; - std::vector m_pluginKeywords; - std::vector m_pluginVariables; - - std::vector> m_keywordRules; - - bool m_isFirstLaunch = true; // For exec-once - - std::vector m_monitorRules; - std::vector m_workspaceRules; - - bool m_firstExecDispatched = false; - bool m_manualCrashInitiated = false; - - std::vector m_firstExecRequests; // bool is for if with rules - std::vector m_finalExecRequests; - - std::vector> m_failedPluginConfigValues; // for plugin values of unloaded plugins - std::string m_configErrors = ""; - - uint32_t m_configValueNumber = 0; - - // internal methods - void setDefaultAnimationVars(); - std::optional resetHLConfig(); - std::optional generateConfig(std::string configPath); - std::optional verifyConfigExists(); - void reloadRuleConfigs(); - - void postConfigReload(const Hyprlang::CParseResult& result); - SWorkspaceRule mergeWorkspaceRules(const SWorkspaceRule&, const SWorkspaceRule&); - - void registerConfigVar(const char* name, const Hyprlang::INT& val); - void registerConfigVar(const char* name, const Hyprlang::FLOAT& val); - void registerConfigVar(const char* name, const Hyprlang::VEC2& val); - void registerConfigVar(const char* name, const Hyprlang::STRING& val); - void registerConfigVar(const char* name, Hyprlang::CUSTOMTYPE&& val); - - std::unordered_map m_mStoredFloatingSizes; - - friend struct SConfigOptionDescription; - friend class CMonitorRuleParser; -}; - -inline UP g_pConfigManager; + UP& mgr(); +}; \ No newline at end of file diff --git a/src/config/ConfigValue.cpp b/src/config/ConfigValue.cpp index 9f6cc3191..169bf29cb 100644 --- a/src/config/ConfigValue.cpp +++ b/src/config/ConfigValue.cpp @@ -1,15 +1,12 @@ #include "ConfigValue.hpp" #include "ConfigManager.hpp" -#include "../macros.hpp" void local__configValuePopulate(void* const** p, const std::string& val) { - const auto PVHYPRLANG = g_pConfigManager->getHyprlangConfigValuePtr(val); - - *p = PVHYPRLANG->getDataStaticPtr(); + const auto BIGP = Config::mgr()->getConfigValue(val); + *p = BIGP.dataptr; } std::type_index local__configValueTypeIdx(const std::string& val) { - const auto PVHYPRLANG = g_pConfigManager->getHyprlangConfigValuePtr(val); - const auto ANY = PVHYPRLANG->getValue(); - return std::type_index(ANY.type()); + const auto BIGP = Config::mgr()->getConfigValue(val); + return std::type_index(*BIGP.type); } \ No newline at end of file diff --git a/src/config/ConfigWatcher.hpp b/src/config/ConfigWatcher.hpp deleted file mode 100644 index 979456891..000000000 --- a/src/config/ConfigWatcher.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once -#include "../helpers/memory/Memory.hpp" -#include -#include -#include -#include - -class CConfigWatcher { - public: - CConfigWatcher(); - ~CConfigWatcher() = default; - - struct SConfigWatchEvent { - std::string file; - }; - - Hyprutils::OS::CFileDescriptor& getInotifyFD(); - void setWatchList(const std::vector& paths); - void setOnChange(const std::function& fn); - void onInotifyEvent(); - - private: - struct SInotifyWatch { - int wd = -1; - std::string file; - }; - - std::function m_watchCallback; - std::vector m_watches; - Hyprutils::OS::CFileDescriptor m_inotifyFd; -}; - -inline UP g_pConfigWatcher = makeUnique(); diff --git a/src/config/legacy/ConfigManager.cpp b/src/config/legacy/ConfigManager.cpp new file mode 100644 index 000000000..310d27109 --- /dev/null +++ b/src/config/legacy/ConfigManager.cpp @@ -0,0 +1,2414 @@ +#include + +#include "ConfigManager.hpp" +#include "../shared/inotify/ConfigWatcher.hpp" +#include "../../managers/KeybindManager.hpp" +#include "../../Compositor.hpp" + +#include "../../render/decorations/CHyprGroupBarDecoration.hpp" +#include "../shared/complex/ComplexDataTypes.hpp" +#include "../ConfigValue.hpp" +#include "../shared/monitor/MonitorRuleManager.hpp" +#include "../shared/workspace/WorkspaceRuleManager.hpp" +#include "../shared/animation/AnimationTree.hpp" +#include "../shared/monitor/Parser.hpp" +#include "../supplementary/executor/Executor.hpp" +#include "../supplementary/jeremy/Jeremy.hpp" +#include "../../protocols/LayerShell.hpp" +#include "../../xwayland/XWayland.hpp" +#include "../../protocols/OutputManagement.hpp" +#include "../../managers/animation/AnimationManager.hpp" +#include "../../desktop/view/LayerSurface.hpp" +#include "../../desktop/rule/Engine.hpp" +#include "../../desktop/rule/windowRule/WindowRule.hpp" +#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 "../../errorOverlay/Overlay.hpp" +#include "../../managers/input/InputManager.hpp" +#include "../../managers/eventLoop/EventLoopManager.hpp" +#include "../../managers/EventManager.hpp" +#include "../../managers/permissions/DynamicPermissionManager.hpp" +#include "../../notification/NotificationOverlay.hpp" +#include "../../plugins/PluginSystem.hpp" + +#include "../../managers/input/trackpad/TrackpadGestures.hpp" +#include "../../managers/input/trackpad/gestures/DispatcherGesture.hpp" +#include "../../managers/input/trackpad/gestures/WorkspaceSwipeGesture.hpp" +#include "../../managers/input/trackpad/gestures/ResizeGesture.hpp" +#include "../../managers/input/trackpad/gestures/MoveGesture.hpp" +#include "../../managers/input/trackpad/gestures/SpecialWorkspaceGesture.hpp" +#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 "../../protocols/types/ContentType.hpp" +#include "render/types.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace Hyprutils::String; +using namespace Hyprutils::Animation; +using namespace Config; +using namespace Config::Legacy; +using enum NContentType::eContentType; + +//NOLINTNEXTLINE +extern "C" char** environ; + +#include "../supplementary/ConfigDescriptions.hpp" + +WP Config::Legacy::mgr() { + if (Config::mgr() && Config::mgr()->type() == CONFIG_LEGACY) + return dynamicPointerCast(WP(Config::mgr())); + return nullptr; +} + +static Hyprlang::CParseResult configHandleGradientSet(const char* VALUE, void** data) { + std::string V = VALUE; + + if (!*data) + *data = new Config::CGradientValueData(); + + const auto DATA = sc(*data); + + CVarList2 varlist(std::string(V), 0, ' '); + DATA->m_colors.clear(); + + std::string parseError = ""; + + for (auto const& var : varlist) { + if (var.find("deg") != std::string::npos) { + // last arg + try { + DATA->m_angle = std::stoi(std::string(var.substr(0, var.find("deg")))) * (PI / 180.0); // radians + } catch (...) { + Log::logger->log(Log::WARN, "Error parsing gradient {}", V); + parseError = "Error parsing gradient " + V; + } + + break; + } + + if (DATA->m_colors.size() >= 10) { + Log::logger->log(Log::WARN, "Error parsing gradient {}: max colors is 10.", V); + parseError = "Error parsing gradient " + V + ": max colors is 10."; + break; + } + + try { + const auto COL = configStringToInt(std::string(var)); + if (!COL) + throw std::runtime_error(std::format("failed to parse {} as a color", var)); + DATA->m_colors.emplace_back(COL.value()); + } catch (std::exception& e) { + Log::logger->log(Log::WARN, "Error parsing gradient {}", V); + parseError = "Error parsing gradient " + V + ": " + e.what(); + } + } + + if (DATA->m_colors.empty()) { + Log::logger->log(Log::WARN, "Error parsing gradient {}", V); + if (parseError.empty()) + parseError = "Error parsing gradient " + V + ": No colors?"; + + DATA->m_colors.emplace_back(0); // transparent + } + + DATA->updateColorsOk(); + + Hyprlang::CParseResult result; + if (!parseError.empty()) + result.setError(parseError.c_str()); + + return result; +} + +static void configHandleGradientDestroy(void** data) { + if (*data) + delete sc(*data); +} + +static Hyprlang::CParseResult configHandleGapSet(const char* VALUE, void** data) { + std::string V = VALUE; + + if (!*data) + *data = new CCssGapData(); + + const auto DATA = sc(*data); + CVarList2 varlist((std::string(V))); + Hyprlang::CParseResult result; + + try { + DATA->parseGapData(varlist); + } catch (...) { + std::string parseError = "Error parsing gaps " + V; + result.setError(parseError.c_str()); + } + + return result; +} + +static void configHandleGapDestroy(void** data) { + if (*data) + delete sc(*data); +} + +static Hyprlang::CParseResult configHandleFontWeightSet(const char* VALUE, void** data) { + if (!*data) + *data = new CFontWeightConfigValueData(); + + const auto DATA = sc(*data); + Hyprlang::CParseResult result; + + try { + DATA->parseWeight(VALUE); + } catch (...) { + std::string parseError = std::format("{} is not a valid font weight", VALUE); + result.setError(parseError.c_str()); + } + + return result; +} + +static void configHandleFontWeightDestroy(void** data) { + if (*data) + delete sc(*data); +} + +static Hyprlang::CParseResult handleExec(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = Config::Legacy::mgr()->handleExec(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + return result; +} + +static Hyprlang::CParseResult handleRawExec(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = Config::Legacy::mgr()->handleRawExec(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + return result; +} + +static Hyprlang::CParseResult handleExecOnce(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = Config::Legacy::mgr()->handleExecOnce(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + return result; +} + +static Hyprlang::CParseResult handleExecRawOnce(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = Config::Legacy::mgr()->handleExecRawOnce(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + return result; +} + +static Hyprlang::CParseResult handleExecShutdown(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = Config::Legacy::mgr()->handleExecShutdown(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + return result; +} + +static Hyprlang::CParseResult handleMonitor(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = Config::Legacy::mgr()->handleMonitor(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + return result; +} + +static Hyprlang::CParseResult handleBezier(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = Config::Legacy::mgr()->handleBezier(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + return result; +} + +static Hyprlang::CParseResult handleAnimation(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = Config::Legacy::mgr()->handleAnimation(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + return result; +} + +static Hyprlang::CParseResult handleBind(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = Config::Legacy::mgr()->handleBind(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + return result; +} + +static Hyprlang::CParseResult handleUnbind(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = Config::Legacy::mgr()->handleUnbind(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + return result; +} + +static Hyprlang::CParseResult handleWorkspaceRules(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = Config::Legacy::mgr()->handleWorkspaceRules(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + return result; +} + +static Hyprlang::CParseResult handleSubmap(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = Config::Legacy::mgr()->handleSubmap(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + return result; +} + +static Hyprlang::CParseResult handleSource(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = Config::Legacy::mgr()->handleSource(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + return result; +} + +static Hyprlang::CParseResult handleEnv(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = Config::Legacy::mgr()->handleEnv(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + return result; +} + +static Hyprlang::CParseResult handlePlugin(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = Config::Legacy::mgr()->handlePlugin(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + return result; +} + +static Hyprlang::CParseResult handlePermission(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = Config::Legacy::mgr()->handlePermission(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + return result; +} + +static Hyprlang::CParseResult handleGesture(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = Config::Legacy::mgr()->handleGesture(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + return result; +} + +static Hyprlang::CParseResult handleWindowrule(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = Config::Legacy::mgr()->handleWindowrule(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + 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; + + const auto RESULT = Config::Legacy::mgr()->handleLayerrule(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + 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); +} + +void CConfigManager::registerConfigVar(const char* name, const Hyprlang::FLOAT& val) { + m_configValueNumber++; + m_config->addConfigValue(name, val); +} + +void CConfigManager::registerConfigVar(const char* name, const Hyprlang::VEC2& val) { + m_configValueNumber++; + m_config->addConfigValue(name, val); +} + +void CConfigManager::registerConfigVar(const char* name, const Hyprlang::STRING& val) { + m_configValueNumber++; + m_config->addConfigValue(name, val); +} + +void CConfigManager::registerConfigVar(const char* name, Hyprlang::CUSTOMTYPE&& val) { + m_configValueNumber++; + m_config->addConfigValue(name, std::move(val)); +} + +CConfigManager::CConfigManager() { + const auto ERR = verifyConfigExists(); + + m_mainConfigPath = *Supplementary::Jeremy::getMainConfigPath(); + + m_configPaths.emplace_back(m_mainConfigPath); + m_config = makeUnique(m_configPaths.begin()->c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = true}); + + registerConfigVar("general:border_size", Hyprlang::INT{1}); + registerConfigVar("general:gaps_in", Hyprlang::CConfigCustomValueType{configHandleGapSet, configHandleGapDestroy, "5"}); + registerConfigVar("general:gaps_out", Hyprlang::CConfigCustomValueType{configHandleGapSet, configHandleGapDestroy, "20"}); + registerConfigVar("general:float_gaps", Hyprlang::CConfigCustomValueType{configHandleGapSet, configHandleGapDestroy, "0"}); + registerConfigVar("general:gaps_workspaces", Hyprlang::INT{0}); + registerConfigVar("general:no_focus_fallback", Hyprlang::INT{0}); + registerConfigVar("general:resize_on_border", Hyprlang::INT{0}); + registerConfigVar("general:extend_border_grab_area", Hyprlang::INT{15}); + registerConfigVar("general:hover_icon_on_border", Hyprlang::INT{1}); + registerConfigVar("general:layout", {"dwindle"}); + registerConfigVar("general:allow_tearing", Hyprlang::INT{0}); + registerConfigVar("general:resize_corner", Hyprlang::INT{0}); + registerConfigVar("general:snap:enabled", Hyprlang::INT{0}); + registerConfigVar("general:snap:window_gap", Hyprlang::INT{10}); + registerConfigVar("general:snap:monitor_gap", Hyprlang::INT{10}); + registerConfigVar("general:snap:border_overlap", Hyprlang::INT{0}); + registerConfigVar("general:snap:respect_gaps", Hyprlang::INT{0}); + registerConfigVar("general:col.active_border", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0xffffffff"}); + registerConfigVar("general:col.inactive_border", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0xff444444"}); + registerConfigVar("general:col.nogroup_border", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0xffffaaff"}); + registerConfigVar("general:col.nogroup_border_active", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0xffff00ff"}); + registerConfigVar("general:modal_parent_blocking", Hyprlang::INT{1}); + registerConfigVar("general:locale", {""}); + + registerConfigVar("misc:disable_hyprland_logo", Hyprlang::INT{0}); + registerConfigVar("misc:disable_splash_rendering", Hyprlang::INT{0}); + registerConfigVar("misc:col.splash", Hyprlang::INT{0x55ffffff}); + registerConfigVar("misc:splash_font_family", {STRVAL_EMPTY}); + registerConfigVar("misc:font_family", {"Sans"}); + registerConfigVar("misc:force_default_wallpaper", Hyprlang::INT{-1}); + registerConfigVar("misc:vrr", Hyprlang::INT{0}); + registerConfigVar("misc:mouse_move_enables_dpms", Hyprlang::INT{0}); + registerConfigVar("misc:key_press_enables_dpms", Hyprlang::INT{0}); + registerConfigVar("misc:name_vk_after_proc", Hyprlang::INT{1}); + registerConfigVar("misc:always_follow_on_dnd", Hyprlang::INT{1}); + registerConfigVar("misc:layers_hog_keyboard_focus", Hyprlang::INT{1}); + registerConfigVar("misc:animate_manual_resizes", Hyprlang::INT{0}); + registerConfigVar("misc:animate_mouse_windowdragging", Hyprlang::INT{0}); + registerConfigVar("misc:disable_autoreload", Hyprlang::INT{0}); + registerConfigVar("misc:enable_swallow", Hyprlang::INT{0}); + registerConfigVar("misc:swallow_regex", {STRVAL_EMPTY}); + registerConfigVar("misc:swallow_exception_regex", {STRVAL_EMPTY}); + registerConfigVar("misc:focus_on_activate", Hyprlang::INT{0}); + registerConfigVar("misc:mouse_move_focuses_monitor", Hyprlang::INT{1}); + registerConfigVar("misc:allow_session_lock_restore", Hyprlang::INT{0}); + registerConfigVar("misc:session_lock_xray", Hyprlang::INT{0}); + registerConfigVar("misc:close_special_on_empty", Hyprlang::INT{1}); + registerConfigVar("misc:background_color", Hyprlang::INT{0xff111111}); + registerConfigVar("misc:on_focus_under_fullscreen", Hyprlang::INT{2}); + registerConfigVar("misc:exit_window_retains_fullscreen", Hyprlang::INT{0}); + registerConfigVar("misc:initial_workspace_tracking", Hyprlang::INT{1}); + registerConfigVar("misc:middle_click_paste", Hyprlang::INT{1}); + registerConfigVar("misc:render_unfocused_fps", Hyprlang::INT{15}); + registerConfigVar("misc:disable_xdg_env_checks", Hyprlang::INT{0}); + registerConfigVar("misc:disable_hyprland_guiutils_check", Hyprlang::INT{0}); + registerConfigVar("misc:disable_watchdog_warning", Hyprlang::INT{0}); + registerConfigVar("misc:lockdead_screen_delay", Hyprlang::INT{1000}); + registerConfigVar("misc:enable_anr_dialog", Hyprlang::INT{1}); + registerConfigVar("misc:anr_missed_pings", Hyprlang::INT{5}); + registerConfigVar("misc:screencopy_force_8b", Hyprlang::INT{1}); + registerConfigVar("misc:disable_scale_notification", Hyprlang::INT{0}); + registerConfigVar("misc:size_limits_tiled", Hyprlang::INT{0}); + + registerConfigVar("group:insert_after_current", Hyprlang::INT{1}); + registerConfigVar("group:focus_removed_window", Hyprlang::INT{1}); + registerConfigVar("group:merge_groups_on_drag", Hyprlang::INT{1}); + registerConfigVar("group:merge_groups_on_groupbar", Hyprlang::INT{1}); + registerConfigVar("group:merge_floated_into_tiled_on_groupbar", Hyprlang::INT{0}); + registerConfigVar("group:auto_group", Hyprlang::INT{1}); + registerConfigVar("group:drag_into_group", Hyprlang::INT{1}); + registerConfigVar("group:group_on_movetoworkspace", Hyprlang::INT{0}); + registerConfigVar("group:groupbar:enabled", Hyprlang::INT{1}); + registerConfigVar("group:groupbar:font_family", {STRVAL_EMPTY}); + registerConfigVar("group:groupbar:font_weight_active", Hyprlang::CConfigCustomValueType{&configHandleFontWeightSet, configHandleFontWeightDestroy, "normal"}); + registerConfigVar("group:groupbar:font_weight_inactive", Hyprlang::CConfigCustomValueType{&configHandleFontWeightSet, configHandleFontWeightDestroy, "normal"}); + registerConfigVar("group:groupbar:font_size", Hyprlang::INT{8}); + registerConfigVar("group:groupbar:gradients", Hyprlang::INT{0}); + registerConfigVar("group:groupbar:height", Hyprlang::INT{14}); + registerConfigVar("group:groupbar:indicator_gap", Hyprlang::INT{0}); + registerConfigVar("group:groupbar:indicator_height", Hyprlang::INT{3}); + registerConfigVar("group:groupbar:priority", Hyprlang::INT{3}); + registerConfigVar("group:groupbar:render_titles", Hyprlang::INT{1}); + registerConfigVar("group:groupbar:scrolling", Hyprlang::INT{1}); + registerConfigVar("group:groupbar:text_color", Hyprlang::INT{0xffffffff}); + registerConfigVar("group:groupbar:text_color_inactive", Hyprlang::INT{-1}); + registerConfigVar("group:groupbar:text_color_locked_active", Hyprlang::INT{-1}); + registerConfigVar("group:groupbar:text_color_locked_inactive", Hyprlang::INT{-1}); + registerConfigVar("group:groupbar:stacked", Hyprlang::INT{0}); + registerConfigVar("group:groupbar:rounding", Hyprlang::INT{1}); + registerConfigVar("group:groupbar:rounding_power", {2.F}); + registerConfigVar("group:groupbar:gradient_rounding", Hyprlang::INT{2}); + registerConfigVar("group:groupbar:gradient_rounding_power", {2.F}); + registerConfigVar("group:groupbar:round_only_edges", Hyprlang::INT{1}); + registerConfigVar("group:groupbar:gradient_round_only_edges", Hyprlang::INT{1}); + registerConfigVar("group:groupbar:gaps_out", Hyprlang::INT{2}); + 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:vfr", Hyprlang::INT{1}); + 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}); + registerConfigVar("debug:damage_tracking", {sc(Render::DAMAGE_TRACKING_FULL)}); + registerConfigVar("debug:manual_crash", Hyprlang::INT{0}); + registerConfigVar("debug:suppress_errors", Hyprlang::INT{0}); + registerConfigVar("debug:error_limit", Hyprlang::INT{5}); + registerConfigVar("debug:error_position", Hyprlang::INT{0}); + 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("debug:invalidate_fp16", Hyprlang::INT{2}); + + registerConfigVar("decoration:rounding", Hyprlang::INT{0}); + registerConfigVar("decoration:rounding_power", {2.F}); + registerConfigVar("decoration:blur:enabled", Hyprlang::INT{1}); + registerConfigVar("decoration:blur:size", Hyprlang::INT{8}); + registerConfigVar("decoration:blur:passes", Hyprlang::INT{1}); + registerConfigVar("decoration:blur:ignore_opacity", Hyprlang::INT{1}); + registerConfigVar("decoration:blur:new_optimizations", Hyprlang::INT{1}); + registerConfigVar("decoration:blur:xray", Hyprlang::INT{0}); + registerConfigVar("decoration:blur:contrast", {0.8916F}); + registerConfigVar("decoration:blur:brightness", {1.0F}); + registerConfigVar("decoration:blur:vibrancy", {0.1696F}); + registerConfigVar("decoration:blur:vibrancy_darkness", {0.0F}); + registerConfigVar("decoration:blur:noise", {0.0117F}); + registerConfigVar("decoration:blur:special", Hyprlang::INT{0}); + registerConfigVar("decoration:blur:popups", Hyprlang::INT{0}); + registerConfigVar("decoration:blur:popups_ignorealpha", {0.2F}); + registerConfigVar("decoration:blur:input_methods", Hyprlang::INT{0}); + registerConfigVar("decoration:blur:input_methods_ignorealpha", {0.2F}); + registerConfigVar("decoration:active_opacity", {1.F}); + registerConfigVar("decoration:inactive_opacity", {1.F}); + registerConfigVar("decoration:fullscreen_opacity", {1.F}); + registerConfigVar("decoration:shadow:enabled", Hyprlang::INT{1}); + registerConfigVar("decoration:shadow:range", Hyprlang::INT{4}); + registerConfigVar("decoration:shadow:render_power", Hyprlang::INT{3}); + registerConfigVar("decoration:shadow:offset", Hyprlang::VEC2{0, 0}); + registerConfigVar("decoration:shadow:scale", {1.f}); + registerConfigVar("decoration:shadow:sharp", Hyprlang::INT{0}); + registerConfigVar("decoration:shadow:color", Hyprlang::INT{0xee1a1a1a}); + registerConfigVar("decoration:shadow:color_inactive", Hyprlang::INT{-1}); + registerConfigVar("decoration:glow:enabled", Hyprlang::INT{0}); + registerConfigVar("decoration:glow:range", Hyprlang::INT{10}); + registerConfigVar("decoration:glow:render_power", Hyprlang::INT{3}); + registerConfigVar("decoration:glow:color", Hyprlang::INT{0xee33ccff}); + registerConfigVar("decoration:glow:color_inactive", Hyprlang::INT{0x0033ccff}); + registerConfigVar("decoration:dim_inactive", Hyprlang::INT{0}); + registerConfigVar("decoration:dim_modal", Hyprlang::INT{1}); + registerConfigVar("decoration:dim_strength", {0.5f}); + registerConfigVar("decoration:dim_special", {0.2f}); + registerConfigVar("decoration:dim_around", {0.4f}); + 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}); + registerConfigVar("dwindle:preserve_split", Hyprlang::INT{0}); + registerConfigVar("dwindle:special_scale_factor", {1.f}); + registerConfigVar("dwindle:split_width_multiplier", {1.0f}); + registerConfigVar("dwindle:use_active_for_splits", Hyprlang::INT{1}); + registerConfigVar("dwindle:default_split_ratio", {1.f}); + registerConfigVar("dwindle:split_bias", Hyprlang::INT{0}); + registerConfigVar("dwindle:smart_split", Hyprlang::INT{0}); + registerConfigVar("dwindle:smart_resizing", Hyprlang::INT{1}); + registerConfigVar("dwindle:precise_mouse_move", Hyprlang::INT{0}); + + registerConfigVar("master:special_scale_factor", {1.f}); + registerConfigVar("master:mfact", {0.55f}); + registerConfigVar("master:new_status", {"slave"}); + registerConfigVar("master:slave_count_for_center_master", Hyprlang::INT{2}); + registerConfigVar("master:center_master_fallback", {"left"}); + registerConfigVar("master:center_ignores_reserved", Hyprlang::INT{0}); + registerConfigVar("master:new_on_active", {"none"}); + registerConfigVar("master:new_on_top", Hyprlang::INT{0}); + registerConfigVar("master:orientation", {"left"}); + registerConfigVar("master:allow_small_split", Hyprlang::INT{0}); + registerConfigVar("master:smart_resizing", Hyprlang::INT{1}); + 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}); + + registerConfigVar("input:follow_mouse", Hyprlang::INT{1}); + registerConfigVar("input:follow_mouse_shrink", Hyprlang::INT{0}); + registerConfigVar("input:follow_mouse_threshold", Hyprlang::FLOAT{0}); + registerConfigVar("input:focus_on_close", Hyprlang::INT{0}); + registerConfigVar("input:mouse_refocus", Hyprlang::INT{1}); + registerConfigVar("input:special_fallthrough", Hyprlang::INT{0}); + registerConfigVar("input:off_window_axis_events", Hyprlang::INT{1}); + registerConfigVar("input:sensitivity", {0.f}); + registerConfigVar("input:accel_profile", {STRVAL_EMPTY}); + registerConfigVar("input:rotation", Hyprlang::INT{0}); + registerConfigVar("input:kb_file", {STRVAL_EMPTY}); + registerConfigVar("input:kb_layout", {"us"}); + registerConfigVar("input:kb_variant", {STRVAL_EMPTY}); + registerConfigVar("input:kb_options", {STRVAL_EMPTY}); + registerConfigVar("input:kb_rules", {STRVAL_EMPTY}); + registerConfigVar("input:kb_model", {STRVAL_EMPTY}); + registerConfigVar("input:repeat_rate", Hyprlang::INT{25}); + registerConfigVar("input:repeat_delay", Hyprlang::INT{600}); + registerConfigVar("input:natural_scroll", Hyprlang::INT{0}); + registerConfigVar("input:numlock_by_default", Hyprlang::INT{0}); + registerConfigVar("input:resolve_binds_by_sym", Hyprlang::INT{0}); + registerConfigVar("input:force_no_accel", Hyprlang::INT{0}); + registerConfigVar("input:float_switch_override_focus", Hyprlang::INT{1}); + registerConfigVar("input:left_handed", Hyprlang::INT{0}); + registerConfigVar("input:scroll_method", {STRVAL_EMPTY}); + registerConfigVar("input:scroll_button", Hyprlang::INT{0}); + registerConfigVar("input:scroll_button_lock", Hyprlang::INT{0}); + registerConfigVar("input:scroll_factor", {1.f}); + registerConfigVar("input:scroll_points", {STRVAL_EMPTY}); + registerConfigVar("input:emulate_discrete_scroll", Hyprlang::INT{1}); + registerConfigVar("input:touchpad:natural_scroll", Hyprlang::INT{0}); + registerConfigVar("input:touchpad:disable_while_typing", Hyprlang::INT{1}); + registerConfigVar("input:touchpad:clickfinger_behavior", Hyprlang::INT{0}); + registerConfigVar("input:touchpad:tap_button_map", {STRVAL_EMPTY}); + registerConfigVar("input:touchpad:middle_button_emulation", Hyprlang::INT{0}); + registerConfigVar("input:touchpad:tap-to-click", Hyprlang::INT{1}); + registerConfigVar("input:touchpad:tap-and-drag", Hyprlang::INT{1}); + registerConfigVar("input:touchpad:drag_lock", Hyprlang::INT{0}); + registerConfigVar("input:touchpad:scroll_factor", {1.f}); + registerConfigVar("input:touchpad:flip_x", Hyprlang::INT{0}); + registerConfigVar("input:touchpad:flip_y", Hyprlang::INT{0}); + registerConfigVar("input:touchpad:drag_3fg", Hyprlang::INT{0}); + registerConfigVar("input:touchdevice:transform", Hyprlang::INT{-1}); + registerConfigVar("input:touchdevice:output", {"[[Auto]]"}); + registerConfigVar("input:touchdevice:enabled", Hyprlang::INT{1}); + registerConfigVar("input:virtualkeyboard:share_states", Hyprlang::INT{2}); + registerConfigVar("input:virtualkeyboard:release_pressed_on_close", Hyprlang::INT{0}); + registerConfigVar("input:tablet:transform", Hyprlang::INT{0}); + registerConfigVar("input:tablet:output", {STRVAL_EMPTY}); + registerConfigVar("input:tablet:region_position", Hyprlang::VEC2{0, 0}); + registerConfigVar("input:tablet:absolute_region_position", Hyprlang::INT{0}); + registerConfigVar("input:tablet:region_size", Hyprlang::VEC2{0, 0}); + registerConfigVar("input:tablet:relative_input", Hyprlang::INT{0}); + registerConfigVar("input:tablet:left_handed", Hyprlang::INT{0}); + registerConfigVar("input:tablet:active_area_position", Hyprlang::VEC2{0, 0}); + registerConfigVar("input:tablet:active_area_size", Hyprlang::VEC2{0, 0}); + + registerConfigVar("binds:pass_mouse_when_bound", Hyprlang::INT{0}); + registerConfigVar("binds:scroll_event_delay", Hyprlang::INT{300}); + registerConfigVar("binds:workspace_back_and_forth", Hyprlang::INT{0}); + registerConfigVar("binds:hide_special_on_workspace_change", Hyprlang::INT{0}); + registerConfigVar("binds:allow_workspace_cycles", Hyprlang::INT{0}); + registerConfigVar("binds:workspace_center_on", Hyprlang::INT{1}); + registerConfigVar("binds:focus_preferred_method", Hyprlang::INT{0}); + registerConfigVar("binds:ignore_group_lock", Hyprlang::INT{0}); + 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: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}); + registerConfigVar("gestures:workspace_swipe_min_speed_to_force", Hyprlang::INT{30}); + registerConfigVar("gestures:workspace_swipe_cancel_ratio", {0.5f}); + registerConfigVar("gestures:workspace_swipe_create_new", Hyprlang::INT{1}); + registerConfigVar("gestures:workspace_swipe_direction_lock", Hyprlang::INT{1}); + registerConfigVar("gestures:workspace_swipe_direction_lock_threshold", Hyprlang::INT{10}); + registerConfigVar("gestures:workspace_swipe_forever", Hyprlang::INT{0}); + registerConfigVar("gestures:workspace_swipe_use_r", Hyprlang::INT{0}); + registerConfigVar("gestures:workspace_swipe_touch", Hyprlang::INT{0}); + registerConfigVar("gestures:workspace_swipe_touch_invert", Hyprlang::INT{0}); + registerConfigVar("gestures:close_max_timeout", Hyprlang::INT{1000}); + + registerConfigVar("xwayland:enabled", Hyprlang::INT{1}); + registerConfigVar("xwayland:use_nearest_neighbor", Hyprlang::INT{1}); + registerConfigVar("xwayland:force_zero_scaling", Hyprlang::INT{0}); + registerConfigVar("xwayland:create_abstract_socket", Hyprlang::INT{0}); + + registerConfigVar("opengl:nvidia_anti_flicker", Hyprlang::INT{1}); + + registerConfigVar("cursor:invisible", Hyprlang::INT{0}); + registerConfigVar("cursor:no_hardware_cursors", Hyprlang::INT{2}); + registerConfigVar("cursor:no_break_fs_vrr", Hyprlang::INT{2}); + registerConfigVar("cursor:min_refresh_rate", Hyprlang::INT{24}); + registerConfigVar("cursor:hotspot_padding", Hyprlang::INT{0}); + registerConfigVar("cursor:inactive_timeout", {0.f}); + registerConfigVar("cursor:no_warps", Hyprlang::INT{0}); + registerConfigVar("cursor:persistent_warps", Hyprlang::INT{0}); + registerConfigVar("cursor:warp_on_change_workspace", Hyprlang::INT{0}); + registerConfigVar("cursor:warp_on_toggle_special", Hyprlang::INT{0}); + registerConfigVar("cursor:default_monitor", {STRVAL_EMPTY}); + registerConfigVar("cursor:zoom_factor", {1.f}); + registerConfigVar("cursor:zoom_rigid", Hyprlang::INT{0}); + registerConfigVar("cursor:zoom_disable_aa", Hyprlang::INT{0}); + registerConfigVar("cursor:zoom_detached_camera", Hyprlang::INT{1}); + registerConfigVar("cursor:enable_hyprcursor", Hyprlang::INT{1}); + registerConfigVar("cursor:sync_gsettings_theme", Hyprlang::INT{1}); + registerConfigVar("cursor:hide_on_key_press", Hyprlang::INT{0}); + registerConfigVar("cursor:hide_on_touch", Hyprlang::INT{1}); + registerConfigVar("cursor:hide_on_tablet", Hyprlang::INT{0}); + registerConfigVar("cursor:use_cpu_buffer", Hyprlang::INT{2}); + registerConfigVar("cursor:warp_back_after_non_mouse_input", Hyprlang::INT{0}); + + registerConfigVar("autogenerated", Hyprlang::INT{0}); + + registerConfigVar("group:col.border_active", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66ffff00"}); + registerConfigVar("group:col.border_inactive", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66777700"}); + registerConfigVar("group:col.border_locked_active", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66ff5500"}); + registerConfigVar("group:col.border_locked_inactive", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66775500"}); + + registerConfigVar("group:groupbar:col.active", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66ffff00"}); + registerConfigVar("group:groupbar:col.inactive", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66777700"}); + registerConfigVar("group:groupbar:col.locked_active", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66ff5500"}); + registerConfigVar("group:groupbar:col.locked_inactive", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66775500"}); + + registerConfigVar("render:direct_scanout", Hyprlang::INT{0}); + registerConfigVar("render:expand_undersized_textures", Hyprlang::INT{1}); + registerConfigVar("render:xp_mode", Hyprlang::INT{0}); + registerConfigVar("render:ctm_animation", Hyprlang::INT{2}); + registerConfigVar("render:cm_enabled", Hyprlang::INT{1}); + registerConfigVar("render:send_content_type", Hyprlang::INT{1}); + registerConfigVar("render:cm_auto_hdr", Hyprlang::INT{1}); + registerConfigVar("render:new_render_scheduling", Hyprlang::INT{0}); + registerConfigVar("render:non_shader_cm", Hyprlang::INT{2}); + registerConfigVar("render:non_shader_cm_interop", Hyprlang::INT{2}); + 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:use_fp16", Hyprlang::INT{2}); + registerConfigVar("render:keep_unmodified_copy", Hyprlang::INT{2}); + + registerConfigVar("ecosystem:no_update_news", Hyprlang::INT{0}); + registerConfigVar("ecosystem:no_donation_nag", Hyprlang::INT{0}); + registerConfigVar("ecosystem:enforce_permissions", Hyprlang::INT{0}); + + registerConfigVar("experimental:wp_cm_1_2", 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"}); + m_config->addSpecialConfigValue("device", "sensitivity", {0.F}); + m_config->addSpecialConfigValue("device", "accel_profile", {STRVAL_EMPTY}); + m_config->addSpecialConfigValue("device", "rotation", Hyprlang::INT{0}); + m_config->addSpecialConfigValue("device", "kb_file", {STRVAL_EMPTY}); + m_config->addSpecialConfigValue("device", "kb_layout", {"us"}); + m_config->addSpecialConfigValue("device", "kb_variant", {STRVAL_EMPTY}); + m_config->addSpecialConfigValue("device", "kb_options", {STRVAL_EMPTY}); + m_config->addSpecialConfigValue("device", "kb_rules", {STRVAL_EMPTY}); + m_config->addSpecialConfigValue("device", "kb_model", {STRVAL_EMPTY}); + m_config->addSpecialConfigValue("device", "repeat_rate", Hyprlang::INT{25}); + m_config->addSpecialConfigValue("device", "repeat_delay", Hyprlang::INT{600}); + m_config->addSpecialConfigValue("device", "natural_scroll", Hyprlang::INT{0}); + m_config->addSpecialConfigValue("device", "tap_button_map", {STRVAL_EMPTY}); + m_config->addSpecialConfigValue("device", "numlock_by_default", Hyprlang::INT{0}); + m_config->addSpecialConfigValue("device", "resolve_binds_by_sym", Hyprlang::INT{0}); + m_config->addSpecialConfigValue("device", "disable_while_typing", Hyprlang::INT{1}); + m_config->addSpecialConfigValue("device", "clickfinger_behavior", Hyprlang::INT{0}); + m_config->addSpecialConfigValue("device", "middle_button_emulation", Hyprlang::INT{0}); + m_config->addSpecialConfigValue("device", "tap-to-click", Hyprlang::INT{1}); + m_config->addSpecialConfigValue("device", "tap-and-drag", Hyprlang::INT{1}); + m_config->addSpecialConfigValue("device", "drag_lock", Hyprlang::INT{0}); + m_config->addSpecialConfigValue("device", "left_handed", Hyprlang::INT{0}); + m_config->addSpecialConfigValue("device", "scroll_method", {STRVAL_EMPTY}); + m_config->addSpecialConfigValue("device", "scroll_button", Hyprlang::INT{0}); + m_config->addSpecialConfigValue("device", "scroll_button_lock", Hyprlang::INT{0}); + m_config->addSpecialConfigValue("device", "scroll_points", {STRVAL_EMPTY}); + m_config->addSpecialConfigValue("device", "scroll_factor", Hyprlang::FLOAT{-1}); + m_config->addSpecialConfigValue("device", "transform", Hyprlang::INT{-1}); + m_config->addSpecialConfigValue("device", "output", {STRVAL_EMPTY}); + m_config->addSpecialConfigValue("device", "enabled", Hyprlang::INT{1}); // only for mice, touchpads, and touchdevices + m_config->addSpecialConfigValue("device", "region_position", Hyprlang::VEC2{0, 0}); // only for tablets + m_config->addSpecialConfigValue("device", "absolute_region_position", Hyprlang::INT{0}); // only for tablets + m_config->addSpecialConfigValue("device", "region_size", Hyprlang::VEC2{0, 0}); // only for tablets + m_config->addSpecialConfigValue("device", "relative_input", Hyprlang::INT{0}); // only for tablets + m_config->addSpecialConfigValue("device", "active_area_position", Hyprlang::VEC2{0, 0}); // only for tablets + m_config->addSpecialConfigValue("device", "active_area_size", Hyprlang::VEC2{0, 0}); // only for tablets + m_config->addSpecialConfigValue("device", "flip_x", Hyprlang::INT{0}); // only for touchpads + m_config->addSpecialConfigValue("device", "flip_y", Hyprlang::INT{0}); // only for touchpads + m_config->addSpecialConfigValue("device", "drag_3fg", Hyprlang::INT{0}); // only for touchpads + m_config->addSpecialConfigValue("device", "keybinds", Hyprlang::INT{1}); // enable/disable keybinds + m_config->addSpecialConfigValue("device", "share_states", Hyprlang::INT{0}); // only for virtualkeyboards + m_config->addSpecialConfigValue("device", "release_pressed_on_close", Hyprlang::INT{0}); // only for virtualkeyboards + + m_config->addSpecialCategory("monitorv2", {.key = "output"}); + m_config->addSpecialConfigValue("monitorv2", "disabled", Hyprlang::INT{0}); + m_config->addSpecialConfigValue("monitorv2", "mode", {"preferred"}); + m_config->addSpecialConfigValue("monitorv2", "position", {"auto"}); + m_config->addSpecialConfigValue("monitorv2", "scale", {"auto"}); + m_config->addSpecialConfigValue("monitorv2", "addreserved", {STRVAL_EMPTY}); + 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", "sdrbrightness", Hyprlang::FLOAT{1.0}); + m_config->addSpecialConfigValue("monitorv2", "sdrsaturation", Hyprlang::FLOAT{1.0}); + m_config->addSpecialConfigValue("monitorv2", "vrr", Hyprlang::INT{0}); + m_config->addSpecialConfigValue("monitorv2", "transform", {STRVAL_EMPTY}); // TODO use correct type + m_config->addSpecialConfigValue("monitorv2", "supports_wide_color", Hyprlang::INT{0}); + m_config->addSpecialConfigValue("monitorv2", "supports_hdr", Hyprlang::INT{0}); + m_config->addSpecialConfigValue("monitorv2", "sdr_min_luminance", Hyprlang::FLOAT{0.2}); + m_config->addSpecialConfigValue("monitorv2", "sdr_max_luminance", Hyprlang::INT{80}); + 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"}); + m_config->addSpecialConfigValue("windowrule", "enable", Hyprlang::INT{1}); + + // layerrule v2 + m_config->addSpecialCategory("layerrule", {.key = "name"}); + m_config->addSpecialConfigValue("layerrule", "enable", Hyprlang::INT{1}); + + reloadRuleConfigs(); + + // keywords + m_config->registerHandler(&::handleExec, "exec", {false}); + m_config->registerHandler(&::handleRawExec, "execr", {false}); + m_config->registerHandler(&::handleExecOnce, "exec-once", {false}); + m_config->registerHandler(&::handleExecRawOnce, "execr-once", {false}); + m_config->registerHandler(&::handleExecShutdown, "exec-shutdown", {false}); + m_config->registerHandler(&::handleMonitor, "monitor", {false}); + m_config->registerHandler(&::handleBind, "bind", {true}); + m_config->registerHandler(&::handleUnbind, "unbind", {false}); + m_config->registerHandler(&::handleWorkspaceRules, "workspace", {false}); + m_config->registerHandler(&::handleWindowrule, "windowrule", {false}); + m_config->registerHandler(&::handleLayerrule, "layerrule", {false}); + m_config->registerHandler(&::handleBezier, "bezier", {false}); + m_config->registerHandler(&::handleAnimation, "animation", {false}); + m_config->registerHandler(&::handleSource, "source", {false}); + 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(&::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}); + + m_config->commence(); + + resetHLConfig(); + + if (Config::Supplementary::CONFIG_OPTIONS.size() != m_configValueNumber - 1 /* autogenerated is special */) + Log::logger->log(Log::DEBUG, "Warning: config descriptions have {} entries, but there are {} config values. This should fail tests!!", + Config::Supplementary::CONFIG_OPTIONS.size(), m_configValueNumber); + + if (!g_pCompositor->m_onlyConfigVerification) { + Log::logger->log( + Log::DEBUG, + "!!!!HEY YOU, YES YOU!!!!: further logs to stdout / logfile are disabled by default. BEFORE SENDING THIS LOG, ENABLE THEM. Use debug:disable_logs = false to do so: " + "https://wiki.hypr.land/Configuring/Variables/#debug"); + } + + if (g_pEventLoopManager && ERR.has_value()) + g_pEventLoopManager->doLater([ERR] { ErrorOverlay::overlay()->queueCreate(ERR.value(), ErrorOverlay::Colors::ERROR); }); +} + +eConfigManagerType CConfigManager::type() { + return CONFIG_LEGACY; +} + +void CConfigManager::reloadRuleConfigs() { + // FIXME: this should also remove old values if they are removed + + for (const auto& r : Desktop::Rule::allMatchPropStrings()) { + m_config->addSpecialConfigValue("windowrule", ("match:" + r).c_str(), Hyprlang::STRING{""}); + } + + for (const auto& r : Desktop::Rule::windowEffects()->allEffectStrings()) { + m_config->addSpecialConfigValue("windowrule", r.c_str(), Hyprlang::STRING{""}); + } + + for (const auto& r : Desktop::Rule::allMatchPropStrings()) { + m_config->addSpecialConfigValue("layerrule", ("match:" + r).c_str(), Hyprlang::STRING{""}); + } + + for (const auto& r : Desktop::Rule::layerEffects()->allEffectStrings()) { + m_config->addSpecialConfigValue("layerrule", r.c_str(), Hyprlang::STRING{""}); + } +} + +std::optional CConfigManager::verifyConfigExists() { + auto mainConfigPath = Supplementary::Jeremy::getMainConfigPath(); + + if (!mainConfigPath) + return "Broken config directory"; + + std::error_code ec; + const bool VALID_CFG = std::filesystem::exists(*mainConfigPath, ec) && !ec; + + if (!VALID_CFG && !g_pCompositor->m_explicitConfigPath.empty()) + return "Invalid config file provided as explicit"; + + if (!VALID_CFG) { + if (const auto res = generateDefaultConfig(*mainConfigPath, g_pCompositor->m_safeMode); !res) + return res.error(); + } + + return {}; +} + +std::string CConfigManager::getConfigString() { + std::string configString; + std::string currFileContent; + + for (const auto& path : m_configPaths) { + std::ifstream configFile(path); + configString += ("\n\nConfig File: " + path + ": "); + if (!configFile.is_open()) { + Log::logger->log(Log::DEBUG, "Config file not readable/found!"); + configString += "Read Failed\n"; + continue; + } + configString += "Read Succeeded\n"; + currFileContent.assign(std::istreambuf_iterator(configFile), std::istreambuf_iterator()); + configString.append(currFileContent); + } + return configString; +} + +std::string CConfigManager::getErrors() { + return m_configErrors; +} + +static std::vector HL_VERSION_VARS = { + "HYPRLAND_V_0_53", +}; + +static void exportHlVersionVars() { + for (const auto& v : HL_VERSION_VARS) { + setenv(v, "1", 1); + } +} + +static void clearHlVersionVars() { + for (const auto& v : HL_VERSION_VARS) { + unsetenv(v); + } +} + +void CConfigManager::reload() { + Event::bus()->m_events.config.preReload.emit(); + Config::animationTree()->reset(); + Config::workspaceRuleMgr()->clear(); + Config::monitorRuleMgr()->clear(); + resetHLConfig(); + + auto oldConfigPath = m_mainConfigPath; + + m_mainConfigPath = *Supplementary::Jeremy::getMainConfigPath(); + m_configCurrentPath = m_mainConfigPath; + + if (m_mainConfigPath != oldConfigPath) + m_config->changeRootPath(m_mainConfigPath.c_str()); + + exportHlVersionVars(); + + const auto ERR = m_config->parse(); + + clearHlVersionVars(); + + const auto monitorError = handleMonitorv2(); + const auto ruleError = reloadRules(); + m_lastConfigVerificationWasSuccessful = !ERR.error && !monitorError.error; + postConfigReload(ERR.error || !monitorError.error ? ERR : monitorError); +} + +std::string CConfigManager::verify() { + Config::animationTree()->reset(); + resetHLConfig(); + m_configCurrentPath = *Supplementary::Jeremy::getMainConfigPath(); + const auto ERR = m_config->parse(); + m_lastConfigVerificationWasSuccessful = !ERR.error; + if (ERR.error) + return ERR.getError(); + return "config ok"; +} + +std::optional CConfigManager::resetHLConfig() { + g_pKeybindManager->clearKeybinds(); + g_pAnimationManager->removeAllBeziers(); + g_pAnimationManager->addBezierWithName("linear", Vector2D(0.0, 0.0), Vector2D(1.0, 1.0)); + g_pTrackpadGestures->clearGestures(); + + Config::animationTree()->reset(); + m_declaredPlugins.clear(); + m_failedPluginConfigValues.clear(); + m_keywordRules.clear(); + + // paths + m_configPaths.clear(); + std::string mainConfigPath = getMainConfigPath(); + Log::logger->log(Log::DEBUG, "Using config: {}", mainConfigPath); + m_configPaths.emplace_back(mainConfigPath); + + const auto RET = verifyConfigExists(); + + reloadRuleConfigs(); + + return RET; +} + +std::optional CConfigManager::handleMonitorv2(const std::string& output) { + auto parser = Config::CMonitorRuleParser(output); + auto VAL = m_config->getSpecialConfigValuePtr("monitorv2", "disabled", output.c_str()); + if (VAL && VAL->m_bSetByUser && std::any_cast(VAL->getValue())) + parser.setDisabled(); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "mode", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.parseMode(std::any_cast(VAL->getValue())); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "position", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.parsePosition(std::any_cast(VAL->getValue())); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "scale", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.parseScale(std::any_cast(VAL->getValue())); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "addreserved", output.c_str()); + if (VAL && VAL->m_bSetByUser) { + const auto ARGS = CVarList(std::any_cast(VAL->getValue())); + try { + // top, right, bottom, left + parser.setReserved({std::stoi(ARGS[0]), std::stoi(ARGS[3]), std::stoi(ARGS[1]), std::stoi(ARGS[2])}); + } catch (...) { return "parse error: invalid reserved area"; } + } + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "mirror", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.setMirror(std::any_cast(VAL->getValue())); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "bitdepth", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.parseBitdepth(std::any_cast(VAL->getValue())); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "cm", output.c_str()); + 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().m_sdrEotf = NTransferFunction::TF_AUTO; + else if (value == "1") + parser.rule().m_sdrEotf = NTransferFunction::TF_SRGB; + else if (value == "2") + parser.rule().m_sdrEotf = NTransferFunction::TF_GAMMA22; + else + parser.rule().m_sdrEotf = NTransferFunction::fromString(value); + } + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdrbrightness", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().m_sdrBrightness = std::any_cast(VAL->getValue()); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdrsaturation", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().m_sdrSaturation = std::any_cast(VAL->getValue()); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "vrr", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().m_vrr = std::any_cast(VAL->getValue()); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "transform", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.parseTransform(std::any_cast(VAL->getValue())); + + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "supports_wide_color", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().m_supportsWideColor = std::any_cast(VAL->getValue()); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "supports_hdr", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().m_supportsHDR = std::any_cast(VAL->getValue()); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdr_min_luminance", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().m_sdrMinLuminance = std::any_cast(VAL->getValue()); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdr_max_luminance", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().m_sdrMaxLuminance = std::any_cast(VAL->getValue()); + + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "min_luminance", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().m_minLuminance = std::any_cast(VAL->getValue()); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "max_luminance", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().m_maxLuminance = std::any_cast(VAL->getValue()); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "max_avg_luminance", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().m_maxAvgLuminance = std::any_cast(VAL->getValue()); + + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "icc", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().m_iccFile = std::any_cast(VAL->getValue()); + + auto newrule = parser.rule(); + + Config::monitorRuleMgr()->add(std::move(parser.rule())); + + return parser.getError(); +} + +Hyprlang::CParseResult CConfigManager::handleMonitorv2() { + Hyprlang::CParseResult result; + for (const auto& output : m_config->listKeysForSpecialCategory("monitorv2")) { + const auto error = handleMonitorv2(output); + if (error.has_value()) { + result.setError(error.value().c_str()); + return result; + } + } + return result; +} + +std::optional CConfigManager::addRuleFromConfigKey(const std::string& name) { + const auto ENABLED = m_config->getSpecialConfigValuePtr("windowrule", "enable", name.c_str()); + if (ENABLED && ENABLED->m_bSetByUser && std::any_cast(ENABLED->getValue()) == 0) + return std::nullopt; + + SP rule = makeShared(name); + + for (const auto& r : Desktop::Rule::allMatchPropStrings()) { + auto VAL = m_config->getSpecialConfigValuePtr("windowrule", ("match:" + r).c_str(), name.c_str()); + if (VAL && VAL->m_bSetByUser) + rule->registerMatch(Desktop::Rule::matchPropFromString(r).value_or(Desktop::Rule::RULE_PROP_NONE), std::any_cast(VAL->getValue())); + } + + for (const auto& e : Desktop::Rule::windowEffects()->allEffectStrings()) { + auto VAL = m_config->getSpecialConfigValuePtr("windowrule", e.c_str(), name.c_str()); + if (VAL && VAL->m_bSetByUser) + rule->addEffect(Desktop::Rule::windowEffects()->get(e).value_or(Desktop::Rule::WINDOW_RULE_EFFECT_NONE), std::any_cast(VAL->getValue())); + } + + Desktop::Rule::ruleEngine()->registerRule(std::move(rule)); + return std::nullopt; +} + +std::optional CConfigManager::addLayerRuleFromConfigKey(const std::string& name) { + + const auto ENABLED = m_config->getSpecialConfigValuePtr("layerrule", "enable", name.c_str()); + if (ENABLED && ENABLED->m_bSetByUser && std::any_cast(ENABLED->getValue()) != 0) + return std::nullopt; + + SP rule = makeShared(name); + + for (const auto& r : Desktop::Rule::allMatchPropStrings()) { + auto VAL = m_config->getSpecialConfigValuePtr("layerrule", ("match:" + r).c_str(), name.c_str()); + if (VAL && VAL->m_bSetByUser) + rule->registerMatch(Desktop::Rule::matchPropFromString(r).value_or(Desktop::Rule::RULE_PROP_NONE), std::any_cast(VAL->getValue())); + } + + for (const auto& e : Desktop::Rule::layerEffects()->allEffectStrings()) { + auto VAL = m_config->getSpecialConfigValuePtr("layerrule", e.c_str(), name.c_str()); + if (VAL && VAL->m_bSetByUser) + rule->addEffect(Desktop::Rule::layerEffects()->get(e).value_or(Desktop::Rule::LAYER_RULE_EFFECT_NONE), std::any_cast(VAL->getValue())); + } + + Desktop::Rule::ruleEngine()->registerRule(std::move(rule)); + return std::nullopt; +} + +Hyprlang::CParseResult CConfigManager::reloadRules() { + Desktop::Rule::ruleEngine()->clearAllRules(); + + Hyprlang::CParseResult result; + for (const auto& name : m_config->listKeysForSpecialCategory("windowrule")) { + const auto error = addRuleFromConfigKey(name); + if (error.has_value()) + result.setError(error.value().c_str()); + } + for (const auto& name : m_config->listKeysForSpecialCategory("layerrule")) { + const auto error = addLayerRuleFromConfigKey(name); + if (error.has_value()) + result.setError(error.value().c_str()); + } + + for (auto& rule : m_keywordRules) { + Desktop::Rule::ruleEngine()->registerRule(SP{rule}); + } + + Desktop::Rule::ruleEngine()->updateAllRules(); + + return result; +} + +void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) { + Config::watcher()->update(); + + for (auto const& w : g_pCompositor->m_windows) { + w->uncacheWindowDecos(); + } + + 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(); + } + + // Update the keyboard layout to the cfg'd one if this is not the first launch + if (!m_isFirstLaunch) { + g_pInputManager->setKeyboardLayout(); + g_pInputManager->setPointerConfigs(); + g_pInputManager->setTouchDeviceConfigs(); + g_pInputManager->setTabletConfigs(); + + g_pHyprRenderer->m_reloadScreenShader = true; + } + + // parseError will be displayed next frame + + if (result.error) + m_configErrors = result.getError(); + else + m_configErrors = ""; + + if (result.error && !std::any_cast(m_config->getConfigValue("debug:suppress_errors"))) + ErrorOverlay::overlay()->queueCreate(result.getError(), ErrorOverlay::Colors::ERROR); + else if (std::any_cast(m_config->getConfigValue("autogenerated")) == 1) + ErrorOverlay::overlay()->queueCreate( + "Warning: You're using an autogenerated config! Edit the config file to get rid of this message. (config file: " + getMainConfigPath() + + " )\nSUPER+Q -> kitty (if it doesn't launch, make sure it's installed or choose a different terminal in the config)\nSUPER+M -> exit Hyprland", + ErrorOverlay::Colors::ERROR); + else + ErrorOverlay::overlay()->destroy(); + + // Set the modes for all monitors as we configured them + // not on first launch because monitors might not exist yet + // and they'll be taken care of in the newMonitor event + if (!m_isFirstLaunch) { + // check + Config::monitorRuleMgr()->scheduleReload(); + Config::monitorRuleMgr()->ensureMonitorStatus(); + Config::monitorRuleMgr()->ensureVRR(); + } + +#ifndef NO_XWAYLAND + const auto PENABLEXWAYLAND = std::any_cast(m_config->getConfigValue("xwayland:enabled")); + g_pCompositor->m_wantsXwayland = PENABLEXWAYLAND; + // enable/disable xwayland usage + if (!m_isFirstLaunch && + g_pXWayland /* XWayland has to be initialized by CCompositor::initManagers for this to make sense, and it doesn't have to be (e.g. very early plugin load) */) { + bool prevEnabledXwayland = g_pXWayland->enabled(); + if (g_pCompositor->m_wantsXwayland != prevEnabledXwayland) + g_pXWayland = makeUnique(g_pCompositor->m_wantsXwayland); + } else + g_pCompositor->m_wantsXwayland = PENABLEXWAYLAND; +#endif + + if (!m_isFirstLaunch && !g_pCompositor->m_unsafeState) + refreshGroupBarGradients(); + + // Updates dynamic window and workspace rules + for (auto const& w : g_pCompositor->getWorkspaces()) { + if (w->inert()) + continue; + w->updateWindows(); + w->updateWindowData(); + } + + // Update window border colors + g_pCompositor->updateAllWindowsAnimatedDecorationValues(); + + // manual crash + if (std::any_cast(m_config->getConfigValue("debug:manual_crash")) && !m_manualCrashInitiated) { + m_manualCrashInitiated = true; + Notification::overlay()->addNotification("Manual crash has been set up. Set debug:manual_crash back to 0 in order to crash the compositor.", CHyprColor(0), 5000, + ICON_INFO); + } else if (m_manualCrashInitiated && !std::any_cast(m_config->getConfigValue("debug:manual_crash"))) { + // cowabunga it is + g_pHyprRenderer->initiateManualCrash(); + } + + auto disableStdout = !std::any_cast(m_config->getConfigValue("debug:enable_stdout_logs")); + if (disableStdout && m_isFirstLaunch) + Log::logger->log(Log::DEBUG, "Disabling stdout logs! Check the log for further logs."); + + for (auto const& m : g_pCompositor->m_monitors) { + // mark blur dirty + m->m_blurFBDirty = true; + + g_pCompositor->scheduleFrameForMonitor(m); + + // Force the compositor to fully re-render all monitors + m->m_forceFullFrames = 2; + + // also force mirrors, as the aspect ratio could've changed + for (auto const& mirror : m->m_mirrors) + mirror->m_forceFullFrames = 3; + } + + // update plugins + handlePluginLoads(); + + // update persistent workspaces + if (!m_isFirstLaunch) + g_pCompositor->ensurePersistentWorkspacesPresent(); + + // update layouts + Layout::Supplementary::algoMatcher()->updateWorkspaceLayouts(); + + Event::bus()->m_events.config.reloaded.emit(); + if (g_pEventManager) + g_pEventManager->postEvent(SHyprIPCEvent{"configreloaded", ""}); +} + +void CConfigManager::init() { + + Config::watcher()->setOnChange([this](const CConfigWatcher::SConfigWatchEvent& e) { + Log::logger->log(Log::DEBUG, "CConfigManager: file {} modified, reloading", e.file); + reload(); + }); + + reload(); + + m_isFirstLaunch = false; +} + +std::string CConfigManager::parseKeyword(const std::string& COMMAND, const std::string& VALUE) { + const auto RET = m_config->parseDynamic(COMMAND.c_str(), VALUE.c_str()); + + // invalidate layouts if they changed + if (COMMAND == "monitor" || COMMAND.contains("gaps_") || COMMAND.starts_with("dwindle:") || COMMAND.starts_with("master:")) { + for (auto const& m : g_pCompositor->m_monitors) { + g_layoutManager->recalculateMonitor(m); + } + } + + // Update window border colors + g_pCompositor->updateAllWindowsAnimatedDecorationValues(); + + // manual crash + if (std::any_cast(m_config->getConfigValue("debug:manual_crash")) && !m_manualCrashInitiated) { + m_manualCrashInitiated = true; + Notification::overlay()->addNotification("Manual crash has been set up. Set debug:manual_crash back to 0 in order to crash the compositor.", CHyprColor(0), 5000, + ICON_INFO); + } else if (m_manualCrashInitiated && !std::any_cast(m_config->getConfigValue("debug:manual_crash"))) { + // cowabunga it is + g_pHyprRenderer->initiateManualCrash(); + } + + return RET.error ? RET.getError() : ""; +} + +Hyprlang::CConfigValue* CConfigManager::getConfigValueSafeDevice(const std::string& dev, const std::string& val, const std::string& fallback) { + + const auto VAL = m_config->getSpecialConfigValuePtr("device", val.c_str(), dev.c_str()); + + if ((!VAL || !VAL->m_bSetByUser) && !fallback.empty()) + return m_config->getConfigValuePtr(fallback.c_str()); + + return VAL; +} + +bool CConfigManager::deviceConfigExplicitlySet(const std::string& dev, const std::string& val) { + const auto VAL = m_config->getSpecialConfigValuePtr("device", val.c_str(), dev.c_str()); + + return VAL && VAL->m_bSetByUser; +} + +int CConfigManager::getDeviceInt(const std::string& dev, const std::string& v, const std::string& fallback) { + return std::any_cast(getConfigValueSafeDevice(dev, v, fallback)->getValue()); +} + +float CConfigManager::getDeviceFloat(const std::string& dev, const std::string& v, const std::string& fallback) { + return std::any_cast(getConfigValueSafeDevice(dev, v, fallback)->getValue()); +} + +Vector2D CConfigManager::getDeviceVec(const std::string& dev, const std::string& v, const std::string& fallback) { + auto vec = std::any_cast(getConfigValueSafeDevice(dev, v, fallback)->getValue()); + return {vec.x, vec.y}; +} + +std::string CConfigManager::getDeviceString(const std::string& dev, const std::string& v, const std::string& fallback) { + auto VAL = std::string{std::any_cast(getConfigValueSafeDevice(dev, v, fallback)->getValue())}; + + if (VAL == STRVAL_EMPTY) + return ""; + + return VAL; +} + +SConfigOptionReply CConfigManager::getConfigValue(const std::string& val) { + const auto VAL = m_config->getConfigValuePtr(val.c_str()); + if (!VAL) + return {}; + + return {.dataptr = VAL->getDataStaticPtr(), .type = &VAL->getValue().type()}; +} + +Hyprlang::CConfigValue* CConfigManager::getHyprlangConfigValuePtr(const std::string& name, const std::string& specialCat) { + if (!specialCat.empty()) + return m_config->getSpecialConfigValuePtr(specialCat.c_str(), name.c_str(), nullptr); + + if (name.starts_with("plugin:")) + return m_config->getSpecialConfigValuePtr("plugin", name.substr(7).c_str(), nullptr); + + return m_config->getConfigValuePtr(name.c_str()); +} + +bool CConfigManager::deviceConfigExists(const std::string& dev) { + auto copy = dev; + std::ranges::replace(copy, ' ', '-'); + + return m_config->specialCategoryExistsForKey("device", copy.c_str()); +} + +void CConfigManager::handlePluginLoads() { + if (!g_pPluginSystem) + return; + + bool pluginsChanged = false; + g_pPluginSystem->updateConfigPlugins(m_declaredPlugins, pluginsChanged); + + if (pluginsChanged) { + ErrorOverlay::overlay()->destroy(); + reload(); + } +} + +void CConfigManager::addPluginConfigVar(HANDLE handle, const std::string& name, const Hyprlang::CConfigValue& value) { + if (!name.starts_with("plugin:")) + return; + + std::string field = name.substr(7); + + m_config->addSpecialConfigValue("plugin", field.c_str(), value); + m_pluginVariables.push_back({handle, field}); +} + +void CConfigManager::addPluginKeyword(HANDLE handle, const std::string& name, Hyprlang::PCONFIGHANDLERFUNC fn, Hyprlang::SHandlerOptions opts) { + m_pluginKeywords.emplace_back(SPluginKeyword{handle, name, fn}); + m_config->registerHandler(fn, name.c_str(), opts); +} + +void CConfigManager::removePluginConfig(HANDLE handle) { + for (auto const& k : m_pluginKeywords) { + if (k.handle != handle) + continue; + + m_config->unregisterHandler(k.name.c_str()); + } + + std::erase_if(m_pluginKeywords, [&](const auto& other) { return other.handle == handle; }); + for (auto const& [h, n] : m_pluginVariables) { + if (h != handle) + continue; + + m_config->removeSpecialConfigValue("plugin", n.c_str()); + } + std::erase_if(m_pluginVariables, [handle](const auto& other) { return other.handle == handle; }); +} + +std::optional CConfigManager::handleRawExec(const std::string& command, const std::string& args) { + if (m_isFirstLaunch) { + Config::Supplementary::executor()->addExecOnce({args, false}); + return {}; + } + + g_pKeybindManager->spawnRaw(args); + return {}; +} + +std::optional CConfigManager::handleExec(const std::string& command, const std::string& args) { + if (m_isFirstLaunch) { + Config::Supplementary::executor()->addExecOnce({args, true}); + return {}; + } + + g_pKeybindManager->spawn(args); + return {}; +} + +std::optional CConfigManager::handleExecOnce(const std::string& command, const std::string& args) { + if (m_isFirstLaunch) + Config::Supplementary::executor()->addExecOnce({args, true}); + + return {}; +} + +std::optional CConfigManager::handleExecRawOnce(const std::string& command, const std::string& args) { + if (m_isFirstLaunch) + Config::Supplementary::executor()->addExecOnce({args, false}); + + return {}; +} + +std::optional CConfigManager::handleExecShutdown(const std::string& command, const std::string& args) { + if (g_pCompositor->m_finalRequests) { + g_pKeybindManager->spawn(args); + return {}; + } + + Config::Supplementary::executor()->addExecShutdown({args, true}); + return {}; +} + +std::optional CConfigManager::handleMonitor(const std::string& command, const std::string& args) { + // get the monitor config + const auto ARGS = CVarList2(std::string(args)); + + auto parser = Config::CMonitorRuleParser(std::string(ARGS[0])); + + if (ARGS[1] == "disable" || ARGS[1] == "disabled" || ARGS[1] == "addreserved" || ARGS[1] == "transform") { + if (ARGS[1] == "disable" || ARGS[1] == "disabled") + parser.setDisabled(); + else if (ARGS[1] == "transform") { + if (!parser.parseTransform(std::string(ARGS[2]))) + return parser.getError(); + + const auto TRANSFORM = parser.rule().m_transform; + + // overwrite if exists + for (const auto& r : Config::monitorRuleMgr()->all()) { + if (r.m_name == parser.name()) { + auto cpy = r; + cpy.m_transform = TRANSFORM; + Config::monitorRuleMgr()->add(std::move(cpy)); + return {}; + } + } + + return {}; + } else if (ARGS[1] == "addreserved") { + std::optional area; + try { + // top, right, bottom, left + area = {std::stoi(std::string{ARGS[2]}), std::stoi(std::string{ARGS[5]}), std::stoi(std::string{ARGS[3]}), std::stoi(std::string{ARGS[4]})}; + } catch (...) { return "parse error: invalid reserved area"; } + + if (!area.has_value()) + return "parse error: bad addreserved"; + + auto rule = std::ranges::find_if(Config::monitorRuleMgr()->allMut(), [n = ARGS[0]](const auto& other) { return other.m_name == n; }); + if (rule != Config::monitorRuleMgr()->allMut().end()) { + rule->m_reservedArea = area.value(); + return {}; + } + + // fall + } else { + Log::logger->log(Log::ERR, "ConfigManager parseMonitor, curitem bogus???"); + return "parse error: curitem bogus"; + } + + Config::monitorRuleMgr()->add(std::move(parser.rule())); + return {}; + } + + parser.parseMode(std::string(ARGS[1])); + parser.parsePosition(std::string(ARGS[2])); + parser.parseScale(std::string(ARGS[3])); + + int argno = 4; + + while (!ARGS[argno].empty()) { + if (ARGS[argno] == "mirror") { + parser.setMirror(std::string(ARGS[argno + 1])); + argno++; + } else if (ARGS[argno] == "bitdepth") { + parser.parseBitdepth(std::string(ARGS[argno + 1])); + argno++; + } else if (ARGS[argno] == "cm") { + parser.parseCM(std::string(ARGS[argno + 1])); + argno++; + } else if (ARGS[argno] == "sdrsaturation") { + parser.parseSDRSaturation(std::string(ARGS[argno + 1])); + argno++; + } else if (ARGS[argno] == "sdrbrightness") { + parser.parseSDRBrightness(std::string(ARGS[argno + 1])); + argno++; + } else if (ARGS[argno] == "transform") { + parser.parseTransform(std::string(ARGS[argno + 1])); + argno++; + } 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 { + Log::logger->log(Log::ERR, "Config error: invalid monitor syntax at \"{}\"", ARGS[argno]); + return "invalid syntax at \"" + std::string(ARGS[argno]) + "\""; + } + + argno++; + } + + Config::monitorRuleMgr()->add(std::move(parser.rule())); + + return parser.getError(); +} + +std::optional CConfigManager::handleBezier(const std::string& command, const std::string& args) { + const auto ARGS = CVarList(args); + + std::string bezierName = ARGS[0]; + + if (ARGS[1].empty()) + return "too few arguments"; + else if (!isNumber(ARGS[1], true)) + return "invalid point"; + float p1x = std::stof(ARGS[1]); + + if (ARGS[2].empty()) + return "too few arguments"; + else if (!isNumber(ARGS[2], true)) + return "invalid point"; + float p1y = std::stof(ARGS[2]); + + if (ARGS[3].empty()) + return "too few arguments"; + else if (!isNumber(ARGS[3], true)) + return "invalid point"; + float p2x = std::stof(ARGS[3]); + + if (ARGS[4].empty()) + return "too few arguments"; + else if (!isNumber(ARGS[4], true)) + return "invalid point"; + float p2y = std::stof(ARGS[4]); + + if (!ARGS[5].empty()) + return "too many arguments"; + + g_pAnimationManager->addBezierWithName(bezierName, Vector2D(p1x, p1y), Vector2D(p2x, p2y)); + + return {}; +} + +std::optional CConfigManager::handleAnimation(const std::string& command, const std::string& args) { + const auto ARGS = CVarList(args); + + // Master on/off + + // anim name + const auto ANIMNAME = ARGS[0]; + + if (!Config::animationTree()->nodeExists(ANIMNAME)) + return "no such animation"; + + // This helper casts strings like "1", "true", "off", "yes"... to int. + int64_t enabledInt = configStringToInt(ARGS[1]).value_or(0) == 1; + + // Checking that the int is 1 or 0 because the helper can return integers out of range. + if (enabledInt != 0 && enabledInt != 1) + return "invalid animation on/off state"; + + if (!enabledInt) { + Config::animationTree()->setConfigForNode(ANIMNAME, enabledInt, 1, "default"); + return {}; + } + + float speed = -1; + + // speed + if (isNumber(ARGS[2], true)) { + speed = std::stof(ARGS[2]); + + if (speed <= 0) { + speed = 1.f; + return "invalid speed"; + } + } else { + speed = 10.f; + return "invalid speed"; + } + + std::string bezierName = ARGS[3]; + + if (!g_pAnimationManager->bezierExists(bezierName)) + return "no such bezier"; + + Config::animationTree()->setConfigForNode(ANIMNAME, enabledInt, speed, ARGS[3], ARGS[4]); + + if (!ARGS[4].empty()) { + auto ERR = g_pAnimationManager->styleValidInConfigVar(ANIMNAME, ARGS[4]); + + if (!ERR.empty()) + return ERR; + } + + return {}; +} + +SParsedKey parseKey(const std::string& key) { + if (isNumber(key) && std::stoi(key) > 9) + return {.keycode = std::stoi(key)}; + else if (key.starts_with("code:") && isNumber(key.substr(5))) + return {.keycode = std::stoi(key.substr(5))}; + else if (key == "catchall") + return {.catchAll = true}; + else + return {.key = key}; +} + +std::optional CConfigManager::handleBind(const std::string& command, const std::string& value) { + // example: + // bind[fl]=SUPER,G,exec,dmenu_run + + // flags + bool locked = false; + bool release = false; + bool repeat = false; + bool mouse = false; + bool nonConsuming = false; + bool transparent = false; + bool ignoreMods = false; + bool multiKey = false; + bool longPress = false; + bool hasDescription = false; + bool dontInhibit = false; + bool click = false; + bool drag = false; + bool submapUniversal = false; + bool isPerDevice = false; + const auto BINDARGS = command.substr(4); + + for (auto const& arg : BINDARGS) { + switch (arg) { + case 'l': locked = true; break; + case 'r': release = true; break; + case 'e': repeat = true; break; + case 'm': mouse = true; break; + case 'n': nonConsuming = true; break; + case 't': transparent = true; break; + case 'i': ignoreMods = true; break; + case 's': multiKey = true; break; + case 'o': longPress = true; break; + case 'd': hasDescription = true; break; + case 'p': dontInhibit = true; break; + case 'c': + click = true; + release = true; + break; + case 'g': + drag = true; + release = true; + break; + case 'u': submapUniversal = true; break; + case 'k': isPerDevice = true; break; + default: return "bind: invalid flag"; + } + } + + if ((longPress || release) && repeat) + return "flags e is mutually exclusive with r and o"; + + if (mouse && (repeat || release || locked)) + return "flag m is exclusive"; + + if (click && drag) + return "flags c and g are mutually exclusive"; + + const int numbArgs = (hasDescription ? 5 : 4) + sc(isPerDevice); + const auto ARGS = CVarList(value, numbArgs); + + const int DESCR_OFFSET = hasDescription ? 1 : 0; + const int DEVICE_OFFSET = sc(isPerDevice); + if ((ARGS.size() < 3 && !mouse) || (ARGS.size() < 3 && mouse)) + return "bind: too few args"; + else if ((ARGS.size() > sc(4) + DESCR_OFFSET + DEVICE_OFFSET && !mouse) || (ARGS.size() > sc(3) + DESCR_OFFSET + DEVICE_OFFSET && mouse)) + return "bind: too many args"; + + std::set KEYSYMS; + std::set MODS; + + if (multiKey) { + for (const auto& splitKey : CVarList(ARGS[1], 8, '&')) { + KEYSYMS.insert(xkb_keysym_from_name(splitKey.c_str(), XKB_KEYSYM_CASE_INSENSITIVE)); + } + for (const auto& splitMod : CVarList(ARGS[0], 8, '&')) { + MODS.insert(xkb_keysym_from_name(splitMod.c_str(), XKB_KEYSYM_CASE_INSENSITIVE)); + } + } + const auto MOD = g_pKeybindManager->stringToModMask(ARGS[0]); + const auto MODSTR = ARGS[0]; + + const auto KEY = multiKey ? "" : ARGS[1]; + + const auto DEVICEARGS = isPerDevice ? ARGS[2] : ""; + + const auto DESCRIPTION = hasDescription ? ARGS[2 + DEVICE_OFFSET] : ""; + + auto HANDLER = ARGS[2 + DESCR_OFFSET + DEVICE_OFFSET]; + + const auto COMMAND = mouse ? HANDLER : ARGS[3 + DESCR_OFFSET + DEVICE_OFFSET]; + + if (mouse) + HANDLER = "mouse"; + + // to lower + std::ranges::transform(HANDLER, HANDLER.begin(), ::tolower); + + const auto DISPATCHER = g_pKeybindManager->m_dispatchers.find(HANDLER); + + if (DISPATCHER == g_pKeybindManager->m_dispatchers.end()) { + Log::logger->log(Log::ERR, "Invalid dispatcher: {}", HANDLER); + return "Invalid dispatcher, requested \"" + HANDLER + "\" does not exist"; + } + + if (MOD == 0 && !MODSTR.empty()) { + Log::logger->log(Log::ERR, "Invalid mod: {}", MODSTR); + return "Invalid mod, requested mod \"" + MODSTR + "\" is not a valid mod."; + } + + //[!]keyboard1 keyboard2 ... + bool deviceInclusive = false; + std::unordered_set devices = {}; + if (!DEVICEARGS.empty()) { + deviceInclusive = DEVICEARGS[0] != '!'; + for (const auto deviceString : std::ranges::views::split(DEVICEARGS.substr(deviceInclusive ? 0 : 1), ' ')) { + devices.emplace(std::string_view(deviceString)); + } + } + + if ((!KEY.empty()) || multiKey) { + SParsedKey parsedKey = parseKey(KEY); + + if (parsedKey.catchAll && m_currentSubmap.name.empty()) { + Log::logger->log(Log::ERR, "Catchall not allowed outside of submap!"); + return "Invalid catchall, catchall keybinds are only allowed in submaps."; + } + + g_pKeybindManager->addKeybind(SKeybind{parsedKey.key, KEYSYMS, parsedKey.keycode, parsedKey.catchAll, MOD, MODS, HANDLER, + COMMAND, locked, m_currentSubmap, DESCRIPTION, release, repeat, longPress, + mouse, nonConsuming, transparent, ignoreMods, multiKey, hasDescription, dontInhibit, + click, drag, submapUniversal, deviceInclusive, devices}); + } + + return {}; +} + +std::optional CConfigManager::handleUnbind(const std::string& command, const std::string& value) { + const auto ARGS = CVarList(value); + + if (ARGS.size() == 1 && ARGS[0] == "all") { + g_pKeybindManager->m_keybinds.clear(); + g_pKeybindManager->m_activeKeybinds.clear(); + g_pKeybindManager->m_lastLongPressKeybind.reset(); + return {}; + } + + const auto MOD = g_pKeybindManager->stringToModMask(ARGS[0]); + + const auto KEY = parseKey(ARGS[1]); + + g_pKeybindManager->removeKeybind(MOD, KEY); + + return {}; +} + +std::optional CConfigManager::handleWorkspaceRules(const std::string& command, const std::string& value) { + // This can either be the monitor or the workspace identifier + const auto FIRST_DELIM = value.find_first_of(','); + + auto first_ident = trim(value.substr(0, FIRST_DELIM)); + + const auto& [id, name, isAutoID] = getWorkspaceIDNameFromString(first_ident); + + auto rules = value.substr(FIRST_DELIM + 1); + Config::CWorkspaceRule wsRule; + wsRule.m_workspaceString = first_ident; + // if (id == WORKSPACE_INVALID) { + // // it could be the monitor. If so, second value MUST be + // // the workspace. + // const auto WORKSPACE_DELIM = value.find_first_of(',', FIRST_DELIM + 1); + // auto wsIdent = removeBeginEndSpacesTabs(value.substr(FIRST_DELIM + 1, (WORKSPACE_DELIM - FIRST_DELIM - 1))); + // id = getWorkspaceIDFromString(wsIdent, name); + // if (id == WORKSPACE_INVALID) { + // Log::logger->log(Log::ERR, "Invalid workspace identifier found: {}", wsIdent); + // return "Invalid workspace identifier found: " + wsIdent; + // } + // wsRule.monitor = first_ident; + // wsRule.workspaceString = wsIdent; + // wsRule.isDefault = true; // backwards compat + // rules = value.substr(WORKSPACE_DELIM + 1); + // } + + const static std::string ruleOnCreatedEmpty = "on-created-empty:"; + const static auto ruleOnCreatedEmptyLen = ruleOnCreatedEmpty.length(); + +#define CHECK_OR_THROW(expr) \ + \ + auto X = expr; \ + if (!X) { \ + return "Failed parsing a workspace rule"; \ + } + + auto assignRule = [&](std::string rule) -> std::optional { + size_t delim = std::string::npos; + if ((delim = rule.find("gapsin:")) != std::string::npos) { + CVarList2 varlist(rule.substr(delim + 7), 0, ' '); + wsRule.m_gapsIn = CCssGapData(); + try { + wsRule.m_gapsIn->parseGapData(varlist); + } catch (...) { return "Error parsing workspace rule gaps: {}", rule.substr(delim + 7); } + } else if ((delim = rule.find("gapsout:")) != std::string::npos) { + CVarList2 varlist(rule.substr(delim + 8), 0, ' '); + wsRule.m_gapsOut = CCssGapData(); + try { + wsRule.m_gapsOut->parseGapData(varlist); + } catch (...) { return "Error parsing workspace rule gaps: {}", rule.substr(delim + 8); } + } else if ((delim = rule.find("bordersize:")) != std::string::npos) + try { + wsRule.m_borderSize = std::stoi(rule.substr(delim + 11)); + } catch (...) { return "Error parsing workspace rule bordersize: {}", rule.substr(delim + 11); } + else if ((delim = rule.find("border:")) != std::string::npos) { + CHECK_OR_THROW(configStringToInt(rule.substr(delim + 7))) + wsRule.m_noBorder = !*X; + } else if ((delim = rule.find("shadow:")) != std::string::npos) { + CHECK_OR_THROW(configStringToInt(rule.substr(delim + 7))) + wsRule.m_noShadow = !*X; + } else if ((delim = rule.find("rounding:")) != std::string::npos) { + CHECK_OR_THROW(configStringToInt(rule.substr(delim + 9))) + wsRule.m_noRounding = !*X; + } else if ((delim = rule.find("decorate:")) != std::string::npos) { + CHECK_OR_THROW(configStringToInt(rule.substr(delim + 9))) + wsRule.m_decorate = *X; + } else if ((delim = rule.find("monitor:")) != std::string::npos) + wsRule.m_monitor = rule.substr(delim + 8); + else if ((delim = rule.find("default:")) != std::string::npos) { + CHECK_OR_THROW(configStringToInt(rule.substr(delim + 8))) + wsRule.m_isDefault = *X; + } else if ((delim = rule.find("persistent:")) != std::string::npos) { + CHECK_OR_THROW(configStringToInt(rule.substr(delim + 11))) + wsRule.m_isPersistent = *X; + } else if ((delim = rule.find("defaultName:")) != std::string::npos) + wsRule.m_defaultName = trim(rule.substr(delim + 12)); + else if ((delim = rule.find(ruleOnCreatedEmpty)) != std::string::npos) { + CHECK_OR_THROW(cleanCmdForWorkspace(name, rule.substr(delim + ruleOnCreatedEmptyLen))) + wsRule.m_onCreatedEmptyRunCmd = *X; + } else if ((delim = rule.find("layoutopt:")) != std::string::npos) { + std::string opt = rule.substr(delim + 10); + if (!opt.contains(":")) { + // invalid + Log::logger->log(Log::ERR, "Invalid workspace rule found: {}", rule); + return "Invalid workspace rule found: " + rule; + } + + std::string val = opt.substr(opt.find(':') + 1); + opt = opt.substr(0, opt.find(':')); + + wsRule.m_layoutopts[opt] = val; + } else if ((delim = rule.find("layout:")) != std::string::npos) { + std::string layout = rule.substr(delim + 7); + wsRule.m_layout = std::move(layout); + } else if ((delim = rule.find("animation:")) != std::string::npos) { + std::string animationStyle = rule.substr(delim + 10); + wsRule.m_animationStyle = std::move(animationStyle); + } + + return {}; + }; + +#undef CHECK_OR_THROW + + CVarList2 rulesList(std::string(rules), 0, ',', true); + for (auto const& r : rulesList) { + const auto R = assignRule(std::string(r)); + if (R.has_value()) + return R; + } + + wsRule.m_workspaceName = name; + wsRule.m_workspaceId = isAutoID ? WORKSPACE_INVALID : id; + + Config::workspaceRuleMgr()->replaceOrAdd(std::move(wsRule)); + return {}; +} + +std::optional CConfigManager::handleSubmap(const std::string&, const std::string& submap) { + CVarList2 data((std::string(submap))); + m_currentSubmap.name = (data[0] == "reset") ? "" : data[0]; + m_currentSubmap.reset = data[1]; + return {}; +} + +std::optional CConfigManager::handleSource(const std::string& command, const std::string& rawpath) { + if (rawpath.length() < 2) { + Log::logger->log(Log::ERR, "source= path garbage"); + return "source= path " + rawpath + " bogus!"; + } + + std::unique_ptr glob_buf{sc(calloc(1, sizeof(glob_t))), // allocate and zero-initialize NOLINT(cppcoreguidelines-no-malloc) + [](glob_t* g) { + if (g) { + globfree(g); // free internal resources allocated by glob() + free(g); // free the memory for the glob_t structure NOLINT(cppcoreguidelines-no-malloc) + } + }}; + + if (auto r = glob(absolutePath(rawpath, m_configCurrentPath).c_str(), GLOB_TILDE, nullptr, glob_buf.get()); r != 0) { + std::string err = std::format("source= globbing error: {}", r == GLOB_NOMATCH ? "found no match" : GLOB_ABORTED ? "read error" : "out of memory"); + Log::logger->log(Log::ERR, "{}", err); + return err; + } + + std::string errorsFromParsing; + + for (size_t i = 0; i < glob_buf->gl_pathc; i++) { + auto value = absolutePath(glob_buf->gl_pathv[i], m_configCurrentPath); + + std::error_code ec; + auto file_status = std::filesystem::status(value, ec); + + if (ec) { + Log::logger->log(Log::ERR, "source= file from glob result is inaccessible ({}): {}", ec.message(), value); + return "source= file " + value + " is inaccessible!"; + } + + if (std::filesystem::is_regular_file(file_status)) { + m_configPaths.emplace_back(value); + auto configCurrentPathBackup = m_configCurrentPath; + m_configCurrentPath = value; + const auto THISRESULT = m_config->parseFile(value.c_str()); + m_configCurrentPath = configCurrentPathBackup; + if (THISRESULT.error && errorsFromParsing.empty()) + errorsFromParsing += THISRESULT.getError(); + } else if (std::filesystem::is_directory(file_status)) { + Log::logger->log(Log::WARN, "source= skipping directory {}", value); + continue; + } else { + Log::logger->log(Log::WARN, "source= skipping non-regular-file {}", value); + continue; + } + } + + if (errorsFromParsing.empty()) + return {}; + return errorsFromParsing; +} + +std::optional CConfigManager::handleEnv(const std::string& command, const std::string& value) { + const auto ARGS = CVarList(value, 2); + + if (ARGS[0].empty()) + return "env empty"; + + if (!m_isFirstLaunch) { + // check if env changed at all. If it didn't, ignore. If it did, update it. + const auto* ENV = getenv(ARGS[0].c_str()); + if (ENV && ENV == ARGS[1]) + return {}; // env hasn't changed + } + + setenv(ARGS[0].c_str(), ARGS[1].c_str(), 1); + + if (command.back() == 'd') { + // dbus + const auto CMD = +#ifdef USES_SYSTEMD + "systemctl --user import-environment " + ARGS[0] + + " && hash dbus-update-activation-environment 2>/dev/null && " +#endif + "dbus-update-activation-environment --systemd " + + ARGS[0]; + handleRawExec("", CMD); + } + + return {}; +} + +std::optional CConfigManager::handlePlugin(const std::string& command, const std::string& path) { + if (std::ranges::find(m_declaredPlugins, path) != m_declaredPlugins.end()) + return "plugin '" + path + "' declared twice"; + + m_declaredPlugins.push_back(path); + + return {}; +} + +std::optional CConfigManager::handlePermission(const std::string& command, const std::string& value) { + CVarList2 data((std::string(value))); + + eDynamicPermissionType type = PERMISSION_TYPE_UNKNOWN; + eDynamicPermissionAllowMode mode = PERMISSION_RULE_ALLOW_MODE_UNKNOWN; + + 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") + type = PERMISSION_TYPE_KEYBOARD; + + if (data[2] == "ask") + mode = PERMISSION_RULE_ALLOW_MODE_ASK; + else if (data[2] == "allow") + mode = PERMISSION_RULE_ALLOW_MODE_ALLOW; + else if (data[2] == "deny") + mode = PERMISSION_RULE_ALLOW_MODE_DENY; + + if (type == PERMISSION_TYPE_UNKNOWN) + return "unknown permission type"; + if (mode == PERMISSION_RULE_ALLOW_MODE_UNKNOWN) + return "unknown permission allow mode"; + + if (m_isFirstLaunch && g_pDynamicPermissionManager) + g_pDynamicPermissionManager->addConfigPermissionRule(std::string(data[0]), type, mode); + + return {}; +} + +std::optional CConfigManager::handleGesture(const std::string& command, const std::string& value) { + CVarList2 data((std::string(value))); + + size_t fingerCount = 0; + eTrackpadGestureDirection direction = TRACKPAD_GESTURE_DIR_NONE; + + try { + fingerCount = std::stoul(std::string{data[0]}); + } catch (...) { return std::format("Invalid value {} for finger count", data[0]); } + + if (fingerCount <= 1 || fingerCount >= 10) + return std::format("Invalid value {} for finger count", data[0]); + + direction = g_pTrackpadGestures->dirForString(data[1]); + + 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"; + } + } + + while (true) { + + if (data[startDataIdx].starts_with("mod:")) { + modMask = g_pKeybindManager->stringToModMask(std::string{data[startDataIdx].substr(4)}); + startDataIdx++; + continue; + } else if (data[startDataIdx].starts_with("scale:")) { + try { + deltaScale = std::clamp(std::stof(std::string{data[startDataIdx].substr(6)}), 0.1F, 10.F); + startDataIdx++; + continue; + } catch (...) { return std::format("Invalid delta scale: {}", std::string{data[startDataIdx].substr(6)}); } + } + + break; + } + + std::expected result; + + if (data[startDataIdx] == "dispatcher") + result = g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}, data.join(",", startDataIdx + 2)), fingerCount, + direction, modMask, deltaScale, disableInhibit); + else if (data[startDataIdx] == "workspace") + result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale, disableInhibit); + else if (data[startDataIdx] == "resize") + result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale, disableInhibit); + else if (data[startDataIdx] == "move") + result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale, disableInhibit); + else if (data[startDataIdx] == "special") + result = + g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale, disableInhibit); + else if (data[startDataIdx] == "close") + result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale, disableInhibit); + else if (data[startDataIdx] == "float") + result = + g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale, disableInhibit); + 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); + else + return std::format("Invalid gesture: {}", data[startDataIdx]); + + if (!result) + return result.error(); + + return std::nullopt; +} + +std::optional CConfigManager::handleWindowrule(const std::string& command, const std::string& value) { + CVarList2 data((std::string(value))); + + SP rule = makeShared(); + + const auto& PROPS = Desktop::Rule::allMatchPropStrings(); + const auto& EFFECTS = Desktop::Rule::windowEffects()->allEffectStrings(); + + for (const auto& el : data) { + // split on space, no need for a CVarList here + size_t spacePos = el.find(' '); + if (spacePos == std::string::npos) + return std::format("invalid field {}: missing a value", el); + + const bool FIRST_IS_PROP = el.starts_with("match:"); + const auto FIRST = FIRST_IS_PROP ? el.substr(6, spacePos - 6) : el.substr(0, spacePos); + if (FIRST_IS_PROP && std::ranges::contains(PROPS, FIRST)) { + // it's a prop + const auto PROP = Desktop::Rule::matchPropFromString(FIRST); + if (!PROP.has_value()) + return std::format("invalid prop {}", el); + rule->registerMatch(*PROP, std::string{el.substr(spacePos + 1)}); + } else if (!FIRST_IS_PROP && std::ranges::contains(EFFECTS, FIRST)) { + // it's an effect + const auto EFFECT = Desktop::Rule::windowEffects()->get(FIRST); + if (!EFFECT.has_value()) + return std::format("invalid effect {}", el); + rule->addEffect(*EFFECT, std::string{el.substr(spacePos + 1)}); + } else + return std::format("invalid field type {}", FIRST); + } + + m_keywordRules.emplace_back(std::move(rule)); + if (g_pHyprCtl && g_pHyprCtl->m_currentRequestParams.isDynamicKeyword) + Desktop::Rule::ruleEngine()->registerRule(SP{m_keywordRules.back()}); + + return std::nullopt; +} + +std::optional CConfigManager::handleLayerrule(const std::string& command, const std::string& value) { + CVarList2 data((std::string(value))); + + SP rule = makeShared(); + + const auto& PROPS = Desktop::Rule::allMatchPropStrings(); + const auto& EFFECTS = Desktop::Rule::layerEffects()->allEffectStrings(); + + for (const auto& el : data) { + // split on space, no need for a CVarList here + size_t spacePos = el.find(' '); + if (spacePos == std::string::npos) + return std::format("invalid field {}: missing a value", el); + + const bool FIRST_IS_PROP = el.starts_with("match:"); + const auto FIRST = FIRST_IS_PROP ? el.substr(6, spacePos - 6) : el.substr(0, spacePos); + if (FIRST_IS_PROP && std::ranges::contains(PROPS, FIRST)) { + // it's a prop + const auto PROP = Desktop::Rule::matchPropFromString(FIRST); + if (!PROP.has_value()) + return std::format("invalid prop {}", el); + rule->registerMatch(*PROP, std::string{el.substr(spacePos + 1)}); + } else if (!FIRST_IS_PROP && std::ranges::contains(EFFECTS, FIRST)) { + // it's an effect + const auto EFFECT = Desktop::Rule::layerEffects()->get(FIRST); + if (!EFFECT.has_value()) + return std::format("invalid effect {}", el); + rule->addEffect(*EFFECT, std::string{el.substr(spacePos + 1)}); + } else + return std::format("invalid field type {}", FIRST); + } + + m_keywordRules.emplace_back(std::move(rule)); + + return std::nullopt; +} + +std::expected CConfigManager::generateDefaultConfig(const std::filesystem::path& path, bool safeMode) { + std::string parentPath = std::filesystem::path(path).parent_path(); + + if (!parentPath.empty()) { + std::error_code ec; + bool created = std::filesystem::create_directories(parentPath, ec); + if (ec) { + Log::logger->log(Log::ERR, "Couldn't create config home directory ({}): {}", ec.message(), parentPath); + return std::unexpected("Config could not be generated."); + } + if (created) + Log::logger->log(Log::WARN, "Creating config home directory"); + } + + Log::logger->log(Log::WARN, "No config file found; attempting to generate."); + std::ofstream ofs; + ofs.open(path, std::ios::trunc); + + if (!ofs.good()) + return std::unexpected("Config could not be generated."); + + if (!safeMode) { + ofs << AUTOGENERATED_PREFIX; + ofs << EXAMPLE_CONFIG; + } else { + std::string n = std::string{EXAMPLE_CONFIG}; + replaceInString(n, "\n$menu = hyprlauncher\n", "\n$menu = hyprland-run\n"); + ofs << n; + } + + ofs.close(); + + if (ofs.fail()) + return std::unexpected("Config could not be generated."); + + return {}; +} + +const std::vector& CConfigManager::getConfigPaths() { + return m_configPaths; +} + +bool CConfigManager::configVerifPassed() { + return m_lastConfigVerificationWasSuccessful; +} + +std::string CConfigManager::getMainConfigPath() { + return m_mainConfigPath; +} + +std::string CConfigManager::currentConfigPath() { + return m_configCurrentPath; +} diff --git a/src/config/legacy/ConfigManager.hpp b/src/config/legacy/ConfigManager.hpp new file mode 100644 index 000000000..30251a339 --- /dev/null +++ b/src/config/legacy/ConfigManager.hpp @@ -0,0 +1,153 @@ +#pragma once + +#include + +#include +#include +#include + +#include "../../desktop/DesktopTypes.hpp" +#include "../../desktop/rule/Rule.hpp" +#include "../../helpers/memory/Memory.hpp" +#include "../../managers/KeybindManager.hpp" + +#include "../ConfigManager.hpp" + +#include + +#define HANDLE void* + +namespace Config::Supplementary { + struct SConfigOptionDescription; +}; + +namespace Config::Legacy { + + class CConfigManager; + + struct SPluginKeyword { + HANDLE handle = nullptr; + std::string name = ""; + Hyprlang::PCONFIGHANDLERFUNC fn = nullptr; + }; + + struct SPluginVariable { + HANDLE handle = nullptr; + std::string name = ""; + }; + + class CConfigManager : public Config::IConfigManager { + public: + CConfigManager(); + + virtual eConfigManagerType type() override; + + virtual void init() override; + virtual void reload() override; + virtual std::string verify() override; + + virtual int getDeviceInt(const std::string&, const std::string&, const std::string& fallback = "") override; + virtual float getDeviceFloat(const std::string&, const std::string&, const std::string& fallback = "") override; + virtual Vector2D getDeviceVec(const std::string&, const std::string&, const std::string& fallback = "") override; + virtual std::string getDeviceString(const std::string&, const std::string&, const std::string& fallback = "") override; + virtual bool deviceConfigExplicitlySet(const std::string&, const std::string&) override; + virtual bool deviceConfigExists(const std::string&) override; + + virtual SConfigOptionReply getConfigValue(const std::string&) override; + + virtual std::string getMainConfigPath() override; + virtual std::string getErrors() override; + virtual std::string getConfigString() override; + virtual std::string currentConfigPath() override; + virtual const std::vector& getConfigPaths() override; + + virtual std::expected generateDefaultConfig(const std::filesystem::path&, bool safeMode) override; + + virtual void handlePluginLoads() override; + virtual bool configVerifPassed() override; + + void addPluginConfigVar(HANDLE handle, const std::string& name, const Hyprlang::CConfigValue& value); + void addPluginKeyword(HANDLE handle, const std::string& name, Hyprlang::PCONFIGHANDLERFUNC fun, Hyprlang::SHandlerOptions opts = {}); + void removePluginConfig(HANDLE handle); + + std::string parseKeyword(const std::string&, const std::string&); + + Hyprlang::CConfigValue* getHyprlangConfigValuePtr(const std::string& name, const std::string& specialCat = ""); + + // keywords + std::optional handleRawExec(const std::string&, const std::string&); + std::optional handleExec(const std::string&, const std::string&); + std::optional handleExecOnce(const std::string&, const std::string&); + std::optional handleExecRawOnce(const std::string&, const std::string&); + std::optional handleExecShutdown(const std::string&, const std::string&); + std::optional handleMonitor(const std::string&, const std::string&); + std::optional handleBind(const std::string&, const std::string&); + std::optional handleUnbind(const std::string&, const std::string&); + std::optional handleWorkspaceRules(const std::string&, const std::string&); + std::optional handleBezier(const std::string&, const std::string&); + std::optional handleAnimation(const std::string&, const std::string&); + std::optional handleSource(const std::string&, const std::string&); + std::optional handleSubmap(const std::string&, const std::string&); + std::optional handleBindWS(const std::string&, const std::string&); + std::optional handleEnv(const std::string&, const std::string&); + std::optional handlePlugin(const std::string&, const std::string&); + std::optional handlePermission(const std::string&, const std::string&); + std::optional handleGesture(const std::string&, const std::string&); + std::optional handleWindowrule(const std::string&, const std::string&); + std::optional handleLayerrule(const std::string&, const std::string&); + + std::optional handleMonitorv2(const std::string& output); + Hyprlang::CParseResult handleMonitorv2(); + std::optional addRuleFromConfigKey(const std::string& name); + std::optional addLayerRuleFromConfigKey(const std::string& name); + Hyprlang::CParseResult reloadRules(); + + std::string m_configCurrentPath; + + bool m_isLaunchingExecOnce = false; // For exec-once to skip initial ws tracking + bool m_lastConfigVerificationWasSuccessful = true; + + private: + // internal methods + std::optional resetHLConfig(); + std::optional verifyConfigExists(); + void reloadRuleConfigs(); + + void postConfigReload(const Hyprlang::CParseResult& result); + + void registerConfigVar(const char* name, const Hyprlang::INT& val); + void registerConfigVar(const char* name, const Hyprlang::FLOAT& val); + void registerConfigVar(const char* name, const Hyprlang::VEC2& val); + void registerConfigVar(const char* name, const Hyprlang::STRING& val); + void registerConfigVar(const char* name, Hyprlang::CUSTOMTYPE&& val); + Hyprlang::CConfigValue* getConfigValueSafeDevice(const std::string& dev, const std::string& val, const std::string& fallback); + + UP m_config; + + std::vector m_configPaths; + std::string m_mainConfigPath; + + SSubmap m_currentSubmap; + + std::vector m_declaredPlugins; + std::vector m_pluginKeywords; + std::vector m_pluginVariables; + + std::vector> m_keywordRules; + + bool m_isFirstLaunch = true; // For exec-once + + bool m_firstExecDispatched = false; + bool m_manualCrashInitiated = false; + + std::vector> m_failedPluginConfigValues; // for plugin values of unloaded plugins + std::string m_configErrors = ""; + + uint32_t m_configValueNumber = 0; + + friend struct Config::Supplementary::SConfigOptionDescription; + friend class CMonitorRuleParser; + }; + + WP mgr(); +} diff --git a/src/config/shared/Types.hpp b/src/config/shared/Types.hpp new file mode 100644 index 000000000..6b0ad9d43 --- /dev/null +++ b/src/config/shared/Types.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "../../helpers/math/Math.hpp" + +#include + +namespace Config { + class ICustomConfigValueData; + + typedef int64_t INTEGER; + typedef float FLOAT; + typedef Vector2D VEC2; + typedef std::string STRING; + typedef ICustomConfigValueData* COMPLEX; +}; \ No newline at end of file diff --git a/src/config/shared/animation/AnimationTree.cpp b/src/config/shared/animation/AnimationTree.cpp new file mode 100644 index 000000000..bd763a701 --- /dev/null +++ b/src/config/shared/animation/AnimationTree.cpp @@ -0,0 +1,79 @@ +#include "AnimationTree.hpp" + +using namespace Config; + +UP& Config::animationTree() { + static UP p = makeUnique(); + return p; +} + +CAnimationTreeController::CAnimationTreeController() { + reset(); +} + +void CAnimationTreeController::reset() { + m_animationTree.createNode("__internal_fadeCTM"); + m_animationTree.createNode("global"); + + // global + m_animationTree.createNode("windows", "global"); + m_animationTree.createNode("layers", "global"); + m_animationTree.createNode("fade", "global"); + m_animationTree.createNode("border", "global"); + m_animationTree.createNode("borderangle", "global"); + m_animationTree.createNode("workspaces", "global"); + m_animationTree.createNode("zoomFactor", "global"); + m_animationTree.createNode("monitorAdded", "global"); + + // layer + m_animationTree.createNode("layersIn", "layers"); + m_animationTree.createNode("layersOut", "layers"); + + // windows + m_animationTree.createNode("windowsIn", "windows"); + m_animationTree.createNode("windowsOut", "windows"); + m_animationTree.createNode("windowsMove", "windows"); + + // fade + m_animationTree.createNode("fadeIn", "fade"); + m_animationTree.createNode("fadeOut", "fade"); + m_animationTree.createNode("fadeSwitch", "fade"); + m_animationTree.createNode("fadeShadow", "fade"); + m_animationTree.createNode("fadeGlow", "fade"); + m_animationTree.createNode("fadeDim", "fade"); + m_animationTree.createNode("fadeLayers", "fade"); + m_animationTree.createNode("fadeLayersIn", "fadeLayers"); + m_animationTree.createNode("fadeLayersOut", "fadeLayers"); + m_animationTree.createNode("fadePopups", "fade"); + m_animationTree.createNode("fadePopupsIn", "fadePopups"); + m_animationTree.createNode("fadePopupsOut", "fadePopups"); + m_animationTree.createNode("fadeDpms", "fade"); + + // workspaces + m_animationTree.createNode("workspacesIn", "workspaces"); + m_animationTree.createNode("workspacesOut", "workspaces"); + m_animationTree.createNode("specialWorkspace", "workspaces"); + m_animationTree.createNode("specialWorkspaceIn", "specialWorkspace"); + m_animationTree.createNode("specialWorkspaceOut", "specialWorkspace"); + + // init the root nodes + m_animationTree.setConfigForNode("global", 1, 8.f, "default"); + m_animationTree.setConfigForNode("__internal_fadeCTM", 1, 5.f, "linear"); + m_animationTree.setConfigForNode("borderangle", 0, 1, "default"); +} + +const std::unordered_map>& CAnimationTreeController::getAnimationConfig() { + return m_animationTree.getFullConfig(); +} + +SP CAnimationTreeController::getAnimationPropertyConfig(const std::string& name) { + return m_animationTree.getConfig(name); +} + +void CAnimationTreeController::setConfigForNode(const std::string& name, bool enabled, float speed, const std::string& bezier, const std::string& style) { + m_animationTree.setConfigForNode(name, enabled, speed, bezier, style); +} + +bool CAnimationTreeController::nodeExists(const std::string& name) { + return m_animationTree.nodeExists(name); +} diff --git a/src/config/shared/animation/AnimationTree.hpp b/src/config/shared/animation/AnimationTree.hpp new file mode 100644 index 000000000..301e09f68 --- /dev/null +++ b/src/config/shared/animation/AnimationTree.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "../../../helpers/memory/Memory.hpp" + +#include + +namespace Config { + class CAnimationTreeController { + public: + CAnimationTreeController(); + + const std::unordered_map>& getAnimationConfig(); + SP getAnimationPropertyConfig(const std::string&); + + // + void reset(); + + // + void setConfigForNode(const std::string& name, bool enabled, float speed, const std::string& bezier, const std::string& style = ""); + bool nodeExists(const std::string& name); + + private: + Hyprutils::Animation::CAnimationConfigTree m_animationTree; + }; + + UP& animationTree(); +}; \ No newline at end of file diff --git a/src/config/shared/complex/ComplexDataType.hpp b/src/config/shared/complex/ComplexDataType.hpp new file mode 100644 index 000000000..84b4c5038 --- /dev/null +++ b/src/config/shared/complex/ComplexDataType.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +namespace Config { + enum eConfigValueDataTypes : int8_t { + CVD_TYPE_INVALID = -1, + CVD_TYPE_GRADIENT = 0, + CVD_TYPE_CSS_VALUE = 1, + CVD_TYPE_FONT_WEIGHT = 2, + }; + + class IComplexConfigValue { + public: + virtual ~IComplexConfigValue() = default; + + virtual eConfigValueDataTypes getDataType() = 0; + + virtual std::string toString() = 0; + }; +} \ No newline at end of file diff --git a/src/config/shared/complex/ComplexDataTypes.hpp b/src/config/shared/complex/ComplexDataTypes.hpp new file mode 100644 index 000000000..536b0433a --- /dev/null +++ b/src/config/shared/complex/ComplexDataTypes.hpp @@ -0,0 +1,176 @@ +#pragma once +#include "ComplexDataType.hpp" +#include "../../../helpers/Color.hpp" + +#include + +#include +#include +#include +#include + +namespace Config { + + class CGradientValueData : public IComplexConfigValue { + public: + CGradientValueData() = default; + CGradientValueData(CHyprColor col) { + m_colors.push_back(col); + updateColorsOk(); + }; + CGradientValueData(std::vector&& cols, float ang) : m_colors(std::move(cols)), m_angle(ang) { + updateColorsOk(); + }; + virtual ~CGradientValueData() = default; + + virtual eConfigValueDataTypes getDataType() { + return CVD_TYPE_GRADIENT; + } + + void reset(CHyprColor col) { + m_colors.clear(); + m_colors.emplace_back(col); + m_angle = 0; + updateColorsOk(); + } + + void updateColorsOk() { + m_colorsOkLabA.clear(); + for (auto& c : m_colors) { + const auto OKLAB = c.asOkLab(); + m_colorsOkLabA.emplace_back(OKLAB.l); + m_colorsOkLabA.emplace_back(OKLAB.a); + m_colorsOkLabA.emplace_back(OKLAB.b); + m_colorsOkLabA.emplace_back(c.a); + } + } + + /* Vector containing the colors */ + std::vector m_colors; + + /* Vector containing pure colors for shoving into opengl */ + std::vector m_colorsOkLabA; + + /* Float corresponding to the angle (rad) */ + float m_angle = 0; + + // + bool operator==(const CGradientValueData& other) const { + if (other.m_colors.size() != m_colors.size() || m_angle != other.m_angle) + return false; + + for (size_t i = 0; i < m_colors.size(); ++i) + if (m_colors[i] != other.m_colors[i]) + return false; + + return true; + } + + virtual std::string toString() { + std::string result; + for (auto& c : m_colors) { + result += std::format("{:x} ", c.getAsHex()); + } + + result += std::format("{}deg", sc(m_angle * 180.0 / M_PI)); + return result; + } + }; + + class CCssGapData : public IComplexConfigValue { + public: + CCssGapData() : m_top(0), m_right(0), m_bottom(0), m_left(0) {}; + CCssGapData(int64_t global) : m_top(global), m_right(global), m_bottom(global), m_left(global) {}; + CCssGapData(int64_t vertical, int64_t horizontal) : m_top(vertical), m_right(horizontal), m_bottom(vertical), m_left(horizontal) {}; + CCssGapData(int64_t top, int64_t horizontal, int64_t bottom) : m_top(top), m_right(horizontal), m_bottom(bottom), m_left(horizontal) {}; + CCssGapData(int64_t top, int64_t right, int64_t bottom, int64_t left) : m_top(top), m_right(right), m_bottom(bottom), m_left(left) {}; + + /* Css like directions */ + int64_t m_top; + int64_t m_right; + int64_t m_bottom; + int64_t m_left; + + void parseGapData(Hyprutils::String::CVarList2 varlist) { + const auto toInt = [](std::string_view string) -> int { return std::stoi(std::string(string)); }; + + switch (varlist.size()) { + case 1: { + *this = CCssGapData(toInt(varlist[0])); + break; + } + case 2: { + *this = CCssGapData(toInt(varlist[0]), toInt(varlist[1])); + break; + } + case 3: { + *this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]), toInt(varlist[2])); + break; + } + case 4: { + *this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]), toInt(varlist[2]), toInt(varlist[3])); + break; + } + default: { + Log::logger->log(Log::WARN, "Too many arguments provided for gaps."); + *this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]), toInt(varlist[2]), toInt(varlist[3])); + break; + } + } + } + + void reset(int64_t global) { + m_top = global; + m_right = global; + m_bottom = global; + m_left = global; + } + + virtual eConfigValueDataTypes getDataType() { + return CVD_TYPE_CSS_VALUE; + } + + virtual std::string toString() { + return std::format("{} {} {} {}", m_top, m_right, m_bottom, m_left); + } + }; + + class CFontWeightConfigValueData : public IComplexConfigValue { + public: + CFontWeightConfigValueData() = default; + CFontWeightConfigValueData(const char* weight) { + parseWeight(weight); + } + + int64_t m_value = 400; // default to normal weight + + virtual eConfigValueDataTypes getDataType() { + return CVD_TYPE_FONT_WEIGHT; + } + + virtual std::string toString() { + return std::format("{}", m_value); + } + + void parseWeight(const std::string& strWeight) { + auto lcWeight{strWeight}; + std::ranges::transform(strWeight, lcWeight.begin(), ::tolower); + + // values taken from Pango weight enums + const auto WEIGHTS = std::map{ + {"thin", 100}, {"ultralight", 200}, {"light", 300}, {"semilight", 350}, {"book", 380}, {"normal", 400}, + {"medium", 500}, {"semibold", 600}, {"bold", 700}, {"ultrabold", 800}, {"heavy", 900}, {"ultraheavy", 1000}, + }; + + auto weight = WEIGHTS.find(lcWeight); + if (weight != WEIGHTS.end()) + m_value = weight->second; + else { + int w_i = std::stoi(strWeight); + if (w_i < 100 || w_i > 1000) + m_value = 400; + } + } + }; + +} diff --git a/src/config/ConfigWatcher.cpp b/src/config/shared/inotify/ConfigWatcher.cpp similarity index 73% rename from src/config/ConfigWatcher.cpp rename to src/config/shared/inotify/ConfigWatcher.cpp index dfd84b43b..02cedbd69 100644 --- a/src/config/ConfigWatcher.cpp +++ b/src/config/shared/inotify/ConfigWatcher.cpp @@ -3,24 +3,32 @@ #include #endif #include -#include "../debug/Log.hpp" +#include "../../../debug/log/Logger.hpp" +#include "../../ConfigValue.hpp" +#include "../../ConfigManager.hpp" #include #include #include #include using namespace Hyprutils::OS; +using namespace Config; + +UP& Config::watcher() { + static UP p = makeUnique(); + return p; +} CConfigWatcher::CConfigWatcher() : m_inotifyFd(inotify_init()) { if (!m_inotifyFd.isValid()) { - Debug::log(ERR, "CConfigWatcher couldn't open an inotify node. Config will not be automatically reloaded"); + Log::logger->log(Log::ERR, "CConfigWatcher couldn't open an inotify node. Config will not be automatically reloaded"); return; } // TODO: make CFileDescriptor take F_GETFL, F_SETFL const int FLAGS = fcntl(m_inotifyFd.get(), F_GETFL, 0); if (fcntl(m_inotifyFd.get(), F_SETFL, FLAGS | O_NONBLOCK) < 0) { - Debug::log(ERR, "CConfigWatcher couldn't non-block inotify node. Config will not be automatically reloaded"); + Log::logger->log(Log::ERR, "CConfigWatcher couldn't non-block inotify node. Config will not be automatically reloaded"); m_inotifyFd.reset(); return; } @@ -30,6 +38,11 @@ CFileDescriptor& CConfigWatcher::getInotifyFD() { return m_inotifyFd; } +void CConfigWatcher::update() { + static const auto PDISABLEAUTORELOAD = CConfigValue("misc:disable_autoreload"); + setWatchList(*PDISABLEAUTORELOAD ? std::vector{} : Config::mgr()->getConfigPaths()); +} + void CConfigWatcher::setWatchList(const std::vector& paths) { // we clear all watches first, because whichever fired is now invalid @@ -78,19 +91,19 @@ void CConfigWatcher::onInotifyEvent() { const auto* ev = rc(buffer.data() + offset); if (offset + sizeof(inotify_event) > sc(bytesRead)) { - Debug::log(ERR, "CConfigWatcher: malformed inotify event, truncated header"); + Log::logger->log(Log::ERR, "CConfigWatcher: malformed inotify event, truncated header"); break; } if (offset + sizeof(inotify_event) + ev->len > sc(bytesRead)) { - Debug::log(ERR, "CConfigWatcher: malformed inotify event, truncated name field"); + Log::logger->log(Log::ERR, "CConfigWatcher: malformed inotify event, truncated name field"); break; } const auto WD = std::ranges::find_if(m_watches, [wd = ev->wd](const auto& e) { return e.wd == wd; }); if (WD == m_watches.end()) - Debug::log(ERR, "CConfigWatcher: got an event for wd {} which we don't have?!", ev->wd); + Log::logger->log(Log::ERR, "CConfigWatcher: got an event for wd {} which we don't have?!", ev->wd); else m_watchCallback(SConfigWatchEvent{ .file = WD->file, diff --git a/src/config/shared/inotify/ConfigWatcher.hpp b/src/config/shared/inotify/ConfigWatcher.hpp new file mode 100644 index 000000000..046733830 --- /dev/null +++ b/src/config/shared/inotify/ConfigWatcher.hpp @@ -0,0 +1,37 @@ +#pragma once +#include "../../../helpers/memory/Memory.hpp" +#include +#include +#include +#include + +namespace Config { + class CConfigWatcher { + public: + CConfigWatcher(); + ~CConfigWatcher() = default; + + struct SConfigWatchEvent { + std::string file; + }; + + void update(); + + Hyprutils::OS::CFileDescriptor& getInotifyFD(); + void setWatchList(const std::vector& paths); + void setOnChange(const std::function& fn); + void onInotifyEvent(); + + private: + struct SInotifyWatch { + int wd = -1; + std::string file; + }; + + std::function m_watchCallback; + std::vector m_watches; + Hyprutils::OS::CFileDescriptor m_inotifyFd; + }; + + UP& watcher(); +} diff --git a/src/config/shared/monitor/MonitorRule.hpp b/src/config/shared/monitor/MonitorRule.hpp new file mode 100644 index 000000000..c47a03bb6 --- /dev/null +++ b/src/config/shared/monitor/MonitorRule.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include + +#include "../../../helpers/math/Math.hpp" +#include "../../../helpers/cm/ColorManagement.hpp" +#include "../../../helpers/CMType.hpp" +#include "../../../helpers/TransferFunction.hpp" +#include "../../../desktop/reserved/ReservedArea.hpp" + +namespace Config { + // Enum for the different types of auto directions, e.g. auto-left, auto-up. + enum eAutoDirs : uint8_t { + DIR_AUTO_NONE = 0, /* None will be treated as right. */ + DIR_AUTO_UP, + DIR_AUTO_DOWN, + DIR_AUTO_LEFT, + DIR_AUTO_RIGHT, + DIR_AUTO_CENTER_UP, + DIR_AUTO_CENTER_DOWN, + DIR_AUTO_CENTER_LEFT, + DIR_AUTO_CENTER_RIGHT + }; + + class CMonitorRule { + public: + CMonitorRule() = default; + ~CMonitorRule() = default; + + eAutoDirs m_autoDir = DIR_AUTO_NONE; + std::string m_name = ""; + Vector2D m_resolution = Vector2D(1280, 720); + Vector2D m_offset = Vector2D(0, 0); + float m_scale = 1; + float m_refreshRate = 60; // Hz + bool m_disabled = false; + wl_output_transform m_transform = WL_OUTPUT_TRANSFORM_NORMAL; + std::string m_mirrorOf = ""; + bool m_enable10bit = false; + NCMType::eCMType m_cmType = NCMType::CM_SRGB; + NTransferFunction::eTF m_sdrEotf = NTransferFunction::TF_DEFAULT; + float m_sdrSaturation = 1.F; // SDR -> HDR + float m_sdrBrightness = 1.F; // SDR -> HDR + Desktop::CReservedArea m_reservedArea; + std::string m_iccFile; + + int m_supportsWideColor = 0; // 0 - auto, 1 - force enable, -1 - force disable + int m_supportsHDR = 0; // 0 - auto, 1 - force enable, -1 - force disable + float m_sdrMinLuminance = 0.2F; // SDR -> HDR + int m_sdrMaxLuminance = 80; // SDR -> HDR + + // Incorrect values will result in reduced luminance range or incorrect tonemapping. Shouldn't damage the HW. Use with care in case of a faulty monitor firmware. + float m_minLuminance = -1.F; // >= 0 overrides EDID + int m_maxLuminance = -1; // >= 0 overrides EDID + int m_maxAvgLuminance = -1; // >= 0 overrides EDID + + drmModeModeInfo m_drmMode = {}; + std::optional m_vrr; + }; +}; \ No newline at end of file diff --git a/src/config/shared/monitor/MonitorRuleManager.cpp b/src/config/shared/monitor/MonitorRuleManager.cpp new file mode 100644 index 000000000..3c68f65b7 --- /dev/null +++ b/src/config/shared/monitor/MonitorRuleManager.cpp @@ -0,0 +1,252 @@ +#include "MonitorRuleManager.hpp" + +#include "../../../debug/log/Logger.hpp" +#include "../../../protocols/OutputManagement.hpp" +#include "../../../helpers/Monitor.hpp" +#include "../../../Compositor.hpp" +#include "../../../render/Renderer.hpp" +#include "../../../event/EventBus.hpp" +#include "../../../managers/eventLoop/EventLoopManager.hpp" + +#include + +using namespace Config; + +UP& Config::monitorRuleMgr() { + static UP p = makeUnique(); + return p; +} + +CMonitorRuleManager::CMonitorRuleManager() { + m_listeners.preChecksRender = Event::bus()->m_events.render.preChecks.listen([this](PHLMONITOR m) { + if (m_reloadScheduled) + performMonitorReload(); + + m_reloadScheduled = false; + }); +} + +void CMonitorRuleManager::clear() { + m_rules.clear(); +} + +void CMonitorRuleManager::add(CMonitorRule&& x) { + std::erase_if(m_rules, [&x](const auto& e) { return e.m_name == x.m_name; }); + m_rules.emplace_back(std::move(x)); +} + +CMonitorRule CMonitorRuleManager::get(const PHLMONITOR PMONITOR) { + auto applyWlrOutputConfig = [PMONITOR](CMonitorRule rule) -> CMonitorRule { + const auto CONFIG = PROTO::outputManagement->getOutputStateFor(PMONITOR); + + if (!CONFIG) + return rule; + + Log::logger->log(Log::DEBUG, "CConfigManager::getMonitorRuleFor: found a wlr_output_manager override for {}", PMONITOR->m_name); + + Log::logger->log(Log::DEBUG, " > overriding enabled: {} -> {}", !rule.m_disabled, !CONFIG->enabled); + rule.m_disabled = !CONFIG->enabled; + + if ((CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_MODE) || (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_CUSTOM_MODE)) { + Log::logger->log(Log::DEBUG, " > overriding mode: {:.0f}x{:.0f}@{:.2f}Hz -> {:.0f}x{:.0f}@{:.2f}Hz", rule.m_resolution.x, rule.m_resolution.y, rule.m_refreshRate, + CONFIG->resolution.x, CONFIG->resolution.y, CONFIG->refresh / 1000.F); + rule.m_resolution = CONFIG->resolution; + rule.m_refreshRate = CONFIG->refresh / 1000.F; + } + + if (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_POSITION) { + Log::logger->log(Log::DEBUG, " > overriding offset: {:.0f}, {:.0f} -> {:.0f}, {:.0f}", rule.m_offset.x, rule.m_offset.y, CONFIG->position.x, CONFIG->position.y); + rule.m_offset = CONFIG->position; + } + + if (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_TRANSFORM) { + Log::logger->log(Log::DEBUG, " > overriding transform: {} -> {}", sc(rule.m_transform), sc(CONFIG->transform)); + rule.m_transform = CONFIG->transform; + } + + if (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_SCALE) { + Log::logger->log(Log::DEBUG, " > overriding scale: {} -> {}", sc(rule.m_scale), sc(CONFIG->scale)); + rule.m_scale = CONFIG->scale; + } + + if (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC) { + Log::logger->log(Log::DEBUG, " > overriding vrr: {} -> {}", rule.m_vrr.value_or(0), CONFIG->adaptiveSync); + rule.m_vrr = sc(CONFIG->adaptiveSync); + } + + return rule; + }; + + for (auto const& r : m_rules | std::views::reverse) { + if (PMONITOR->matchesStaticSelector(r.m_name)) + return applyWlrOutputConfig(r); + } + + Log::logger->log(Log::WARN, "No rule found for {}, trying to use the first.", PMONITOR->m_name); + + for (auto const& r : m_rules) { + if (r.m_name.empty()) + return applyWlrOutputConfig(r); + } + + Log::logger->log(Log::WARN, "No rules configured. Using the default hardcoded one."); + + CMonitorRule fallbackRule; + fallbackRule.m_autoDir = eAutoDirs::DIR_AUTO_RIGHT; + fallbackRule.m_name = ""; + fallbackRule.m_resolution = Vector2D{}; + fallbackRule.m_offset = Vector2D{-INT32_MAX, -INT32_MAX}; + fallbackRule.m_scale = -1; + return applyWlrOutputConfig(fallbackRule); +} + +const std::vector& CMonitorRuleManager::all() { + return m_rules; +} + +std::vector& CMonitorRuleManager::allMut() { + return m_rules; +} + +void CMonitorRuleManager::scheduleReload() { + if (m_reloadScheduled) + return; + + m_reloadScheduled = true; +} + +void CMonitorRuleManager::performMonitorReload() { + bool overAgain = false; + + for (auto const& m : g_pCompositor->m_realMonitors) { + if (!m->m_output || m->m_isUnsafeFallback) + continue; + + auto rule = get(m); + + if (!m->applyMonitorRule(Config::CMonitorRule{rule})) { + overAgain = true; + break; + } + + // ensure mirror + m->setMirror(rule.m_mirrorOf); + + g_pHyprRenderer->arrangeLayersForMonitor(m->m_id); + } + + if (overAgain) + performMonitorReload(); + + m_reloadScheduled = false; + + Event::bus()->m_events.monitor.layoutChanged.emit(); +} + +void CMonitorRuleManager::ensureMonitorStatus() { + for (auto const& rm : g_pCompositor->m_realMonitors) { + if (!rm->m_output || rm->m_isUnsafeFallback) + continue; + + auto rule = get(rm); + + if (rule.m_disabled == rm->m_enabled) + rm->applyMonitorRule(std::move(rule)); + } +} + +void CMonitorRuleManager::ensureVRR(PHLMONITOR pMonitor) { + static auto PVRR = CConfigValue("misc:vrr"); + + static auto ensureVRRForDisplay = [&](PHLMONITOR m) -> void { + if (!m->m_output || m->m_createdByUser) + return; + + const auto USEVRR = m->m_activeMonitorRule.m_vrr.has_value() ? m->m_activeMonitorRule.m_vrr.value() : *PVRR; + + if (USEVRR == 0) { + if (m->m_vrrActive) { + m->m_output->state->resetExplicitFences(); + m->m_output->state->setAdaptiveSync(false); + + if (!m->m_state.commit()) + Log::logger->log(Log::ERR, "Couldn't commit output {} in ensureVRR -> false", m->m_output->name); + } + m->m_vrrActive = false; + return; + } + + const auto PWORKSPACE = m->m_activeWorkspace; + + if (USEVRR == 1) { + bool wantVRR = true; + if (PWORKSPACE && PWORKSPACE->m_hasFullscreenWindow && (PWORKSPACE->m_fullscreenMode & FSMODE_FULLSCREEN)) + wantVRR = !PWORKSPACE->getFullscreenWindow()->m_ruleApplicator->noVRR().valueOrDefault(); + + if (wantVRR) { + if (!m->m_vrrActive) { + m->m_output->state->resetExplicitFences(); + m->m_output->state->setAdaptiveSync(true); + + if (!m->m_state.test()) { + Log::logger->log(Log::DEBUG, "Pending output {} does not accept VRR.", m->m_output->name); + m->m_output->state->setAdaptiveSync(false); + } + + if (!m->m_state.commit()) + Log::logger->log(Log::ERR, "Couldn't commit output {} in ensureVRR -> true", m->m_output->name); + } + m->m_vrrActive = true; + } else { + if (m->m_vrrActive) { + m->m_output->state->resetExplicitFences(); + m->m_output->state->setAdaptiveSync(false); + + if (!m->m_state.commit()) + Log::logger->log(Log::ERR, "Couldn't commit output {} in ensureVRR -> false", m->m_output->name); + } + m->m_vrrActive = false; + } + return; + } else if (USEVRR == 2 || USEVRR == 3) { + if (!PWORKSPACE) + return; // ??? + + bool wantVRR = PWORKSPACE->m_hasFullscreenWindow && (PWORKSPACE->m_fullscreenMode & FSMODE_FULLSCREEN); + if (wantVRR && PWORKSPACE->getFullscreenWindow()->m_ruleApplicator->noVRR().valueOrDefault()) + wantVRR = false; + + if (wantVRR && USEVRR == 3) { + const auto contentType = PWORKSPACE->getFullscreenWindow()->getContentType(); + wantVRR = contentType == NContentType::CONTENT_TYPE_GAME || contentType == NContentType::CONTENT_TYPE_VIDEO; + } + + if (wantVRR) { + /* fullscreen */ + m->m_vrrActive = true; + + if (!m->m_output->state->state().adaptiveSync) { + 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); + } + } + } else { + m->m_vrrActive = false; + + m->m_output->state->setAdaptiveSync(false); + } + } + }; + + if (pMonitor) { + ensureVRRForDisplay(pMonitor); + return; + } + + for (auto const& m : g_pCompositor->m_monitors) { + ensureVRRForDisplay(m); + } +} diff --git a/src/config/shared/monitor/MonitorRuleManager.hpp b/src/config/shared/monitor/MonitorRuleManager.hpp new file mode 100644 index 000000000..316e5798b --- /dev/null +++ b/src/config/shared/monitor/MonitorRuleManager.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +#include "MonitorRule.hpp" +#include "../../../desktop/DesktopTypes.hpp" +#include "../../../helpers/memory/Memory.hpp" +#include "../../../helpers/signal/Signal.hpp" + +namespace Config { + class CMonitorRuleManager { + public: + CMonitorRuleManager(); + ~CMonitorRuleManager() = default; + + void clear(); + void add(CMonitorRule&&); + + void scheduleReload(); + + void ensureMonitorStatus(); + void ensureVRR(PHLMONITOR pMonitor = nullptr); + + CMonitorRule get(const PHLMONITOR); + const std::vector& all(); + std::vector& allMut(); + + private: + void performMonitorReload(); + + std::vector m_rules; + bool m_reloadScheduled = false; + + struct { + CHyprSignalListener preChecksRender; + } m_listeners; + }; + + UP& monitorRuleMgr(); +}; \ No newline at end of file diff --git a/src/config/shared/monitor/Parser.cpp b/src/config/shared/monitor/Parser.cpp new file mode 100644 index 000000000..05faa0c7a --- /dev/null +++ b/src/config/shared/monitor/Parser.cpp @@ -0,0 +1,294 @@ +#include "Parser.hpp" + +#include "../../../debug/log/Logger.hpp" + +#include +#include +#include +#include +#include + +using namespace Config; +using namespace Hyprutils::String; + +static bool parseModeLine(const std::string& modeline, drmModeModeInfo& mode) { + auto args = CVarList2(std::string{modeline}, 0, 's'); + + auto keyword = std::string{args[0]}; + std::ranges::transform(keyword, keyword.begin(), ::tolower); + + if (keyword != "modeline") + return false; + + if (args.size() < 10) { + Log::logger->log(Log::ERR, "modeline parse error: expected at least 9 arguments, got {}", args.size() - 1); + return false; + } + + int argno = 1; + + auto huErrStr = [](Hyprutils::String::eNumericParseResult r) -> const char* { + switch (r) { + case Hyprutils::String::NUMERIC_PARSE_BAD: return "bad input"; + case Hyprutils::String::NUMERIC_PARSE_GARBAGE: return "garbage input"; + case Hyprutils::String::NUMERIC_PARSE_OUT_OF_RANGE: return "out of range"; + case Hyprutils::String::NUMERIC_PARSE_OK: return "ok"; + default: return "error"; + } + }; + +#define ASSIGN_OR_FAIL(prop, type) \ + if (auto n = strToNumber(args[argno++]); n) \ + prop = n.value(); \ + else { \ + Log::logger->log(Log::ERR, "modeline parse error: invalid input at \"{}\": {}", args[argno - 1], huErrStr(n.error())); \ + return false; \ + } + + mode.type = DRM_MODE_TYPE_USERDEF; + + ASSIGN_OR_FAIL(mode.clock, float); + ASSIGN_OR_FAIL(mode.hdisplay, int); + ASSIGN_OR_FAIL(mode.hsync_start, int); + ASSIGN_OR_FAIL(mode.hsync_end, int); + ASSIGN_OR_FAIL(mode.htotal, int); + ASSIGN_OR_FAIL(mode.vdisplay, int); + ASSIGN_OR_FAIL(mode.vsync_start, int); + ASSIGN_OR_FAIL(mode.vsync_end, int); + ASSIGN_OR_FAIL(mode.vtotal, int); + + mode.clock *= 1000; + mode.vrefresh = mode.clock * 1000.0 * 1000.0 / mode.htotal / mode.vtotal; + +#undef ASSIGN_OR_FAIL + + // clang-format off + static std::unordered_map flagsmap = { + {"+hsync", DRM_MODE_FLAG_PHSYNC}, + {"-hsync", DRM_MODE_FLAG_NHSYNC}, + {"+vsync", DRM_MODE_FLAG_PVSYNC}, + {"-vsync", DRM_MODE_FLAG_NVSYNC}, + {"Interlace", DRM_MODE_FLAG_INTERLACE}, + }; + // clang-format on + + for (; argno < sc(args.size()); argno++) { + auto key = std::string{args[argno]}; + std::ranges::transform(key, key.begin(), ::tolower); + + auto it = flagsmap.find(key); + + if (it != flagsmap.end()) + mode.flags |= it->second; + else + Log::logger->log(Log::ERR, "Invalid flag {} in modeline", key); + } + + snprintf(mode.name, sizeof(mode.name), "%dx%d@%d", mode.hdisplay, mode.vdisplay, mode.vrefresh / 1000); + + return true; +} + +CMonitorRuleParser::CMonitorRuleParser(const std::string& name) { + m_rule.m_name = name; +} + +const std::string& CMonitorRuleParser::name() { + return m_rule.m_name; +} + +Config::CMonitorRule& CMonitorRuleParser::rule() { + return m_rule; +} + +std::optional CMonitorRuleParser::getError() { + if (m_error.empty()) + return {}; + return m_error; +} + +bool CMonitorRuleParser::parseMode(const std::string& value) { + if (value.starts_with("pref")) + m_rule.m_resolution = Vector2D(); + else if (value.starts_with("highrr")) + m_rule.m_resolution = Vector2D(-1, -1); + else if (value.starts_with("highres")) + m_rule.m_resolution = Vector2D(-1, -2); + else if (value.starts_with("maxwidth")) + m_rule.m_resolution = Vector2D(-1, -3); + else if (parseModeLine(value, m_rule.m_drmMode)) { + m_rule.m_resolution = Vector2D(m_rule.m_drmMode.hdisplay, m_rule.m_drmMode.vdisplay); + m_rule.m_refreshRate = sc(m_rule.m_drmMode.vrefresh) / 1000; + } else { + + if (!value.contains("x")) { + m_error += "invalid resolution "; + m_rule.m_resolution = Vector2D(); + return false; + } else { + try { + m_rule.m_resolution.x = stoi(value.substr(0, value.find_first_of('x'))); + m_rule.m_resolution.y = stoi(value.substr(value.find_first_of('x') + 1, value.find_first_of('@'))); + + if (value.contains("@")) + m_rule.m_refreshRate = stof(value.substr(value.find_first_of('@') + 1)); + } catch (...) { + m_error += "invalid resolution "; + m_rule.m_resolution = Vector2D(); + return false; + } + } + } + return true; +} + +bool CMonitorRuleParser::parsePosition(const std::string& value, bool isFirst) { + if (value.starts_with("auto")) { + m_rule.m_offset = Vector2D(-INT32_MAX, -INT32_MAX); + // If this is the first monitor rule needs to be on the right. + if (value == "auto-right" || value == "auto" || isFirst) + m_rule.m_autoDir = eAutoDirs::DIR_AUTO_RIGHT; + else if (value == "auto-left") + m_rule.m_autoDir = eAutoDirs::DIR_AUTO_LEFT; + else if (value == "auto-up") + m_rule.m_autoDir = eAutoDirs::DIR_AUTO_UP; + else if (value == "auto-down") + m_rule.m_autoDir = eAutoDirs::DIR_AUTO_DOWN; + else if (value == "auto-center-right") + m_rule.m_autoDir = eAutoDirs::DIR_AUTO_CENTER_RIGHT; + else if (value == "auto-center-left") + m_rule.m_autoDir = eAutoDirs::DIR_AUTO_CENTER_LEFT; + else if (value == "auto-center-up") + m_rule.m_autoDir = eAutoDirs::DIR_AUTO_CENTER_UP; + else if (value == "auto-center-down") + m_rule.m_autoDir = eAutoDirs::DIR_AUTO_CENTER_DOWN; + else { + Log::logger->log(Log::WARN, + "Invalid auto direction. Valid options are 'auto'," + "'auto-up', 'auto-down', 'auto-left', 'auto-right'," + "'auto-center-up', 'auto-center-down'," + "'auto-center-left', and 'auto-center-right'."); + m_error += "invalid auto direction "; + return false; + } + } else { + if (!value.contains("x")) { + m_error += "invalid offset "; + m_rule.m_offset = Vector2D(-INT32_MAX, -INT32_MAX); + return false; + } else { + try { + m_rule.m_offset.x = stoi(value.substr(0, value.find_first_of('x'))); + m_rule.m_offset.y = stoi(value.substr(value.find_first_of('x') + 1)); + } catch (...) { + m_error += "invalid offset "; + m_rule.m_offset = Vector2D(-INT32_MAX, -INT32_MAX); + return false; + } + } + } + return true; +} + +bool CMonitorRuleParser::parseScale(const std::string& value) { + if (value.starts_with("auto")) + m_rule.m_scale = -1; + else { + if (!isNumber(value, true)) { + m_error += "invalid scale "; + return false; + } else { + m_rule.m_scale = stof(value); + + if (m_rule.m_scale < 0.25F) { + m_error += "invalid scale "; + m_rule.m_scale = 1; + return false; + } + } + } + return true; +} + +bool CMonitorRuleParser::parseTransform(const std::string& value) { + if (!isNumber(value)) { + m_error += "invalid transform "; + return false; + } + + const auto TSF = std::stoi(value); + if (std::clamp(TSF, 0, 7) != TSF) { + Log::logger->log(Log::ERR, "Invalid transform {} in monitor", TSF); + m_error += "invalid transform "; + return false; + } + m_rule.m_transform = sc(TSF); + return true; +} + +bool CMonitorRuleParser::parseBitdepth(const std::string& value) { + m_rule.m_enable10bit = value == "10"; + return true; +} + +bool CMonitorRuleParser::parseCM(const std::string& value) { + auto parsedCM = NCMType::fromString(value); + if (!parsedCM.has_value()) { + m_error += "invalid cm "; + return false; + } + m_rule.m_cmType = parsedCM.value(); + return true; +} + +bool CMonitorRuleParser::parseSDRBrightness(const std::string& value) { + try { + m_rule.m_sdrBrightness = stof(value); + } catch (...) { + m_error += "invalid sdrbrightness "; + return false; + } + return true; +} + +bool CMonitorRuleParser::parseSDRSaturation(const std::string& value) { + try { + m_rule.m_sdrSaturation = stof(value); + } catch (...) { + m_error += "invalid sdrsaturation "; + return false; + } + return true; +} + +bool CMonitorRuleParser::parseVRR(const std::string& value) { + if (!isNumber(value)) { + m_error += "invalid vrr "; + return false; + } + + m_rule.m_vrr = std::stoi(value); + return true; +} + +bool CMonitorRuleParser::parseICC(const std::string& val) { + if (val.empty()) { + m_error += "invalid icc "; + return false; + } + m_rule.m_iccFile = val; + return true; +} + +void CMonitorRuleParser::setDisabled() { + m_rule.m_disabled = true; +} + +void CMonitorRuleParser::setMirror(const std::string& value) { + m_rule.m_mirrorOf = value; +} + +bool CMonitorRuleParser::setReserved(const Desktop::CReservedArea& value) { + m_rule.m_reservedArea = value; + return true; +} \ No newline at end of file diff --git a/src/config/shared/monitor/Parser.hpp b/src/config/shared/monitor/Parser.hpp new file mode 100644 index 000000000..1b3fce142 --- /dev/null +++ b/src/config/shared/monitor/Parser.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "MonitorRule.hpp" + +namespace Config { + class CMonitorRuleParser { + public: + CMonitorRuleParser(const std::string& name); + + const std::string& name(); + CMonitorRule& rule(); + std::optional getError(); + bool parseMode(const std::string& value); + bool parsePosition(const std::string& value, bool isFirst = false); + bool parseScale(const std::string& value); + bool parseTransform(const std::string& value); + bool parseBitdepth(const std::string& value); + bool parseCM(const std::string& value); + 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); + bool setReserved(const Desktop::CReservedArea& value); + + private: + CMonitorRule m_rule; + std::string m_error = ""; + }; +}; \ No newline at end of file diff --git a/src/config/shared/workspace/WorkspaceRule.cpp b/src/config/shared/workspace/WorkspaceRule.cpp new file mode 100644 index 000000000..28d5fcab7 --- /dev/null +++ b/src/config/shared/workspace/WorkspaceRule.cpp @@ -0,0 +1,48 @@ +#include "WorkspaceRule.hpp" + +using namespace Config; + +void CWorkspaceRule::mergeLeft(const CWorkspaceRule& other) { + if (m_monitor.empty()) + m_monitor = other.m_monitor; + if (m_workspaceString.empty()) + m_workspaceString = other.m_workspaceString; + if (m_workspaceName.empty()) + m_workspaceName = other.m_workspaceName; + if (m_workspaceId == WORKSPACE_INVALID) + m_workspaceId = other.m_workspaceId; + + if (other.m_isDefault) + m_isDefault = true; + if (other.m_isPersistent) + m_isPersistent = true; + if (other.m_gapsIn.has_value()) + m_gapsIn = other.m_gapsIn; + if (other.m_gapsOut.has_value()) + m_gapsOut = other.m_gapsOut; + if (other.m_floatGaps) + m_floatGaps = other.m_floatGaps; + if (other.m_borderSize.has_value()) + m_borderSize = other.m_borderSize; + if (other.m_noBorder.has_value()) + m_noBorder = other.m_noBorder; + if (other.m_noRounding.has_value()) + m_noRounding = other.m_noRounding; + if (other.m_decorate.has_value()) + m_decorate = other.m_decorate; + if (other.m_noShadow.has_value()) + m_noShadow = other.m_noShadow; + if (other.m_onCreatedEmptyRunCmd.has_value()) + m_onCreatedEmptyRunCmd = other.m_onCreatedEmptyRunCmd; + if (other.m_defaultName.has_value()) + m_defaultName = other.m_defaultName; + if (other.m_layout.has_value()) + m_layout = other.m_layout; + if (!other.m_layoutopts.empty()) { + for (const auto& layoutopt : other.m_layoutopts) { + m_layoutopts[layoutopt.first] = layoutopt.second; + } + } + if (other.m_animationStyle.has_value()) + m_animationStyle = other.m_animationStyle; +} diff --git a/src/config/shared/workspace/WorkspaceRule.hpp b/src/config/shared/workspace/WorkspaceRule.hpp new file mode 100644 index 000000000..ec4c04931 --- /dev/null +++ b/src/config/shared/workspace/WorkspaceRule.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include + +#include "../complex/ComplexDataTypes.hpp" +#include "../../../defines.hpp" + +namespace Config { + class CWorkspaceRule { + public: + CWorkspaceRule() = default; + ~CWorkspaceRule() = default; + + CWorkspaceRule(const CWorkspaceRule&) = default; + CWorkspaceRule(CWorkspaceRule&) = default; + CWorkspaceRule(CWorkspaceRule&&) = default; + + CWorkspaceRule& operator=(const CWorkspaceRule&) = default; + + // merge other into us + void mergeLeft(const CWorkspaceRule& other); + + std::string m_monitor = ""; + std::string m_workspaceString = ""; + std::string m_workspaceName = ""; + WORKSPACEID m_workspaceId = -1; + bool m_isDefault = false; + bool m_isPersistent = false; + std::optional m_gapsIn; + std::optional m_gapsOut; + std::optional m_floatGaps = m_gapsOut; + std::optional m_borderSize; + std::optional m_decorate; + std::optional m_noRounding; + std::optional m_noBorder; + std::optional m_noShadow; + std::optional m_onCreatedEmptyRunCmd; + std::optional m_defaultName; + std::optional m_layout; + std::map m_layoutopts; + std::optional m_animationStyle; + }; +}; \ No newline at end of file diff --git a/src/config/shared/workspace/WorkspaceRuleManager.cpp b/src/config/shared/workspace/WorkspaceRuleManager.cpp new file mode 100644 index 000000000..ca45873af --- /dev/null +++ b/src/config/shared/workspace/WorkspaceRuleManager.cpp @@ -0,0 +1,85 @@ +#include "WorkspaceRuleManager.hpp" + +#include "../../../Compositor.hpp" +#include "../../../helpers/Monitor.hpp" + +#include + +using namespace Config; +using namespace Hyprutils::String; + +UP& Config::workspaceRuleMgr() { + static UP p = makeUnique(); + return p; +} + +void CWorkspaceRuleManager::clear() { + m_rules.clear(); +} + +void CWorkspaceRuleManager::add(CWorkspaceRule&& x) { + m_rules.emplace_back(std::move(x)); +} + +void CWorkspaceRuleManager::replaceOrAdd(CWorkspaceRule&& x) { + auto it = std::ranges::find_if(m_rules, [&x](const auto& r) { return r.m_workspaceString == x.m_workspaceString; }); + if (it == m_rules.end()) + m_rules.emplace_back(std::move(x)); + else + (*it).mergeLeft(x); +} + +std::optional CWorkspaceRuleManager::getWorkspaceRuleFor(PHLWORKSPACE workspace) { + bool any = false; + + CWorkspaceRule mergedRule; + for (auto const& rule : m_rules) { + if (!workspace->matchesStaticSelector(rule.m_workspaceString)) + continue; + + mergedRule.mergeLeft(rule); + any = true; + } + + if (!any) + return std::nullopt; + + return mergedRule; +} + +std::string CWorkspaceRuleManager::getDefaultWorkspaceFor(const std::string& name) { + for (auto other = m_rules.begin(); other != m_rules.end(); ++other) { + if (other->m_isDefault) { + if (other->m_monitor == name) + return other->m_workspaceString; + if (other->m_monitor.starts_with("desc:")) { + auto const monitor = g_pCompositor->getMonitorFromDesc(trim(other->m_monitor.substr(5))); + if (monitor && monitor->m_name == name) + return other->m_workspaceString; + } + } + } + return ""; +} + +PHLMONITOR CWorkspaceRuleManager::getBoundMonitorForWS(const std::string& wsname) { + auto monitor = getBoundMonitorStringForWS(wsname); + if (monitor.starts_with("desc:")) + return g_pCompositor->getMonitorFromDesc(trim(monitor.substr(5))); + else + return g_pCompositor->getMonitorFromName(monitor); +} + +std::string CWorkspaceRuleManager::getBoundMonitorStringForWS(const std::string& wsname) { + for (auto const& wr : m_rules) { + const auto WSNAME = wr.m_workspaceName.starts_with("name:") ? wr.m_workspaceName.substr(5) : wr.m_workspaceName; + if (WSNAME == wsname) + return wr.m_monitor; + } + + return ""; +} + +const std::vector& CWorkspaceRuleManager::getAllWorkspaceRules() { + return m_rules; +} \ No newline at end of file diff --git a/src/config/shared/workspace/WorkspaceRuleManager.hpp b/src/config/shared/workspace/WorkspaceRuleManager.hpp new file mode 100644 index 000000000..6b0731680 --- /dev/null +++ b/src/config/shared/workspace/WorkspaceRuleManager.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#include "WorkspaceRule.hpp" +#include "../../../desktop/DesktopTypes.hpp" +#include "../../../helpers/memory/Memory.hpp" + +namespace Config { + class CWorkspaceRuleManager { + public: + CWorkspaceRuleManager() = default; + ~CWorkspaceRuleManager() = default; + + void clear(); + void add(CWorkspaceRule&&); + void replaceOrAdd(CWorkspaceRule&&); + + std::optional getWorkspaceRuleFor(PHLWORKSPACE workspace); + std::string getDefaultWorkspaceFor(const std::string&); + PHLMONITOR getBoundMonitorForWS(const std::string&); + std::string getBoundMonitorStringForWS(const std::string&); + const std::vector& getAllWorkspaceRules(); + + private: + std::vector m_rules; + }; + + UP& workspaceRuleMgr(); +}; \ No newline at end of file diff --git a/src/config/supplementary/ConfigDescriptions.cpp b/src/config/supplementary/ConfigDescriptions.cpp new file mode 100644 index 000000000..d0a1b4673 --- /dev/null +++ b/src/config/supplementary/ConfigDescriptions.cpp @@ -0,0 +1,122 @@ +#include "ConfigDescriptions.hpp" + +// FIXME: this NO NO NO!!!! +#include "../ConfigManager.hpp" +#include "../shared/complex/ComplexDataType.hpp" +#include "../../helpers/MiscFunctions.hpp" + +#include +#include + +using namespace Config::Supplementary; + +std::string SConfigOptionDescription::jsonify() const { + auto parseData = [this]() -> std::string { + return std::visit( + [this](auto&& val) { + const auto CONFIG_VAL = Config::mgr()->getConfigValue(value); + if (!CONFIG_VAL.dataptr) { + Log::logger->log(Log::ERR, "invalid SConfigOptionDescription: no config option {} exists", value); + return std::string{""}; + } + const char* const EXPLICIT = CONFIG_VAL.setByUser ? "true" : "false"; + + std::string currentValue = "undefined"; + + if (typeid(Config::INTEGER) == std::type_index(*CONFIG_VAL.type)) + currentValue = std::format("{}", **rc(CONFIG_VAL.dataptr)); + else if (typeid(Config::FLOAT) == std::type_index(*CONFIG_VAL.type)) + currentValue = std::format("{:.2f}", **rc(CONFIG_VAL.dataptr)); + else if (typeid(Hyprlang::STRING) == std::type_index(*CONFIG_VAL.type)) + currentValue = std::format("\"{}\"", *rc(CONFIG_VAL.dataptr)); + else if (typeid(Config::STRING) == std::type_index(*CONFIG_VAL.type)) + currentValue = std::format("\"{}\"", **rc(CONFIG_VAL.dataptr)); + else if (typeid(Config::VEC2) == std::type_index(*CONFIG_VAL.type)) { + const auto V = **rc(CONFIG_VAL.dataptr); + currentValue = std::format("\"{}, {}\"", V.x, V.y); + } else if (typeid(Hyprlang::VEC2) == std::type_index(*CONFIG_VAL.type)) { + const auto V = **rc(CONFIG_VAL.dataptr); + currentValue = std::format("\"{}, {}\"", V.x, V.y); + } else if (typeid(Config::IComplexConfigValue*) == std::type_index(*CONFIG_VAL.type)) { + const auto DATA = *rc(CONFIG_VAL.dataptr); + currentValue = std::format("\"{}\"", DATA->toString()); + } else if (typeid(void*) == std::type_index(*CONFIG_VAL.type)) { + // legacy hyprlang value + const auto DATA = *rc(CONFIG_VAL.dataptr); + const auto DATA2 = rc(DATA->getData()); + currentValue = std::format("\"{}\"", DATA2->toString()); + } + + try { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return std::format(R"#( "value": "{}", + "current": {}, + "explicit": {})#", + val.value, currentValue, EXPLICIT); + } else if constexpr (std::is_same_v) { + return std::format(R"#( "value": {}, + "min": {}, + "max": {}, + "current": {}, + "explicit": {})#", + val.value, val.min, val.max, currentValue, EXPLICIT); + } else if constexpr (std::is_same_v) { + return std::format(R"#( "value": {}, + "min": {}, + "max": {}, + "current": {}, + "explicit": {})#", + val.value, val.min, val.max, currentValue, EXPLICIT); + } else if constexpr (std::is_same_v) { + return std::format(R"#( "value": "{}", + "current": {}, + "explicit": {})#", + val.color.getAsHex(), currentValue, EXPLICIT); + } else if constexpr (std::is_same_v) { + return std::format(R"#( "value": {}, + "current": {}, + "explicit": {})#", + val.value, currentValue, EXPLICIT); + } else if constexpr (std::is_same_v) { + return std::format(R"#( "value": "{}", + "firstIndex": {}, + "current": {}, + "explicit": {})#", + val.choices, val.firstIndex, currentValue, EXPLICIT); + } else if constexpr (std::is_same_v) { + return std::format(R"#( "x": {}, + "y": {}, + "min_x": {}, + "min_y": {}, + "max_x": {}, + "max_y": {}, + "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": {}, + "explicit": {})#", + val.gradient, currentValue, EXPLICIT); + } + + } catch (std::bad_any_cast& e) { Log::logger->log(Log::ERR, "Bad any_cast on value {} in descriptions", value); } + return std::string{""}; + }, + data); + }; + + std::string json = std::format(R"#({{ + "value": "{}", + "description": "{}", + "type": {}, + "flags": {}, + "data": {{ + {} + }} +}})#", + value, escapeJSONStrings(description), sc(type), sc(flags), parseData()); + + return json; +} \ No newline at end of file diff --git a/src/config/supplementary/ConfigDescriptions.hpp b/src/config/supplementary/ConfigDescriptions.hpp new file mode 100644 index 000000000..862cc1a93 --- /dev/null +++ b/src/config/supplementary/ConfigDescriptions.hpp @@ -0,0 +1,2289 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "../../helpers/math/Math.hpp" +#include "../../helpers/Color.hpp" + +namespace Config::Supplementary { + + enum eConfigOptionType : uint8_t { + CONFIG_OPTION_BOOL = 0, + CONFIG_OPTION_INT = 1, /* e.g. 0/1/2*/ + CONFIG_OPTION_FLOAT = 2, + CONFIG_OPTION_STRING_SHORT = 3, /* e.g. "auto" */ + CONFIG_OPTION_STRING_LONG = 4, /* e.g. a command */ + CONFIG_OPTION_COLOR = 5, + CONFIG_OPTION_CHOICE = 6, /* e.g. "one", "two", "three" */ + CONFIG_OPTION_GRADIENT = 7, + CONFIG_OPTION_VECTOR = 8, + }; + + enum eConfigOptionFlags : uint8_t { + CONFIG_OPTION_FLAG_PERCENTAGE = (1 << 0), + }; + + struct SConfigOptionDescription { + + struct SBoolData { + bool value = false; + }; + + struct SRangeData { + int value = 0, min = 0, max = 2; + }; + + struct SFloatData { + float value = 0, min = 0, max = 100; + }; + + struct SStringData { + std::string value; + }; + + struct SColorData { + CHyprColor color; + }; + + struct SChoiceData { + int firstIndex = 0; + std::string choices; // comma-separated + }; + + struct SGradientData { + std::string gradient; + }; + + struct SVectorData { + Vector2D vec, min, max; + }; + + std::string value; // e.g. general:gaps_in + std::string description; + std::string specialCategory; // if value is special (e.g. device:abc) value will be abc and special device + bool specialKey = false; + eConfigOptionType type = CONFIG_OPTION_BOOL; + uint32_t flags = 0; // eConfigOptionFlags + + std::string jsonify() const; + + // + std::variant data; + }; + + inline static const std::vector CONFIG_OPTIONS = { + + /* + * general: + */ + + SConfigOptionDescription{ + .value = "general:border_size", + .description = "size of the border around windows", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1, 0, 20}, + }, + SConfigOptionDescription{ + .value = "general:gaps_in", + .description = "gaps between windows\n\nsupports css style gaps (top, right, bottom, left -> 5 10 15 20)", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"5"}, + }, + SConfigOptionDescription{ + .value = "general:gaps_out", + .description = "gaps between windows and monitor edges\n\nsupports css style gaps (top, right, bottom, left -> 5 10 15 20)", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"20"}, + }, + SConfigOptionDescription{ + .value = "general:float_gaps", + .description = "gaps between windows and monitor edges for floating windows\n\nsupports css style gaps (top, right, bottom, left -> 5 10 15 20). \n-1 means default " + "gaps_in/gaps_out\n0 means no gaps", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"0"}, + }, + SConfigOptionDescription{ + .value = "general:gaps_workspaces", + .description = "gaps between workspaces. Stacks with gaps_out.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 100}, + }, + SConfigOptionDescription{ + .value = "general:col.inactive_border", + .description = "border color for inactive windows", + .type = CONFIG_OPTION_GRADIENT, + .data = SConfigOptionDescription::SGradientData{"0xff444444"}, + }, + SConfigOptionDescription{ + .value = "general:col.active_border", + .description = "border color for the active window", + .type = CONFIG_OPTION_GRADIENT, + .data = SConfigOptionDescription::SGradientData{"0xffffffff"}, + }, + SConfigOptionDescription{ + .value = "general:col.nogroup_border", + .description = "inactive border color for window that cannot be added to a group (see denywindowfromgroup dispatcher)", + .type = CONFIG_OPTION_GRADIENT, + .data = SConfigOptionDescription::SGradientData{"0xffffaaff"}, + }, + SConfigOptionDescription{ + .value = "general:col.nogroup_border_active", + .description = "active border color for window that cannot be added to a group", + .type = CONFIG_OPTION_GRADIENT, + .data = SConfigOptionDescription::SGradientData{"0xffff00ff"}, + }, + SConfigOptionDescription{ + .value = "general:layout", + .description = "which layout to use. [dwindle/master]", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"dwindle"}, + }, + SConfigOptionDescription{ + .value = "general:no_focus_fallback", + .description = "if true, will not fall back to the next available window when moving focus in a direction where no window was found", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "general:resize_on_border", + .description = "enables resizing windows by clicking and dragging on borders and gaps", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "general:extend_border_grab_area", + .description = "extends the area around the border where you can click and drag on, only used when general:resize_on_border is on.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{15, 0, 100}, + }, + SConfigOptionDescription{ + .value = "general:hover_icon_on_border", + .description = "show a cursor icon when hovering over borders, only used when general:resize_on_border is on.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "general:allow_tearing", + .description = "master switch for allowing tearing to occur. See the Tearing page.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "general:resize_corner", + .description = "force floating windows to use a specific corner when being resized (1-4 going clockwise from top left, 0 to disable)", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 4}, + }, + SConfigOptionDescription{ + .value = "general:snap:enabled", + .description = "enable snapping for floating windows", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "general:snap:window_gap", + .description = "minimum gap in pixels between windows before snapping", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{10, 0, 100}, + }, + SConfigOptionDescription{ + .value = "general:snap:monitor_gap", + .description = "minimum gap in pixels between window and monitor edges before snapping", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{10, 0, 100}, + }, + SConfigOptionDescription{ + .value = "general:snap:border_overlap", + .description = "if true, windows snap such that only one border's worth of space is between them", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "general:snap:respect_gaps", + .description = "if true, snapping will respect gaps between windows", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "general:modal_parent_blocking", + .description = "if true, parent windows of modals will not be interactive.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "general:locale", + .description = "overrides the system locale", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, + }, + + /* + * decoration: + */ + + SConfigOptionDescription{ + .value = "decoration:rounding", + .description = "rounded corners' radius (in layout px)", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 20}, + }, + SConfigOptionDescription{ + .value = "decoration:rounding_power", + .description = "rounding power of corners (2 is a circle)", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{2, 2, 10}, + }, + SConfigOptionDescription{ + .value = "decoration:active_opacity", + .description = "opacity of active windows. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 0, 1}, + }, + SConfigOptionDescription{ + .value = "decoration:inactive_opacity", + .description = "opacity of inactive windows. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 0, 1}, + }, + SConfigOptionDescription{ + .value = "decoration:fullscreen_opacity", + .description = "opacity of fullscreen windows. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 0, 1}, + }, + SConfigOptionDescription{ + .value = "decoration:shadow:enabled", + .description = "enable drop shadows on windows", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "decoration:shadow:range", + .description = "Shadow range (size) in layout px", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{4, 0, 100}, + }, + SConfigOptionDescription{ + .value = "decoration:shadow:render_power", + .description = "in what power to render the falloff (more power, the faster the falloff) [1 - 4]", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{3, 1, 4}, + }, + SConfigOptionDescription{ + .value = "decoration:shadow:sharp", + .description = "whether the shadow should be sharp or not. Akin to an infinitely high render power.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "decoration:shadow:color", + .description = "shadow's color. Alpha dictates shadow's opacity.", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{0xee1a1a1a}, + }, + SConfigOptionDescription{ + .value = "decoration:shadow:color_inactive", + .description = "inactive shadow color. (if not set, will fall back to col.shadow)", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{}, //TODO: UNSET? + }, + SConfigOptionDescription{ + .value = "decoration:shadow:offset", + .description = "shadow's rendering offset.", + .type = CONFIG_OPTION_VECTOR, + .data = SConfigOptionDescription::SVectorData{{}, {-250, -250}, {250, 250}}, + }, + SConfigOptionDescription{ + .value = "decoration:shadow:scale", + .description = "shadow's scale. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 0, 1}, + }, + SConfigOptionDescription{ + .value = "decoration:glow:enabled", + .description = "enable inner glow on windows", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "decoration:glow:range", + .description = "glow range (size) in layout px", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{10, 0, 100}, + }, + SConfigOptionDescription{ + .value = "decoration:glow:render_power", + .description = "in what power to render the falloff (more power, the faster the falloff) [1 - 4]", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{3, 1, 4}, + }, + SConfigOptionDescription{ + .value = "decoration:glow:color", + .description = "glow's color. Alpha dictates glow's opacity.", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{0xee33ccff}, + }, + SConfigOptionDescription{ + .value = "decoration:glow:color_inactive", + .description = "inactive glow color. (if not set, will fall back to decoration:glow:color)", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{0x0033ccff}, + }, + SConfigOptionDescription{ + .value = "decoration:dim_modal", + .description = "enables dimming of parents of modal windows", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "decoration:dim_inactive", + .description = "enables dimming of inactive windows", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "decoration:dim_strength", + .description = "how much inactive windows should be dimmed [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.5, 0, 1}, + }, + SConfigOptionDescription{ + .value = "decoration:dim_special", + .description = "how much to dim the rest of the screen by when a special workspace is open. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.2, 0, 1}, + }, + SConfigOptionDescription{ + .value = "decoration:dim_around", + .description = "how much the dimaround window rule should dim by. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.4, 0, 1}, + }, + SConfigOptionDescription{ + .value = "decoration:screen_shader", + .description = "screen_shader a path to a custom shader to be applied at the end of rendering. See examples/screenShader.frag for an example.", + .type = CONFIG_OPTION_STRING_LONG, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "decoration:border_part_of_window", + .description = "whether the border should be treated as a part of the window.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + + /* + * blur: + */ + + SConfigOptionDescription{ + .value = "decoration:blur:enabled", + .description = "enable kawase window background blur", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "decoration:blur:size", + .description = "blur size (distance)", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{8, 0, 100}, + }, + SConfigOptionDescription{ + .value = "decoration:blur:passes", + .description = "the amount of passes to perform", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1, 0, 10}, + }, + SConfigOptionDescription{ + .value = "decoration:blur:ignore_opacity", + .description = "make the blur layer ignore the opacity of the window", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "decoration:blur:new_optimizations", + .description = "whether to enable further optimizations to the blur. Recommended to leave on, as it will massively improve performance.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "decoration:blur:xray", + .description = + "if enabled, floating windows will ignore tiled windows in their blur. Only available if blur_new_optimizations is true. Will reduce overhead on floating " + "blur significantly.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "decoration:blur:noise", + .description = "how much noise to apply. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.0117, 0, 1}, + }, + SConfigOptionDescription{ + .value = "decoration:blur:contrast", + .description = "contrast modulation for blur. [0.0 - 2.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.8916, 0, 2}, + }, + SConfigOptionDescription{ + .value = "decoration:blur:brightness", + .description = "brightness modulation for blur. [0.0 - 2.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.8172, 0, 2}, + }, + SConfigOptionDescription{ + .value = "decoration:blur:vibrancy", + .description = "Increase saturation of blurred colors. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.1696, 0, 1}, + }, + SConfigOptionDescription{ + .value = "decoration:blur:vibrancy_darkness", + .description = "How strong the effect of vibrancy is on dark areas . [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0, 0, 1}, + }, + SConfigOptionDescription{ + .value = "decoration:blur:special", + .description = "whether to blur behind the special workspace (note: expensive)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "decoration:blur:popups", + .description = "whether to blur popups (e.g. right-click menus)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "decoration:blur:popups_ignorealpha", + .description = "works like ignorealpha in layer rules. If pixel opacity is below set value, will not blur. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.2, 0, 1}, + }, + SConfigOptionDescription{ + .value = "decoration:blur:input_methods", + .description = "whether to blur input methods (e.g. fcitx5)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "decoration:blur:input_methods_ignorealpha", + .description = "works like ignorealpha in layer rules. If pixel opacity is below set value, will not blur. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.2, 0, 1}, + }, + + /* + * animations: + */ + + SConfigOptionDescription{ + .value = "animations:enabled", + .description = "enable animations", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "animations:workspace_wraparound", + .description = "changes the direction of slide animations between the first and last workspaces", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + + /* + * input: + */ + + SConfigOptionDescription{ + .value = "input:kb_model", + .description = "Appropriate XKB keymap parameter. See the note below.", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{STRVAL_EMPTY}, + }, + SConfigOptionDescription{ + .value = "input:kb_layout", + .description = "Appropriate XKB keymap parameter", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"us"}, + }, + SConfigOptionDescription{ + .value = "input:kb_variant", + .description = "Appropriate XKB keymap parameter", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, + }, + SConfigOptionDescription{ + .value = "input:kb_options", + .description = "Appropriate XKB keymap parameter", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, + }, + SConfigOptionDescription{ + .value = "input:kb_rules", + .description = "Appropriate XKB keymap parameter", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, + }, + SConfigOptionDescription{ + .value = "input:kb_file", + .description = "Appropriate XKB keymap file", + .type = CONFIG_OPTION_STRING_LONG, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "input:numlock_by_default", + .description = "Engage numlock by default.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:resolve_binds_by_sym", + .description = "Determines how keybinds act when multiple layouts are used. If false, keybinds will always act as if the first specified layout is active. If true, " + "keybinds specified by symbols are activated when you type the respective symbol with the current layout.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:repeat_rate", + .description = "The repeat rate for held-down keys, in repeats per second.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{25, 0, 200}, + }, + SConfigOptionDescription{ + .value = "input:repeat_delay", + .description = "Delay before a held-down key is repeated, in milliseconds.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{600, 0, 2000}, + }, + SConfigOptionDescription{ + .value = "input:sensitivity", + .description = "Sets the mouse input sensitivity. Value is clamped to the range -1.0 to 1.0.", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0, -1, 1}, + }, + SConfigOptionDescription{ + .value = "input:accel_profile", + .description = "Sets the cursor acceleration profile. Can be one of adaptive, flat. Can also be custom, see below. Leave empty to use libinput's default mode for your " + "input device. [adaptive/flat/custom]", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "input:force_no_accel", + .description = + "Force no cursor acceleration. This bypasses most of your pointer settings to get as raw of a signal as possible. Enabling this is not recommended due to " + "potential cursor desynchronization.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:rotation", + .description = "Sets the rotation of a device in degrees clockwise off the logical neutral position. Value is clamped to the range 0 to 359.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 359}, + }, + SConfigOptionDescription{ + .value = "input:left_handed", + .description = "Switches RMB and LMB", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:scroll_points", + .description = + "Sets the scroll acceleration profile, when accel_profile is set to custom. Has to be in the form . Leave empty to have a flat scroll curve.", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "input:scroll_method", + .description = "Sets the scroll method. Can be one of 2fg (2 fingers), edge, on_button_down, no_scroll. [2fg/edge/on_button_down/no_scroll]", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "input:scroll_button", + .description = "Sets the scroll button. Has to be an int, cannot be a string. Check wev if you have any doubts regarding the ID. 0 means default.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 300}, + }, + SConfigOptionDescription{ + .value = "input:scroll_button_lock", + .description = + "If the scroll button lock is enabled, the button does not need to be held down. Pressing and releasing the button toggles the button lock, which logically " + "holds the button down or releases it. While the button is logically held down, motion events are converted to scroll events.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:scroll_factor", + .description = "Multiplier added to scroll movement for external mice. Note that there is a separate setting for touchpad scroll_factor.", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 0, 2}, + }, + SConfigOptionDescription{ + .value = "input:natural_scroll", + .description = "Inverts scrolling direction. When enabled, scrolling moves content directly, rather than manipulating a scrollbar.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:follow_mouse", + .description = "Specify if and how cursor movement should affect window focus. See the note below. [0/1/2/3]", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1, 0, 3}, + }, + SConfigOptionDescription{ + .value = "input:follow_mouse_shrink", + .description = + "Shrinks the inactive window hitboxes used for focus detection by the specified number of pixels. This creates a dead zone in gaps between windows where " + "moving the cursor will not change focus. Works only with follow_mouse = 1.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 300}, + }, + SConfigOptionDescription{ + .value = "input:follow_mouse_threshold", + .description = "The smallest distance in logical pixels the mouse needs to travel for the window under it to get focused. Works only with follow_mouse = 1.", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{}, + }, + SConfigOptionDescription{ + .value = "input:focus_on_close", + .description = + "Controls the window focus behavior when a window is closed. When set to 0, focus will shift to the next window candidate. When set to 1, focus will shift " + "to the window under the cursor. When set to 2, focus will shift to the most recently used/active window.", + .type = CONFIG_OPTION_CHOICE, + .data = SConfigOptionDescription::SChoiceData{0, "next,cursor,mru"}, + }, + SConfigOptionDescription{ + .value = "input:mouse_refocus", + .description = "if disabled, mouse focus won't switch to the hovered window unless the mouse crosses a window boundary when follow_mouse=1.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "input:float_switch_override_focus", + .description = + "If enabled (1 or 2), focus will change to the window under the cursor when changing from tiled-to-floating and vice versa. If 2, focus will also follow " + "mouse on float-to-float switches.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1, 0, 2}, + }, + SConfigOptionDescription{ + .value = "input:special_fallthrough", + .description = "if enabled, having only floating windows in the special workspace will not block focusing windows in the regular workspace.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:off_window_axis_events", + .description = + "Handles axis events around (gaps/border for tiled, dragarea/border for floated) a focused window. 0 ignores axis events 1 sends out-of-bound coordinates 2 " + "fakes pointer coordinates to the closest point inside the window 3 warps the cursor to the closest point inside the window", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1, 0, 3}, + }, + SConfigOptionDescription{ + .value = "input:emulate_discrete_scroll", + .description = + "Emulates discrete scrolling from high resolution scrolling events. 0 disables it, 1 enables handling of non-standard events only, and 2 force enables all " + "scroll wheel events to be handled", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1, 0, 2}, + }, + + /* + * input:touchpad: + */ + + SConfigOptionDescription{ + .value = "input:touchpad:disable_while_typing", + .description = "Disable the touchpad while typing.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "input:touchpad:natural_scroll", + .description = "Inverts scrolling direction. When enabled, scrolling moves content directly, rather than manipulating a scrollbar.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:touchpad:scroll_factor", + .description = "Multiplier applied to the amount of scroll movement.", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 0, 2}, + }, + SConfigOptionDescription{ + .value = "input:touchpad:middle_button_emulation", + .description = "Sending LMB and RMB simultaneously will be interpreted as a middle click. This disables any touchpad area that would normally send a middle click " + "based on location.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:touchpad:tap_button_map", + .description = "Sets the tap button mapping for touchpad button emulation. Can be one of lrm (default) or lmr (Left, Middle, Right Buttons). [lrm/lmr]", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "input:touchpad:clickfinger_behavior", + .description = "Button presses with 1, 2, or 3 fingers will be mapped to LMB, RMB, and MMB respectively. This disables interpretation of clicks based on location on " + "the touchpad.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:touchpad:tap-to-click", + .description = "Tapping on the touchpad with 1, 2, or 3 fingers will send LMB, RMB, and MMB respectively.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "input:touchpad:drag_lock", + .description = "When enabled, lifting the finger off while dragging will not drop the dragged item. 0 -> disabled, 1 -> enabled with timeout, 2 -> enabled sticky." + "dragging will not drop the dragged item.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 2}, + }, + SConfigOptionDescription{ + .value = "input:touchpad:tap-and-drag", + .description = "Sets the tap and drag mode for the touchpad", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:touchpad:flip_x", + .description = "Inverts the horizontal movement of the touchpad", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:touchpad:flip_y", + .description = "Inverts the vertical movement of the touchpad", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:touchpad:drag_3fg", + .description = "Three Finger Drag 0 -> disabled, 1 -> 3 finger, 2 -> 4 finger", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 2}, + }, + + /* + * input:touchdevice: + */ + + SConfigOptionDescription{ + .value = "input:touchdevice:transform", + .description = "Transform the input from touchdevices. The possible transformations are the same as those of the monitors", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 6}, // ##TODO RANGE? + }, + SConfigOptionDescription{ + .value = "input:touchdevice:output", + .description = "The monitor to bind touch devices. The default is auto-detection. To stop auto-detection, use an empty string or the [[Empty]] value.", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "input:touchdevice:enabled", + .description = "Whether input is enabled for touch devices.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + + /* + * input:virtualkeyboard: + */ + + SConfigOptionDescription{ + .value = "input:virtualkeyboard:share_states", + .description = "Unify key down states and modifier states with other keyboards. 0 -> no, 1 -> yes, 2 -> yes unless IME client", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{2, 0, 2}, + }, + SConfigOptionDescription{ + .value = "input:virtualkeyboard:release_pressed_on_close", + .description = "Release all pressed keys by virtual keyboard on close.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + + /* + * input:tablet: + */ + + SConfigOptionDescription{ + .value = "input:tablet:transform", + .description = "transform the input from tablets. The possible transformations are the same as those of the monitors", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 6}, // ##TODO RANGE? + }, + SConfigOptionDescription{ + .value = "input:tablet:output", + .description = "the monitor to bind tablets. Can be current or a monitor name. Leave empty to map across all monitors.", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "input:tablet:region_position", + .description = "position of the mapped region in monitor layout relative to the top left corner of the bound monitor or all monitors.", + .type = CONFIG_OPTION_VECTOR, + .data = SConfigOptionDescription::SVectorData{{}, {-20000, -20000}, {20000, 20000}}, + }, + SConfigOptionDescription{ + .value = "input:tablet:absolute_region_position", + .description = "whether to treat the region_position as an absolute position in monitor layout. Only applies when output is empty.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:tablet:region_size", + .description = "size of the mapped region. When this variable is set, tablet input will be mapped to the region. [0, 0] or invalid size means unset.", + .type = CONFIG_OPTION_VECTOR, + .data = SConfigOptionDescription::SVectorData{{}, {-100, -100}, {4000, 4000}}, + }, + SConfigOptionDescription{ + .value = "input:tablet:relative_input", + .description = "whether the input should be relative", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:tablet:left_handed", + .description = "if enabled, the tablet will be rotated 180 degrees", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:tablet:active_area_size", + .description = "size of tablet's active area in mm", + .type = CONFIG_OPTION_VECTOR, + .data = SConfigOptionDescription::SVectorData{{}, {}, {500, 500}}, + }, + SConfigOptionDescription{ + .value = "input:tablet:active_area_position", + .description = "position of the active area in mm", + .type = CONFIG_OPTION_VECTOR, + .data = SConfigOptionDescription::SVectorData{{}, {}, {500, 500}}, + }, + + /* ##TODO + * + * PER DEVICE SETTINGS? + * + * */ + + /* + * gestures: + */ + + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_distance", + .description = "in px, the distance of the touchpad gesture", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{300, 0, 2000}, //##TODO RANGE? + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_touch", + .description = "enable workspace swiping from the edge of a touchscreen", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_invert", + .description = "invert the direction (touchpad only)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_touch_invert", + .description = "invert the direction (touchscreen only)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_min_speed_to_force", + .description = "minimum speed in px per timepoint to force the change ignoring cancel_ratio. Setting to 0 will disable this mechanic.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{30, 0, 200}, //##TODO RANGE? + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_cancel_ratio", + .description = "how much the swipe has to proceed in order to commence it. (0.7 -> if > 0.7 * distance, switch, if less, revert) [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.5, 0, 1}, + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_create_new", + .description = "whether a swipe right on the last workspace should create a new one.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_direction_lock", + .description = "if enabled, switching direction will be locked when you swipe past the direction_lock_threshold (touchpad only).", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_direction_lock_threshold", + .description = "in px, the distance to swipe before direction lock activates (touchpad only).", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{10, 0, 200}, //##TODO RANGE? + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_forever", + .description = "if enabled, swiping will not clamp at the neighboring workspaces but continue to the further ones.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_use_r", + .description = "if enabled, swiping will use the r prefix instead of the m prefix for finding workspaces.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "gestures:close_max_timeout", + .description = "Timeout for closing windows with the close gesture, in ms.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1000, 10, 2000}, + }, + + /* + * group: + */ + + SConfigOptionDescription{ + .value = "group:insert_after_current", + .description = "whether new windows in a group spawn after current or at group tail", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "group:focus_removed_window", + .description = "whether Hyprland should focus on the window that has just been moved out of the group", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "group:merge_groups_on_drag", + .description = "whether window groups can be dragged into other groups", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "group:merge_groups_on_groupbar", + .description = "whether one group will be merged with another when dragged into its groupbar", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "group:col.border_active", + .description = "border color for inactive windows", + .type = CONFIG_OPTION_GRADIENT, + .data = SConfigOptionDescription::SGradientData{"0x66ffff00"}, + }, + SConfigOptionDescription{ + .value = "group:col.border_inactive", + .description = "border color for the active window", + .type = CONFIG_OPTION_GRADIENT, + .data = SConfigOptionDescription::SGradientData{"0x66777700"}, + }, + SConfigOptionDescription{ + .value = "group:col.border_locked_inactive", + .description = "inactive border color for window that cannot be added to a group (see denywindowfromgroup dispatcher)", + .type = CONFIG_OPTION_GRADIENT, + .data = SConfigOptionDescription::SGradientData{"0x66ff5500"}, + }, + SConfigOptionDescription{ + .value = "group:col.border_locked_active", + .description = "active border color for window that cannot be added to a group", + .type = CONFIG_OPTION_GRADIENT, + .data = SConfigOptionDescription::SGradientData{"0x66775500"}, + }, + SConfigOptionDescription{ + .value = "group:auto_group", + .description = "automatically group new windows", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "group:drag_into_group", + .description = "whether dragging a window into a unlocked group will merge them. Options: 0 (disabled), 1 (enabled), 2 (only when dragging into the groupbar)", + .type = CONFIG_OPTION_CHOICE, + .data = SConfigOptionDescription::SChoiceData{0, "disabled,enabled,only when dragging into the groupbar"}, + }, + SConfigOptionDescription{ + .value = "group:merge_floated_into_tiled_on_groupbar", + .description = "whether dragging a floating window into a tiled window groupbar will merge them", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "group:group_on_movetoworkspace", + .description = "whether using movetoworkspace[silent] will merge the window into the workspace's solitary unlocked group", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + + /* + * group:groupbar: + */ + + SConfigOptionDescription{ + .value = "group:groupbar:enabled", + .description = "enables groupbars", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:font_family", + .description = "font used to display groupbar titles, use misc:font_family if not specified", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{STRVAL_EMPTY}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "group:groupbar:font_weight_active", + .description = "weight of the font used to display active groupbar titles", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"normal"}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:font_weight_inactive", + .description = "weight of the font used to display inactive groupbar titles", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"normal"}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:font_size", + .description = "font size of groupbar title", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{8, 2, 64}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:gradients", + .description = "enables gradients", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:height", + .description = "height of the groupbar", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{14, 1, 64}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:indicator_gap", + .description = "height of the gap between the groupbar indicator and title", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 64}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:indicator_height", + .description = "height of the groupbar indicator", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{3, 1, 64}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:stacked", + .description = "render the groupbar as a vertical stack", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:priority", + .description = "sets the decoration priority for groupbars", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{3, 0, 6}, //##TODO RANGE? + }, + SConfigOptionDescription{ + .value = "group:groupbar:render_titles", + .description = "whether to render titles in the group bar decoration", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:scrolling", + .description = "whether scrolling in the groupbar changes group active window", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:rounding", + .description = "how much to round the groupbar", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1, 0, 20}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:rounding_power", + .description = "rounding power of groupbar corners (2 is a circle)", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{2, 2, 10}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:gradient_rounding", + .description = "how much to round the groupbar gradient", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1, 0, 20}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:gradient_rounding_power", + .description = "rounding power of groupbar gradient corners (2 is a circle)", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{2, 2, 10}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:round_only_edges", + .description = "if yes, will only round at the groupbar edges", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:gradient_round_only_edges", + .description = "if yes, will only round at the groupbar gradient edges", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:text_color", + .description = "color for window titles in the groupbar", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{0xffffffff}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:text_color_inactive", + .description = "color for inactive windows' titles in the groupbar (if unset, defaults to text_color)", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{}, //TODO: UNSET? + }, + SConfigOptionDescription{ + .value = "group:groupbar:text_color_locked_active", + .description = "color for the active window's title in a locked group (if unset, defaults to text_color)", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{}, //TODO: UNSET? + }, + SConfigOptionDescription{ + .value = "group:groupbar:text_color_locked_inactive", + .description = "color for inactive windows' titles in locked groups (if unset, defaults to text_color_inactive)", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{}, //TODO: UNSET? + }, + SConfigOptionDescription{ + .value = "group:groupbar:col.active", + .description = "active group border color", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{0x66ffff00}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:col.inactive", + .description = "inactive (out of focus) group border color", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{0x66777700}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:col.locked_active", + .description = "active locked group border color", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{0x66ff5500}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:col.locked_inactive", + .description = "controls the group bar text color", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{0x66775500}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:gaps_out", + .description = "gap between gradients and window", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{2, 0, 20}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:gaps_in", + .description = "gap between gradients", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{2, 0, 20}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:keep_upper_gap", + .description = "keep an upper gap above gradient", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:text_offset", + .description = "set an offset for a text", + .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", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + + /* + * misc: + */ + + SConfigOptionDescription{ + .value = "misc:disable_hyprland_logo", + .description = "disables the random Hyprland logo / anime girl background. :(", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:disable_splash_rendering", + .description = "disables the Hyprland splash rendering. (requires a monitor reload to take effect)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:col.splash", + .description = "Changes the color of the splash text (requires a monitor reload to take effect).", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{0xffffffff}, + }, + SConfigOptionDescription{ + .value = "misc:font_family", + .description = "Set the global default font to render the text including debug fps/notification, config error messages and etc., selected from system fonts.", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"Sans"}, + }, + SConfigOptionDescription{ + .value = "misc:splash_font_family", + .description = "Changes the font used to render the splash text, selected from system fonts (requires a monitor reload to take effect).", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{STRVAL_EMPTY}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "misc:force_default_wallpaper", + .description = "Enforce any of the 3 default wallpapers. Setting this to 0 or 1 disables the anime background. -1 means “random”. [-1/0/1/2]", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{-1, -1, 2}, + }, + SConfigOptionDescription{ + .value = "misc:vrr", + .description = " controls the VRR (Adaptive Sync) of your monitors. 0 - off, 1 - on, 2 - fullscreen only, 3 - fullscreen with game or video content type [0/1/2/3]", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{.value = 0, .min = 0, .max = 3}, + }, + SConfigOptionDescription{ + .value = "misc:mouse_move_enables_dpms", + .description = "If DPMS is set to off, wake up the monitors if the mouse move", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:key_press_enables_dpms", + .description = "If DPMS is set to off, wake up the monitors if a key is pressed.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:name_vk_after_proc", + .description = "Name virtual keyboards after the processes that create them. E.g. /usr/bin/fcitx5 will have hl-virtual-keyboard-fcitx5.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "misc:always_follow_on_dnd", + .description = "Will make mouse focus follow the mouse when drag and dropping. Recommended to leave it enabled, especially for people using focus follows mouse at 0.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "misc:layers_hog_keyboard_focus", + .description = "If true, will make keyboard-interactive layers keep their focus on mouse move (e.g. wofi, bemenu)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "misc:animate_manual_resizes", + .description = "If true, will animate manual window resizes/moves", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:animate_mouse_windowdragging", + .description = "If true, will animate windows being dragged by mouse, note that this can cause weird behavior on some curves", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:disable_autoreload", + .description = "If true, the config will not reload automatically on save, and instead needs to be reloaded with hyprctl reload. Might save on battery.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:enable_swallow", + .description = "Enable window swallowing", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:swallow_regex", + .description = "The class regex to be used for windows that should be swallowed (usually, a terminal). To know more about the list of regex which can be used use this " + "cheatsheet.", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "misc:swallow_exception_regex", + .description = + "The title regex to be used for windows that should not be swallowed by the windows specified in swallow_regex (e.g. wev). The regex is matched against the " + "parent (e.g. Kitty) window’s title on the assumption that it changes to whatever process it’s running.", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "misc:focus_on_activate", + .description = "Whether Hyprland should focus an app that requests to be focused (an activate request)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:mouse_move_focuses_monitor", + .description = "Whether mouse moving into a different monitor should focus it", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "misc:allow_session_lock_restore", + .description = "if true, will allow you to restart a lockscreen app in case it crashes (red screen of death)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:session_lock_xray", + .description = "keep rendering workspaces below your lockscreen", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:background_color", + .description = "change the background color. (requires enabled disable_hyprland_logo)", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{0x111111}, + }, + SConfigOptionDescription{ + .value = "misc:close_special_on_empty", + .description = "close the special workspace if the last window is removed", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "misc:on_focus_under_fullscreen", + .description = "if there is a fullscreen or maximized window, decide whether a tiled window requested to focus should replace it, stay behind or disable the " + "fullscreen/maximized state. 0 - ignore focus request (keep focus on fullscreen window), 1 - takes over, 2 - unfullscreen/unmaximize [0/1/2]", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{2, 0, 2}, + }, + SConfigOptionDescription{ + .value = "misc:exit_window_retains_fullscreen", + .description = "if true, closing a fullscreen window makes the next focused window fullscreen", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:initial_workspace_tracking", + .description = "if enabled, windows will open on the workspace they were invoked on. 0 - disabled, 1 - single-shot, 2 - persistent (all children too)", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1, 0, 2}, + }, + SConfigOptionDescription{ + .value = "misc:middle_click_paste", + .description = "whether to enable middle-click-paste (aka primary selection)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "misc:render_unfocused_fps", + .description = "the maximum limit for renderunfocused windows' fps in the background", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{15, 1, 120}, + }, + SConfigOptionDescription{ + .value = "misc:disable_xdg_env_checks", + .description = "disable the warning if XDG environment is externally managed", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:disable_hyprland_guiutils_check", + .description = "disable the warning if hyprland-guiutils is missing", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:disable_watchdog_warning", + .description = "whether to disable the warning about not using start-hyprland.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:lockdead_screen_delay", + .description = "the delay in ms after the lockdead screen appears if the lock screen did not appear after a lock event occurred.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1000, 0, 5000}, + }, + SConfigOptionDescription{ + .value = "misc:enable_anr_dialog", + .description = "whether to enable the ANR (app not responding) dialog when your apps hang", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "misc:anr_missed_pings", + .description = "number of missed pings before showing the ANR dialog", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{5, 1, 20}, + }, + SConfigOptionDescription{ + .value = "misc:screencopy_force_8b", + .description = "forces 8 bit screencopy (fixes apps that don't understand 10bit)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "misc:disable_scale_notification", + .description = "disables notification popup when a monitor fails to set a suitable scale and falls back to suggested", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:size_limits_tiled", + .description = "whether to apply minsize and maxsize rules to tiled windows", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + + /* + * binds: + */ + + SConfigOptionDescription{ + .value = "binds:pass_mouse_when_bound", + .description = "if disabled, will not pass the mouse events to apps / dragging windows around if a keybind has been triggered.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "binds:scroll_event_delay", + .description = "in ms, how many ms to wait after a scroll event to allow passing another one for the binds.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{300, 0, 2000}, + }, + SConfigOptionDescription{ + .value = "binds:workspace_back_and_forth", + .description = "If enabled, an attempt to switch to the currently focused workspace will instead switch to the previous workspace. Akin to i3’s auto_back_and_forth.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "binds:hide_special_on_workspace_change", + .description = + "If enabled, changing the active workspace (including to itself) will hide the special workspace on the monitor where the newly active workspace resides.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "binds:allow_workspace_cycles", + .description = + "If enabled, workspaces don’t forget their previous workspace, so cycles can be created by switching to the first workspace in a sequence, then endlessly " + "going to the previous workspace.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "binds:workspace_center_on", + .description = "Whether switching workspaces should center the cursor on the workspace (0) or on the last active window for that workspace (1)", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 1}, + }, + SConfigOptionDescription{ + .value = "binds:focus_preferred_method", + .description = "sets the preferred focus finding method when using focuswindow/movewindow/etc with a direction. 0 - history (recent have priority), 1 - length (longer " + "shared edges have priority)", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 1}, + }, + SConfigOptionDescription{ + .value = "binds:ignore_group_lock", + .description = "If enabled, dispatchers like moveintogroup, moveoutofgroup and movewindoworgroup will ignore lock per group.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "binds:movefocus_cycles_fullscreen", + .description = "If enabled, when on a fullscreen window, movefocus will cycle fullscreen, if not, it will move the focus in a direction.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "binds:movefocus_cycles_groupfirst", + .description = + "If enabled, when in a grouped window, movefocus will cycle windows in the groups first, then at each ends of tabs, it'll move on to other windows/groups", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "binds:disable_keybind_grabbing", + .description = "If enabled, apps that request keybinds to be disabled (e.g. VMs) will not be able to do so.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "binds:window_direction_monitor_fallback", + .description = "If enabled, moving a window or focus over the edge of a monitor with a direction will move it to the next monitor in that direction.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "binds:allow_pin_fullscreen", + .description = "Allows fullscreen to pinned windows, and restore their pinned status afterwards", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "binds:drag_threshold", + .description = "Movement threshold in pixels for window dragging and c/g bind flags. 0 to disable and grab on mousedown.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, INT_MAX}, + }, + + /* + * xwayland: + */ + + SConfigOptionDescription{ + .value = "xwayland:enabled", + .description = "allow running applications using X11", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "xwayland:use_nearest_neighbor", + .description = "uses the nearest neighbor filtering for xwayland apps, making them pixelated rather than blurry", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "xwayland:force_zero_scaling", + .description = "forces a scale of 1 on xwayland windows on scaled displays.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "xwayland:create_abstract_socket", + .description = "Create the abstract Unix domain socket for XWayland", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + + /* + * opengl: + */ + + SConfigOptionDescription{ + .value = "opengl:nvidia_anti_flicker", + .description = "reduces flickering on nvidia at the cost of possible frame drops on lower-end GPUs. On non-nvidia, this is ignored.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + + /* + * render: + */ + + SConfigOptionDescription{ + .value = "render:direct_scanout", + .description = "Enables direct scanout. Direct scanout attempts to reduce lag when there is only one fullscreen application on a screen (e.g. game). It is also " + "recommended to set this to false if the fullscreen application shows graphical glitches. 0 - off, 1 - on, 2 - auto (on with content type 'game')", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{.value = 0, .min = 0, .max = 2}, + }, + SConfigOptionDescription{ + .value = "render:expand_undersized_textures", + .description = "Whether to expand textures that have not yet resized to be larger, or to just stretch them instead.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "render:xp_mode", + .description = "Disable back buffer and bottom layer rendering.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "render:ctm_animation", + .description = "Whether to enable a fade animation for CTM changes (hyprsunset). 2 means 'auto' (Yes on everything but Nvidia).", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{2, 0, 2}, + }, + SConfigOptionDescription{ + .value = "render:cm_enabled", + .description = "Enable Color Management pipelines (requires restart to fully take effect)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "render:send_content_type", + .description = "Report content type to allow monitor profile autoswitch (may result in a black screen during the switch)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "render:cm_auto_hdr", + .description = "Auto-switch to hdr mode when fullscreen app is in hdr, 0 - off, 1 - hdr, 2 - hdredid", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{.value = 1, .min = 0, .max = 2}, + }, + SConfigOptionDescription{ + .value = "render:new_render_scheduling", + .description = "enable new render scheduling, which should improve FPS on underpowered devices. This does not add latency when your PC can keep up.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "render:non_shader_cm", + .description = "Enable CM without shader. 0 - disable, 1 - whenever possible, 2 - DS and passthrough only, 3 - disable and ignore CM issues", + .type = CONFIG_OPTION_CHOICE, + .data = SConfigOptionDescription::SChoiceData{0, "disable,always,ondemand,ignore"}, + }, + SConfigOptionDescription{ + .value = "render:non_shader_cm_interop", + .description = "non_shader_cm interaction with ctm proto (hyprsunset and similar). 0 - disable, 1 - enable, 2 - auto (enabled for unknown content type)", + .type = CONFIG_OPTION_CHOICE, + .data = SConfigOptionDescription::SRangeData{.value = 2, .min = 0, .max = 2}, + }, + 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}, + }, + { + .value = "render:use_fp16", + .description = "Use experimental internal FP16 buffer. 0 - disabled, 1 - on, 2 - auto (enabled in HDR mode)", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{.value = 2, .min = 0, .max = 2}, + }, + { + .value = "render:keep_unmodified_copy", + .description = "Keep umodified SDR frame copy for sreensharing. 0 - disabled, 1 - on, 2 - auto (enabled in HDR with SDR modifiers)", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{.value = 2, .min = 0, .max = 2}, + }, + + /* + * cursor: + */ + + SConfigOptionDescription{ + .value = "cursor:invisible", + .description = "don't render cursors", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "cursor:no_hardware_cursors", + .description = "disables hardware cursors. Auto = disable when multi-gpu on nvidia", + .type = CONFIG_OPTION_CHOICE, + .data = SConfigOptionDescription::SChoiceData{0, "Disabled,Enabled,Auto"}, + }, + SConfigOptionDescription{ + .value = "cursor:no_break_fs_vrr", + .description = + "disables scheduling new frames on cursor movement for fullscreen apps with VRR enabled to avoid framerate spikes (may require no_hardware_cursors = true) " + "0 - off, 1 - on, 2 - auto (on with content type 'game')", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{.value = 2, .min = 0, .max = 2}, + }, + SConfigOptionDescription{ + .value = "cursor:min_refresh_rate", + .description = "minimum refresh rate for cursor movement when no_break_fs_vrr is active. Set to minimum supported refresh rate or higher", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{24, 10, 500}, + }, + SConfigOptionDescription{ + .value = "cursor:hotspot_padding", + .description = "the padding, in logical px, between screen edges and the cursor", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1, 0, 20}, + }, + SConfigOptionDescription{ + .value = "cursor:inactive_timeout", + .description = "in seconds, after how many seconds of cursor’s inactivity to hide it. Set to 0 for never.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 20}, + }, + SConfigOptionDescription{ + .value = "cursor:no_warps", + .description = "if true, will not warp the cursor in many cases (focusing, keybinds, etc)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "cursor:persistent_warps", + .description = "When a window is refocused, the cursor returns to its last position relative to that window, rather than to the centre.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "cursor:warp_on_change_workspace", + .description = + "Move the cursor to the last focused window after changing the workspace. Options: 0 (Disabled), 1 (Enabled), 2 (Force - ignores cursor:no_warps option)", + .type = CONFIG_OPTION_CHOICE, + .data = SConfigOptionDescription::SChoiceData{0, "Disabled,Enabled,Force"}, + }, + SConfigOptionDescription{ + .value = "cursor:warp_on_toggle_special", + .description = "Move the cursor to the last focused window when toggling a special workspace. Options: 0 (Disabled), 1 (Enabled), " + "2 (Force - ignores cursor:no_warps option)", + .type = CONFIG_OPTION_CHOICE, + .data = SConfigOptionDescription::SChoiceData{0, "Disabled,Enabled,Force"}, + }, + SConfigOptionDescription{ + .value = "cursor:default_monitor", + .description = "the name of a default monitor for the cursor to be set to on startup (see hyprctl monitors for names)", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "cursor:zoom_factor", + .description = "the factor to zoom by around the cursor. Like a magnifying glass. Minimum 1.0 (meaning no zoom)", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 1, 10}, + }, + SConfigOptionDescription{ + .value = "cursor:zoom_rigid", + .description = "whether the zoom should follow the cursor rigidly (cursor is always centered if it can be) or loosely", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "cursor:zoom_disable_aa", + .description = "If enabled, when zooming, no antialiasing will be used (zoom will be pixelated)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "cursor:zoom_detached_camera", + .description = "Detaches the camera from the mouse when zoomed in, only ever moving to keep the mouse in view", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "cursor:enable_hyprcursor", + .description = "whether to enable hyprcursor support", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "cursor:hide_on_key_press", + .description = "Hides the cursor when you press any key until the mouse is moved.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "cursor:hide_on_touch", + .description = "Hides the cursor when the last input was a touch input until a mouse input is done.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "cursor:hide_on_tablet", + .description = "Hides the cursor when the last input was a tablet input until a mouse input is done.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + 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}, + }, + SConfigOptionDescription{ + .value = "cursor:sync_gsettings_theme", + .description = + "sync xcursor theme with gsettings, it applies cursor-theme and cursor-size on theme load to gsettings making most CSD gtk based clients use same xcursor " + "theme and size.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "cursor:warp_back_after_non_mouse_input", + .description = "warp the cursor back to where it was after using a non-mouse input to move it, and then returning back to mouse.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + + /* + * ecosystem: + */ + SConfigOptionDescription{ + .value = "ecosystem:no_update_news", + .description = "disable the popup that shows up when you update hyprland to a new version.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "ecosystem:no_donation_nag", + .description = "disable the popup that shows up twice a year encouraging to donate.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "ecosystem:enforce_permissions", + .description = "whether to enable permission control (see https://wiki.hypr.land/Configuring/Permissions/).", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + + /* + * debug: + */ + + SConfigOptionDescription{ + .value = "debug:overlay", + .description = "print the debug performance overlay. Disable VFR for accurate results.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "debug:damage_blink", + .description = "disable logging to a file", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "debug:vfr", + .description = "controls the VFR status of Hyprland. Do not turn off unless debugging.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + 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", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "debug:disable_time", + .description = "disables time logging", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "debug:damage_tracking", + .description = "redraw only the needed bits of the display. Do not change. (default: full - 2) monitor - 1, none - 0", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{2, 0, 2}, + }, + SConfigOptionDescription{ + .value = "debug:enable_stdout_logs", + .description = "enables logging to stdout", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "debug:manual_crash", + .description = "set to 1 and then back to 0 to crash Hyprland.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 1}, + }, + SConfigOptionDescription{ + .value = "debug:suppress_errors", + .description = "if true, do not display config file parsing errors.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "debug:disable_scale_checks", + .description = "disables verification of the scale factors. Will result in pixel alignment and rounding errors.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "debug:error_limit", + .description = "limits the number of displayed config file parsing errors.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{5, 0, 20}, + }, + SConfigOptionDescription{ + .value = "debug:error_position", + .description = "sets the position of the error bar. top - 0, bottom - 1", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 1}, + }, + SConfigOptionDescription{ + .value = "debug:colored_stdout_logs", + .description = "enables colors in the stdout logs.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "debug:log_damage", + .description = "enables logging the damage.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "debug:pass", + .description = "enables render pass debugging.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "debug:full_cm_proto", + .description = "claims support for all cm proto features (requires restart)", + .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}, + }, + SConfigOptionDescription{ + .value = "debug:invalidate_fp16", + .description = "Allow fp16 buffer invalidation. 0 - disable, 1 - enabled, 2 - disable on nvidia", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{.value = 2, .min = 0, .max = 2}, + }, + + /* + * 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: + */ + + SConfigOptionDescription{ + .value = "dwindle:pseudotile", + .description = "enable pseudotiling. Pseudotiled windows retain their floating size when tiled.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "dwindle:force_split", + .description = "0 -> split follows mouse, 1 -> always split to the left (new = left or top) 2 -> always split to the right (new = right or bottom)", + .type = CONFIG_OPTION_CHOICE, + .data = SConfigOptionDescription::SChoiceData{0, "follow mouse,left or top,right or bottom"}, + }, + SConfigOptionDescription{ + .value = "dwindle:preserve_split", + .description = "if enabled, the split (side/top) will not change regardless of what happens to the container.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "dwindle:smart_split", + .description = "if enabled, allows a more precise control over the window split direction based on the cursor's position. The window is conceptually divided into four " + "triangles, and cursor's triangle determines the split direction. This feature also turns on preserve_split.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "dwindle:smart_resizing", + .description = "if enabled, resizing direction will be determined by the mouse's position on the window (nearest to which corner). Else, it is based on the window's " + "tiling position.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "dwindle:permanent_direction_override", + .description = + "if enabled, makes the preselect direction persist until either this mode is turned off, another direction is specified, or a non-direction is specified " + "(anything other than l,r,u/t,d/b)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "dwindle:special_scale_factor", + .description = "specifies the scale factor of windows on the special workspace [0 - 1]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 0, 1}, + }, + SConfigOptionDescription{ + .value = "dwindle:split_width_multiplier", + .description = "specifies the auto-split width multiplier", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 0.1, 3}, + }, + SConfigOptionDescription{ + .value = "dwindle:use_active_for_splits", + .description = "whether to prefer the active window or the mouse position for splits", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "dwindle:default_split_ratio", + .description = "the default split ratio on window open. 1 means even 50/50 split. [0.1 - 1.9]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 0.1, 1.9}, + }, + SConfigOptionDescription{ + .value = "dwindle:split_bias", + .description = "specifies which window will receive the split ratio. 0 -> directional (the top or left window), 1 -> the current window", + .type = CONFIG_OPTION_CHOICE, + .data = SConfigOptionDescription::SChoiceData{0, "directional,current"}, + }, + SConfigOptionDescription{ + .value = "dwindle:precise_mouse_move", + .description = "if enabled, bindm movewindow will drop the window more precisely depending on where your mouse is. This feature also turns on preserve_split.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + + /* + * master: + */ + + SConfigOptionDescription{ + .value = "master:allow_small_split", + .description = "enable adding additional master windows in a horizontal split style", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "master:special_scale_factor", + .description = "the scale of the special workspace windows. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 0, 1}, + }, + SConfigOptionDescription{ + .value = "master:mfact", + .description = + "the size as a percentage of the master window, for example `mfact = 0.70` would mean 70% of the screen will be the master window, and 30% the slave [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.55, 0, 1}, + }, + SConfigOptionDescription{ + .value = "master:new_status", + .description = "`master`: new window becomes master; `slave`: new windows are added to slave stack; `inherit`: inherit from focused window", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"slave"}, + }, + SConfigOptionDescription{ + .value = "master:new_on_top", + .description = "whether a newly open window should be on the top of the stack", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "master:new_on_active", + .description = "`before`, `after`: place new window relative to the focused window; `none`: place new window according to the value of `new_on_top`. ", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"none"}, + }, + SConfigOptionDescription{ + .value = "master:orientation", + .description = "default placement of the master area, can be left, right, top, bottom or center", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"left"}, + }, + SConfigOptionDescription{ + .value = "master:slave_count_for_center_master", + .description = "when using orientation=center, make the master window centered only when at least this many slave windows are open. (Set 0 to always_center_master)", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{2, 0, 10}, //##TODO RANGE? + }, + SConfigOptionDescription{.value = "master:center_master_fallback", + .description = "Set fallback for center master when slaves are less than slave_count_for_center_master, can be left ,right ,top ,bottom", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"left"}}, + SConfigOptionDescription{ + .value = "master:center_ignores_reserved", + .description = "centers the master window on monitor ignoring reserved areas", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "master:smart_resizing", + .description = "if enabled, resizing direction will be determined by the mouse's position on the window (nearest to which corner). Else, it is based on the window's " + "tiling position.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "master:drop_at_cursor", + .description = "when enabled, dragging and dropping windows will put them at the cursor position. Otherwise, when dropped at the stack side, they will go to the " + "top/bottom of the stack depending on new_on_top.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "master:always_keep_position", + .description = "whether to keep the master window in its configured position when there are no slave windows", + .type = CONFIG_OPTION_BOOL, + .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}, + }, + + /* + * Experimental + */ + + SConfigOptionDescription{ + .value = "experimental:wp_cm_1_2", + .description = "Allow wp-cm-v1 version 2", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + + /* + * Quirks + */ + + SConfigOptionDescription{ + .value = "quirks:prefer_hdr", + .description = "Prefer HDR mode. 0 - off, 1 - always, 2 - gamescope only", + .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}, + }, + + }; +} \ No newline at end of file diff --git a/src/config/supplementary/executor/Executor.cpp b/src/config/supplementary/executor/Executor.cpp new file mode 100644 index 000000000..8c04df819 --- /dev/null +++ b/src/config/supplementary/executor/Executor.cpp @@ -0,0 +1,186 @@ +#include "Executor.hpp" + +#include "../../../event/EventBus.hpp" +#include "../../../Compositor.hpp" +#include "../../../managers/input/InputManager.hpp" +#include "../../../desktop/rule/windowRule/WindowRule.hpp" +#include "../../../desktop/rule/Engine.hpp" +#include "../../../desktop/state/FocusState.hpp" +#include "../../../managers/TokenManager.hpp" +#include "../../../helpers/Monitor.hpp" + +#include +#include + +using namespace Config::Supplementary; +using namespace Hyprutils::String; + +UP& Config::Supplementary::executor() { + static UP p = makeUnique(); + return p; +} + +CExecutor::CExecutor() { + m_listeners.init = Event::bus()->m_events.start.listen([this] { + if (m_firstExecDispatched) + return; + + // update dbus env + if (g_pCompositor->m_aqBackend->hasSession()) + spawnRaw( +#ifdef USES_SYSTEMD + "systemctl --user import-environment DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP QT_QPA_PLATFORMTHEME PATH XDG_DATA_DIRS && hash " + "dbus-update-activation-environment 2>/dev/null && " +#endif + "dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP HYPRLAND_INSTANCE_SIGNATURE QT_QPA_PLATFORMTHEME PATH XDG_DATA_DIRS"); + + m_firstExecDispatched = true; + + for (auto const& c : m_execOnce) { + c.withRules ? spawn(c.exec) : spawnRaw(c.exec); + } + + m_execOnce.clear(); // free some kb of memory :P + + // set input, fixes some certain issues + g_pInputManager->setKeyboardLayout(); + g_pInputManager->setPointerConfigs(); + g_pInputManager->setTouchDeviceConfigs(); + g_pInputManager->setTabletConfigs(); + + // check for user's possible errors with their setup and notify them if needed + // this is additionally guarded because exiting safe mode will re-run this. + g_pCompositor->performUserChecks(); + + m_listeners.shutdown = Event::bus()->m_events.exit.listen([this] { + for (auto const& c : m_execShutdown) { + c.withRules ? spawn(c.exec) : spawnRaw(c.exec); + } + m_execShutdown.clear(); + }); + }); +} + +void CExecutor::addExecOnce(const SExecRequest& cmd) { + m_execOnce.emplace_back(cmd); +} + +void CExecutor::addExecShutdown(const SExecRequest& cmd) { + m_execShutdown.emplace_back(cmd); +} + +std::optional CExecutor::spawn(const std::string& args) { + return spawnWithRules(args); +} + +std::optional CExecutor::spawnRaw(const std::string& args) { + return spawnRawProc(args); +} + +std::optional CExecutor::spawnWithRules(std::string args, PHLWORKSPACE pInitialWorkspace) { + args = trim(args); + + std::string RULES = ""; + + 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); + } + + std::string execToken = ""; + + if (!RULES.empty()) { + auto rule = Desktop::Rule::CWindowRule::buildFromExecString(std::move(RULES)); + + const auto TOKEN = g_pTokenManager->registerNewToken(nullptr, std::chrono::seconds(1)); + + const auto PROC = spawnRawProc(args, pInitialWorkspace, TOKEN); + + if (!PROC) + return std::nullopt; + + 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, pid {}.", *PROC); + + return PROC; + } + + return spawnRawProc(args, pInitialWorkspace, execToken); +} + +static std::vector> getHyprlandLaunchEnv(PHLWORKSPACE pInitialWorkspace) { + static auto PINITIALWSTRACKING = CConfigValue("misc:initial_workspace_tracking"); + + if (!*PINITIALWSTRACKING) + return {}; + + const auto PMONITOR = Desktop::focusState()->monitor(); + if (!PMONITOR || !PMONITOR->m_activeWorkspace) + return {}; + + std::vector> result; + + if (!pInitialWorkspace) { + if (PMONITOR->m_activeSpecialWorkspace) + pInitialWorkspace = PMONITOR->m_activeSpecialWorkspace; + else + pInitialWorkspace = PMONITOR->m_activeWorkspace; + } + + result.push_back(std::make_pair<>("HL_INITIAL_WORKSPACE_TOKEN", + g_pTokenManager->registerNewToken(Desktop::View::SInitialWorkspaceToken{{}, pInitialWorkspace->getConfigName()}, std::chrono::months(1337)))); + + return result; +} + +std::optional CExecutor::spawnRawProc(const std::string& args, PHLWORKSPACE pInitialWorkspace, const std::string& execRuleToken) { + Log::logger->log(Log::DEBUG, "[executor] Executing {}", args); + + const auto HLENV = getHyprlandLaunchEnv(pInitialWorkspace); + + pid_t child = fork(); + if (child < 0) { + Log::logger->log(Log::DEBUG, "Fail to fork"); + return 0; + } + if (child == 0) { + // run in child + g_pCompositor->restoreNofile(); + + sigset_t set; + sigemptyset(&set); + sigprocmask(SIG_SETMASK, &set, nullptr); + + for (auto const& e : HLENV) { + setenv(e.first.c_str(), e.second.c_str(), 1); + } + setenv("WAYLAND_DISPLAY", g_pCompositor->m_wlDisplaySocket.c_str(), 1); + if (!execRuleToken.empty()) + setenv(Desktop::Rule::EXEC_RULE_ENV_NAME, execRuleToken.c_str(), true); + + int devnull = open("/dev/null", O_WRONLY | O_CLOEXEC); + if (devnull != -1) { + dup2(devnull, STDOUT_FILENO); + dup2(devnull, STDERR_FILENO); + close(devnull); + } + + execl("/bin/sh", "/bin/sh", "-c", args.c_str(), nullptr); + + // exit child + _exit(0); + } + // run in parent + + Log::logger->log(Log::DEBUG, "[executor] Process created with pid {}", child); + + return child; +} diff --git a/src/config/supplementary/executor/Executor.hpp b/src/config/supplementary/executor/Executor.hpp new file mode 100644 index 000000000..cc5f94a2a --- /dev/null +++ b/src/config/supplementary/executor/Executor.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include +#include +#include "../../../helpers/signal/Signal.hpp" +#include "../../../helpers/memory/Memory.hpp" +#include "../../../desktop/DesktopTypes.hpp" + +namespace Config::Supplementary { + struct SExecRequest { + std::string exec = ""; + bool withRules = false; + }; + + class CExecutor { + public: + CExecutor(); + ~CExecutor() = default; + + void addExecOnce(const SExecRequest& cmd); + void addExecShutdown(const SExecRequest& cmd); + + std::optional spawn(const std::string& args); + std::optional spawnRaw(const std::string& args); + + std::optional spawnRawProc(const std::string&, PHLWORKSPACE pInitialWorkspace = nullptr, const std::string& execRuleToken = ""); + std::optional spawnWithRules(std::string, PHLWORKSPACE pInitialWorkspace = nullptr); + + private: + std::vector m_execOnce, m_execShutdown; + + struct { + CHyprSignalListener init; + CHyprSignalListener shutdown; + } m_listeners; + + bool m_firstExecDispatched = false; + }; + + UP& executor(); +}; \ No newline at end of file diff --git a/src/config/supplementary/jeremy/Jeremy.cpp b/src/config/supplementary/jeremy/Jeremy.cpp new file mode 100644 index 000000000..5d3378208 --- /dev/null +++ b/src/config/supplementary/jeremy/Jeremy.cpp @@ -0,0 +1,39 @@ +#include "Jeremy.hpp" + +#include "../../../Compositor.hpp" + +#include +#include + +using namespace Config::Supplementary; + +std::expected Config::Supplementary::Jeremy::getMainConfigPath() { + static bool lastSafeMode = g_pCompositor->m_safeMode; + static auto getCfgPath = []() -> std::expected { + lastSafeMode = g_pCompositor->m_safeMode; + + if (g_pCompositor->m_safeMode) + return (std::filesystem::path{g_pCompositor->m_instancePath} / "recoverycfg.conf").string(); + + if (!g_pCompositor->m_explicitConfigPath.empty()) + return g_pCompositor->m_explicitConfigPath; + + if (const auto CFG_ENV = getenv("HYPRLAND_CONFIG"); CFG_ENV) + return CFG_ENV; + + const auto PATHS = Hyprutils::Path::findConfig(ISDEBUG ? "hyprlandd" : "hyprland"); + if (PATHS.first.has_value()) { + return PATHS.first.value(); + } else if (PATHS.second.has_value()) { + auto CONFIGPATH = Hyprutils::Path::fullConfigPath(PATHS.second.value(), ISDEBUG ? "hyprlandd" : "hyprland"); + return CONFIGPATH; + } else + return std::unexpected("Neither HOME nor XDG_CONFIG_HOME are set in the environment. Could not find config in XDG_CONFIG_DIRS or /etc/xdg."); + }; + static auto CONFIG_PATH = getCfgPath(); + + if (lastSafeMode != g_pCompositor->m_safeMode) + CONFIG_PATH = getCfgPath(); + + return CONFIG_PATH; +} diff --git a/src/config/supplementary/jeremy/Jeremy.hpp b/src/config/supplementary/jeremy/Jeremy.hpp new file mode 100644 index 000000000..cd77d727c --- /dev/null +++ b/src/config/supplementary/jeremy/Jeremy.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +namespace Config::Supplementary::Jeremy { + std::expected getMainConfigPath(); +}; \ No newline at end of file diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index dc5be6ce9..fd9cad75f 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -26,37 +26,52 @@ #include #include +#include #include using namespace Hyprutils::String; using namespace Hyprutils::OS; #include -#include "../config/ConfigDataValues.hpp" +#include "../config/shared/complex/ComplexDataTypes.hpp" +#include "../config/legacy/ConfigManager.hpp" #include "../config/ConfigValue.hpp" +#include "../config/shared/complex/ComplexDataTypes.hpp" +#include "../config/shared/inotify/ConfigWatcher.hpp" +#include "../config/shared/workspace/WorkspaceRuleManager.hpp" +#include "../config/shared/monitor/MonitorRuleManager.hpp" +#include "../config/shared/animation/AnimationTree.hpp" +#include "../config/supplementary/ConfigDescriptions.hpp" #include "../managers/CursorManager.hpp" -#include "../hyprerror/HyprError.hpp" +#include "../errorOverlay/Overlay.hpp" #include "../devices/IPointer.hpp" #include "../devices/IKeyboard.hpp" #include "../devices/ITouch.hpp" #include "../devices/Tablet.hpp" #include "../protocols/GlobalShortcuts.hpp" -#include "debug/RollingLogFollow.hpp" +#include "debug/log/RollingLogFollow.hpp" #include "config/ConfigManager.hpp" #include "helpers/MiscFunctions.hpp" -#include "../desktop/LayerSurface.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" #include "../version.h" #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 "../notification/NotificationOverlay.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" + +using namespace Render::GL; #if defined(__DragonFly__) || defined(__FreeBSD__) #include @@ -143,13 +158,13 @@ std::string CHyprCtl::getSolitaryBlockedReason(Hyprutils::Memory::CSharedPointer } const std::array DS_REASONS_JSON = { - "\"UNKNOWN\"", "\"USER\"", "\"WINDOWED\"", "\"CONTENT\"", "\"MIRROR\"", "\"RECORD\"", "\"SW\"", - "\"CANDIDATE\"", "\"SURFACE\"", "\"TRANSFORM\"", "\"DMA\"", "\"TEARING\"", "\"FAILED\"", "\"CM\"", + "\"UNKNOWN\"", "\"USER\"", "\"WINDOWED\"", "\"CONTENT\"", "\"MIRROR\"", "\"RECORD\"", "\"SW\"", + "\"CANDIDATE\"", "\"SURFACE\"", "\"TRANSFORM\"", "\"DMA\"", "\"FAILED\"", "\"CM\"", }; const std::array DS_REASONS_TEXT = { - "unknown reason", "user settings", "windowed mode", "content type", "monitor mirrors", "screen record/screenshot", "software renders/cursors", - "missing candidate", "invalid surface", "surface transformations", "invalid buffer", "tearing", "activation failed", "color management", + "unknown reason", "user settings", "windowed mode", "content type", "monitor mirrors", "screen record/screenshot", "software renders/cursors", + "missing candidate", "invalid surface", "surface transformations", "invalid buffer", "activation failed", "color management", }; std::string CHyprCtl::getDSBlockedReason(Hyprutils::Memory::CSharedPointer m, eHyprCtlOutputFormat format) { @@ -172,14 +187,13 @@ std::string CHyprCtl::getDSBlockedReason(Hyprutils::Memory::CSharedPointer TEARING_REASONS_JSON = { - "\"UNKNOWN\"", "\"NOT_TORN\"", "\"USER\"", "\"ZOOM\"", "\"SUPPORT\"", "\"CANDIDATE\"", "\"WINDOW\"", + "\"UNKNOWN\"", "\"NOT_TORN\"", "\"USER\"", "\"ZOOM\"", "\"SUPPORT\"", "\"CANDIDATE\"", "\"WINDOW\"", "\"HW_CURSOR\"", }; -const std::array TEARING_REASONS_TEXT = { - "unknown reason", "next frame is not torn", "user settings", "zoom", "not supported by monitor", "missing candidate", "window settings", -}; +const std::array TEARING_REASONS_TEXT = {"unknown reason", "next frame is not torn", "user settings", "zoom", + "not supported by monitor", "missing candidate", "window settings", "hw cursor"}; -std::string CHyprCtl::getTearingBlockedReason(Hyprutils::Memory::CSharedPointer m, eHyprCtlOutputFormat format) { +std::string CHyprCtl::getTearingBlockedReason(Hyprutils::Memory::CSharedPointer m, eHyprCtlOutputFormat format) { const auto reasons = m->isTearingBlocked(true); if (!reasons || (reasons == CMonitor::TC_NOT_TORN && m->m_tearingState.activelyTearing)) return "null"; @@ -248,20 +262,22 @@ std::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer "sdrBrightness": {:.2f}, "sdrSaturation": {:.2f}, "sdrMinLuminance": {:.2f}, - "sdrMaxLuminance": {} + "sdrMaxLuminance": {}, + "hardwareCursorsInUse": {} }},)#", m->m_id, escapeJSONStrings(m->m_name), escapeJSONStrings(m->m_shortDescription), escapeJSONStrings(m->m_output->make), escapeJSONStrings(m->m_output->model), escapeJSONStrings(m->m_output->serial), sc(m->m_pixelSize.x), sc(m->m_pixelSize.y), sc(m->m_output->physicalSize.x), sc(m->m_output->physicalSize.y), m->m_refreshRate, sc(m->m_position.x), sc(m->m_position.y), m->activeWorkspaceID(), (!m->m_activeWorkspace ? "" : escapeJSONStrings(m->m_activeWorkspace->m_name)), m->activeSpecialWorkspaceID(), - escapeJSONStrings(m->m_activeSpecialWorkspace ? m->m_activeSpecialWorkspace->m_name : ""), sc(m->m_reservedTopLeft.x), sc(m->m_reservedTopLeft.y), - sc(m->m_reservedBottomRight.x), sc(m->m_reservedBottomRight.y), m->m_scale, sc(m->m_transform), + escapeJSONStrings(m->m_activeSpecialWorkspace ? m->m_activeSpecialWorkspace->m_name : ""), sc(m->m_reservedArea.left()), sc(m->m_reservedArea.top()), + sc(m->m_reservedArea.right()), sc(m->m_reservedArea.bottom()), m->m_scale, sc(m->m_transform), (m == Desktop::focusState()->monitor() ? "true" : "false"), (m->m_dpmsStatus ? "true" : "false"), (m->m_output->state->state().adaptiveSync ? "true" : "false"), rc(m->m_solitaryClient.get()), getSolitaryBlockedReason(m, format), (m->m_tearingState.activelyTearing ? "true" : "false"), getTearingBlockedReason(m, format), rc(m->m_lastScanout.get()), getDSBlockedReason(m, format), (m->m_enabled ? "false" : "true"), formatToString(m->m_output->state->state().drmFormat), m->m_mirrorOf ? std::format("{}", m->m_mirrorOf->m_id) : "none", availableModesForOutput(m, format), - (NCMType::toString(m->m_cmType)), (m->m_sdrBrightness), (m->m_sdrSaturation), (m->m_sdrMinLuminance), (m->m_sdrMaxLuminance)); + (NCMType::toString(m->m_cmType)), (m->m_sdrBrightness), (m->m_sdrSaturation), (m->m_sdrMinLuminance), (m->m_sdrMaxLuminance), + (!m->shouldUseSoftwareCursors() ? "true" : "false")); } else { result += std::format( @@ -270,16 +286,17 @@ std::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer "dpmsStatus: {}\n\tvrr: {}\n\tsolitary: {:x}\n\tsolitaryBlockedBy: {}\n\tactivelyTearing: {}\n\ttearingBlockedBy: {}\n\tdirectScanoutTo: " "{:x}\n\tdirectScanoutBlockedBy: {}\n\tdisabled: " "{}\n\tcurrentFormat: {}\n\tmirrorOf: " - "{}\n\tavailableModes: {}\n\tcolorManagementPreset: {}\n\tsdrBrightness: {:.2f}\n\tsdrSaturation: {:.2f}\n\tsdrMinLuminance: {:.2f}\n\tsdrMaxLuminance: {}\n\n", + "{}\n\tavailableModes: {}\n\tcolorManagementPreset: {}\n\tsdrBrightness: {:.2f}\n\tsdrSaturation: {:.2f}\n\tsdrMinLuminance: {:.2f}\n\tsdrMaxLuminance: " + "{}\n\thardwareCursorsInUse: {}\n\n", m->m_name, m->m_id, sc(m->m_pixelSize.x), sc(m->m_pixelSize.y), m->m_refreshRate, sc(m->m_position.x), sc(m->m_position.y), m->m_shortDescription, m->m_output->make, m->m_output->model, sc(m->m_output->physicalSize.x), sc(m->m_output->physicalSize.y), m->m_output->serial, m->activeWorkspaceID(), (!m->m_activeWorkspace ? "" : m->m_activeWorkspace->m_name), m->activeSpecialWorkspaceID(), (m->m_activeSpecialWorkspace ? m->m_activeSpecialWorkspace->m_name : ""), - sc(m->m_reservedTopLeft.x), sc(m->m_reservedTopLeft.y), sc(m->m_reservedBottomRight.x), sc(m->m_reservedBottomRight.y), m->m_scale, + sc(m->m_reservedArea.left()), sc(m->m_reservedArea.top()), sc(m->m_reservedArea.right()), sc(m->m_reservedArea.bottom()), m->m_scale, sc(m->m_transform), (m == Desktop::focusState()->monitor() ? "yes" : "no"), sc(m->m_dpmsStatus), m->m_output->state->state().adaptiveSync, rc(m->m_solitaryClient.get()), getSolitaryBlockedReason(m, format), m->m_tearingState.activelyTearing, getTearingBlockedReason(m, format), rc(m->m_lastScanout.get()), getDSBlockedReason(m, format), !m->m_enabled, formatToString(m->m_output->state->state().drmFormat), m->m_mirrorOf ? std::format("{}", m->m_mirrorOf->m_id) : "none", availableModesForOutput(m, format), (NCMType::toString(m->m_cmType)), (m->m_sdrBrightness), - (m->m_sdrSaturation), (m->m_sdrMinLuminance), (m->m_sdrMaxLuminance)); + (m->m_sdrSaturation), (m->m_sdrMinLuminance), (m->m_sdrMaxLuminance), (!m->shouldUseSoftwareCursors())); } return result; @@ -330,23 +347,19 @@ static std::string getTagsData(PHLWINDOW w, eHyprCtlOutputFormat format) { static std::string getGroupedData(PHLWINDOW w, eHyprCtlOutputFormat format) { const bool isJson = format == eHyprCtlOutputFormat::FORMAT_JSON; - if (w->m_groupData.pNextWindow.expired()) + if (!w->m_group) return isJson ? "" : "0"; std::ostringstream result; - PHLWINDOW head = w->getGroupHead(); - PHLWINDOW curr = head; - while (true) { + for (const auto& curr : w->m_group->windows()) { if (isJson) result << std::format("\"0x{:x}\"", rc(curr.get())); else result << std::format("{:x}", rc(curr.get())); - curr = curr->m_groupData.pNextWindow.lock(); - // We've wrapped around to the start, break out without trailing comma - if (curr == head) - break; - result << (isJson ? ", " : ","); + + if (curr != w->m_group->windows().back()) + result << (isJson ? ", " : ","); } return result.str(); @@ -354,9 +367,10 @@ static std::string getGroupedData(PHLWINDOW w, eHyprCtlOutputFormat format) { std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) { auto getFocusHistoryID = [](PHLWINDOW wnd) -> int { - for (size_t i = 0; i < Desktop::focusState()->windowHistory().size(); ++i) { - if (Desktop::focusState()->windowHistory()[i].lock() == wnd) - return i; + const auto& HISTORY = Desktop::History::windowTracker()->fullHistory(); + for (size_t i = 0; i < HISTORY.size(); ++i) { + if (HISTORY[i].lock() == wnd) + return HISTORY.size() - i - 1; // reverse order for backwards compat } return -1; }; @@ -374,7 +388,6 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) { "name": "{}" }}, "floating": {}, - "pseudo": {}, "monitor": {}, "class": "{}", "title": "{}", @@ -385,6 +398,7 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) { "pinned": {}, "fullscreen": {}, "fullscreenClient": {}, + "overFullscreen": {}, "grouped": [{}], "tags": [{}], "swallowing": "0x{:x}", @@ -392,29 +406,31 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) { "inhibitingIdle": {}, "xdgTag": "{}", "xdgDescription": "{}", - "contentType": "{}" + "contentType": "{}", + "stableId": "{:x}" }},)#", rc(w.get()), (w->m_isMapped ? "true" : "false"), (w->isHidden() ? "true" : "false"), sc(w->m_realPosition->goal().x), sc(w->m_realPosition->goal().y), sc(w->m_realSize->goal().x), sc(w->m_realSize->goal().y), w->m_workspace ? w->workspaceID() : WORKSPACE_INVALID, - escapeJSONStrings(!w->m_workspace ? "" : w->m_workspace->m_name), (sc(w->m_isFloating) == 1 ? "true" : "false"), (w->m_isPseudotiled ? "true" : "false"), - w->monitorID(), escapeJSONStrings(w->m_class), escapeJSONStrings(w->m_title), escapeJSONStrings(w->m_initialClass), escapeJSONStrings(w->m_initialTitle), w->getPID(), - (sc(w->m_isX11) == 1 ? "true" : "false"), (w->m_pinned ? "true" : "false"), sc(w->m_fullscreenState.internal), sc(w->m_fullscreenState.client), + escapeJSONStrings(!w->m_workspace ? "" : w->m_workspace->m_name), (sc(w->m_isFloating) == 1 ? "true" : "false"), w->monitorID(), escapeJSONStrings(w->m_class), + escapeJSONStrings(w->m_title), escapeJSONStrings(w->m_initialClass), escapeJSONStrings(w->m_initialTitle), w->getPID(), (sc(w->m_isX11) == 1 ? "true" : "false"), + (w->m_pinned ? "true" : "false"), sc(w->m_fullscreenState.internal), sc(w->m_fullscreenState.client), (w->m_createdOverFullscreen ? "true" : "false"), getGroupedData(w, format), getTagsData(w, format), rc(w->m_swallowed.get()), getFocusHistoryID(w), (g_pInputManager->isWindowInhibiting(w, false) ? "true" : "false"), escapeJSONStrings(w->xdgTag().value_or("")), escapeJSONStrings(w->xdgDescription().value_or("")), - escapeJSONStrings(NContentType::toString(w->getContentType()))); + escapeJSONStrings(NContentType::toString(w->getContentType())), w->m_stableID); } else { return std::format( - "Window {:x} -> {}:\n\tmapped: {}\n\thidden: {}\n\tat: {},{}\n\tsize: {},{}\n\tworkspace: {} ({})\n\tfloating: {}\n\tpseudo: {}\n\tmonitor: {}\n\tclass: {}\n\ttitle: " + "Window {:x} -> {}:\n\tmapped: {}\n\thidden: {}\n\tat: {},{}\n\tsize: {},{}\n\tworkspace: {} ({})\n\tfloating: {}\n\tmonitor: {}\n\tclass: {}\n\ttitle: " "{}\n\tinitialClass: {}\n\tinitialTitle: {}\n\tpid: " "{}\n\txwayland: {}\n\tpinned: " - "{}\n\tfullscreen: {}\n\tfullscreenClient: {}\n\tgrouped: {}\n\ttags: {}\n\tswallowing: {:x}\n\tfocusHistoryID: {}\n\tinhibitingIdle: {}\n\txdgTag: " - "{}\n\txdgDescription: {}\n\tcontentType: {}\n\n", + "{}\n\tfullscreen: {}\n\tfullscreenClient: {}\n\toverFullscreen: {}\n\tgrouped: {}\n\ttags: {}\n\tswallowing: {:x}\n\tfocusHistoryID: {}\n\tinhibitingIdle: " + "{}\n\txdgTag: " + "{}\n\txdgDescription: {}\n\tcontentType: {}\n\tstableID: {:x}\n\n", rc(w.get()), w->m_title, sc(w->m_isMapped), sc(w->isHidden()), sc(w->m_realPosition->goal().x), sc(w->m_realPosition->goal().y), sc(w->m_realSize->goal().x), sc(w->m_realSize->goal().y), w->m_workspace ? w->workspaceID() : WORKSPACE_INVALID, - (!w->m_workspace ? "" : w->m_workspace->m_name), sc(w->m_isFloating), sc(w->m_isPseudotiled), w->monitorID(), w->m_class, w->m_title, w->m_initialClass, - w->m_initialTitle, w->getPID(), sc(w->m_isX11), sc(w->m_pinned), sc(w->m_fullscreenState.internal), sc(w->m_fullscreenState.client), + (!w->m_workspace ? "" : w->m_workspace->m_name), sc(w->m_isFloating), w->monitorID(), w->m_class, w->m_title, w->m_initialClass, w->m_initialTitle, w->getPID(), + sc(w->m_isX11), sc(w->m_pinned), sc(w->m_fullscreenState.internal), sc(w->m_fullscreenState.client), sc(w->m_createdOverFullscreen), getGroupedData(w, format), getTagsData(w, format), rc(w->m_swallowed.get()), getFocusHistoryID(w), sc(g_pInputManager->isWindowInhibiting(w, false)), - w->xdgTag().value_or(""), w->xdgDescription().value_or(""), NContentType::toString(w->getContentType())); + w->xdgTag().value_or(""), w->xdgDescription().value_or(""), NContentType::toString(w->getContentType()), w->m_stableID); } } @@ -448,8 +464,15 @@ static std::string clientsRequest(eHyprCtlOutputFormat format, std::string reque } std::string CHyprCtl::getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat format) { - const auto PLASTW = w->getLastFocusedWindow(); - const auto PMONITOR = w->m_monitor.lock(); + const auto PLASTW = w->getLastFocusedWindow(); + const auto PMONITOR = w->m_monitor.lock(); + + std::string layoutName = "unknown"; + if (w->m_space && w->m_space->algorithm() && w->m_space->algorithm()->tiledAlgo()) { + const auto& TILED_ALGO = w->m_space->algorithm()->tiledAlgo(); + layoutName = Layout::Supplementary::algoMatcher()->getNameForTiledAlgo(&typeid(*TILED_ALGO.get())); + } + if (format == eHyprCtlOutputFormat::FORMAT_JSON) { return std::format(R"#({{ "id": {}, @@ -460,65 +483,67 @@ std::string CHyprCtl::getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat form "hasfullscreen": {}, "lastwindow": "0x{:x}", "lastwindowtitle": "{}", - "ispersistent": {} + "ispersistent": {}, + "tiledLayout": "{}" }})#", w->m_id, escapeJSONStrings(w->m_name), escapeJSONStrings(PMONITOR ? PMONITOR->m_name : "?"), escapeJSONStrings(PMONITOR ? std::to_string(PMONITOR->m_id) : "null"), w->getWindows(), w->m_hasFullscreenWindow ? "true" : "false", - rc(PLASTW.get()), PLASTW ? escapeJSONStrings(PLASTW->m_title) : "", w->isPersistent() ? "true" : "false"); + rc(PLASTW.get()), PLASTW ? escapeJSONStrings(PLASTW->m_title) : "", w->isPersistent() ? "true" : "false", escapeJSONStrings(layoutName)); } else { - return std::format( - "workspace ID {} ({}) on monitor {}:\n\tmonitorID: {}\n\twindows: {}\n\thasfullscreen: {}\n\tlastwindow: 0x{:x}\n\tlastwindowtitle: {}\n\tispersistent: {}\n\n", - w->m_id, w->m_name, PMONITOR ? PMONITOR->m_name : "?", PMONITOR ? std::to_string(PMONITOR->m_id) : "null", w->getWindows(), sc(w->m_hasFullscreenWindow), - rc(PLASTW.get()), PLASTW ? PLASTW->m_title : "", sc(w->isPersistent())); + return std::format("workspace ID {} ({}) on monitor {}:\n\tmonitorID: {}\n\twindows: {}\n\thasfullscreen: {}\n\tlastwindow: 0x{:x}\n\tlastwindowtitle: {}\n\tispersistent: " + "{}\n\ttiledLayout: {}\n\n", + w->m_id, w->m_name, PMONITOR ? PMONITOR->m_name : "?", PMONITOR ? std::to_string(PMONITOR->m_id) : "null", w->getWindows(), + sc(w->m_hasFullscreenWindow), rc(PLASTW.get()), PLASTW ? PLASTW->m_title : "", sc(w->isPersistent()), layoutName); } } -static std::string getWorkspaceRuleData(const SWorkspaceRule& r, eHyprCtlOutputFormat format) { +static std::string getWorkspaceRuleData(const Config::CWorkspaceRule& r, eHyprCtlOutputFormat format) { const auto boolToString = [](const bool b) -> std::string { return b ? "true" : "false"; }; if (format == eHyprCtlOutputFormat::FORMAT_JSON) { - const std::string monitor = r.monitor.empty() ? "" : std::format(",\n \"monitor\": \"{}\"", escapeJSONStrings(r.monitor)); - const std::string default_ = sc(r.isDefault) ? std::format(",\n \"default\": {}", boolToString(r.isDefault)) : ""; - const std::string persistent = sc(r.isPersistent) ? std::format(",\n \"persistent\": {}", boolToString(r.isPersistent)) : ""; - const std::string gapsIn = sc(r.gapsIn) ? - std::format(",\n \"gapsIn\": [{}, {}, {}, {}]", r.gapsIn.value().m_top, r.gapsIn.value().m_right, r.gapsIn.value().m_bottom, r.gapsIn.value().m_left) : - ""; - const std::string gapsOut = sc(r.gapsOut) ? - std::format(",\n \"gapsOut\": [{}, {}, {}, {}]", r.gapsOut.value().m_top, r.gapsOut.value().m_right, r.gapsOut.value().m_bottom, r.gapsOut.value().m_left) : - ""; - const std::string borderSize = sc(r.borderSize) ? std::format(",\n \"borderSize\": {}", r.borderSize.value()) : ""; - const std::string border = sc(r.noBorder) ? std::format(",\n \"border\": {}", boolToString(!r.noBorder.value())) : ""; - const std::string rounding = sc(r.noRounding) ? std::format(",\n \"rounding\": {}", boolToString(!r.noRounding.value())) : ""; - const std::string decorate = sc(r.decorate) ? std::format(",\n \"decorate\": {}", boolToString(r.decorate.value())) : ""; - const std::string shadow = sc(r.noShadow) ? std::format(",\n \"shadow\": {}", boolToString(!r.noShadow.value())) : ""; - const std::string defaultName = r.defaultName.has_value() ? std::format(",\n \"defaultName\": \"{}\"", escapeJSONStrings(r.defaultName.value())) : ""; + const std::string monitor = r.m_monitor.empty() ? "" : std::format(",\n \"monitor\": \"{}\"", escapeJSONStrings(r.m_monitor)); + const std::string default_ = sc(r.m_isDefault) ? std::format(",\n \"default\": {}", boolToString(r.m_isDefault)) : ""; + const std::string persistent = sc(r.m_isPersistent) ? std::format(",\n \"persistent\": {}", boolToString(r.m_isPersistent)) : ""; + const std::string gapsIn = sc(r.m_gapsIn) ? + std::format(",\n \"gapsIn\": [{}, {}, {}, {}]", r.m_gapsIn.value().m_top, r.m_gapsIn.value().m_right, r.m_gapsIn.value().m_bottom, r.m_gapsIn.value().m_left) : + ""; + const std::string gapsOut = sc(r.m_gapsOut) ? + std::format(",\n \"gapsOut\": [{}, {}, {}, {}]", r.m_gapsOut.value().m_top, r.m_gapsOut.value().m_right, r.m_gapsOut.value().m_bottom, r.m_gapsOut.value().m_left) : + ""; + const std::string borderSize = sc(r.m_borderSize) ? std::format(",\n \"borderSize\": {}", r.m_borderSize.value()) : ""; + const std::string border = sc(r.m_noBorder) ? std::format(",\n \"border\": {}", boolToString(!r.m_noBorder.value())) : ""; + const std::string rounding = sc(r.m_noRounding) ? std::format(",\n \"rounding\": {}", boolToString(!r.m_noRounding.value())) : ""; + const std::string decorate = sc(r.m_decorate) ? std::format(",\n \"decorate\": {}", boolToString(r.m_decorate.value())) : ""; + const std::string shadow = sc(r.m_noShadow) ? std::format(",\n \"shadow\": {}", boolToString(!r.m_noShadow.value())) : ""; + const std::string defaultName = r.m_defaultName.has_value() ? std::format(",\n \"defaultName\": \"{}\"", escapeJSONStrings(r.m_defaultName.value())) : ""; std::string result = std::format(R"#({{ "workspaceString": "{}"{}{}{}{}{}{}{}{}{}{}{} }})#", - escapeJSONStrings(r.workspaceString), monitor, default_, persistent, gapsIn, gapsOut, borderSize, border, rounding, decorate, shadow, defaultName); + escapeJSONStrings(r.m_workspaceString), monitor, default_, persistent, gapsIn, gapsOut, borderSize, border, rounding, decorate, shadow, defaultName); return result; } else { - const std::string monitor = std::format("\tmonitor: {}\n", r.monitor.empty() ? "" : escapeJSONStrings(r.monitor)); - const std::string default_ = std::format("\tdefault: {}\n", sc(r.isDefault) ? boolToString(r.isDefault) : ""); - const std::string persistent = std::format("\tpersistent: {}\n", sc(r.isPersistent) ? boolToString(r.isPersistent) : ""); - const std::string gapsIn = sc(r.gapsIn) ? std::format("\tgapsIn: {} {} {} {}\n", std::to_string(r.gapsIn.value().m_top), std::to_string(r.gapsIn.value().m_right), - std::to_string(r.gapsIn.value().m_bottom), std::to_string(r.gapsIn.value().m_left)) : - std::format("\tgapsIn: \n"); - const std::string gapsOut = sc(r.gapsOut) ? - std::format("\tgapsOut: {} {} {} {}\n", std::to_string(r.gapsOut.value().m_top), std::to_string(r.gapsOut.value().m_right), std::to_string(r.gapsOut.value().m_bottom), - std::to_string(r.gapsOut.value().m_left)) : - std::format("\tgapsOut: \n"); - const std::string borderSize = std::format("\tborderSize: {}\n", sc(r.borderSize) ? std::to_string(r.borderSize.value()) : ""); - const std::string border = std::format("\tborder: {}\n", sc(r.noBorder) ? boolToString(!r.noBorder.value()) : ""); - const std::string rounding = std::format("\trounding: {}\n", sc(r.noRounding) ? boolToString(!r.noRounding.value()) : ""); - const std::string decorate = std::format("\tdecorate: {}\n", sc(r.decorate) ? boolToString(r.decorate.value()) : ""); - const std::string shadow = std::format("\tshadow: {}\n", sc(r.noShadow) ? boolToString(!r.noShadow.value()) : ""); - const std::string defaultName = std::format("\tdefaultName: {}\n", r.defaultName.value_or("")); + const std::string monitor = std::format("\tmonitor: {}\n", r.m_monitor.empty() ? "" : escapeJSONStrings(r.m_monitor)); + const std::string default_ = std::format("\tdefault: {}\n", sc(r.m_isDefault) ? boolToString(r.m_isDefault) : ""); + const std::string persistent = std::format("\tpersistent: {}\n", sc(r.m_isPersistent) ? boolToString(r.m_isPersistent) : ""); + const std::string gapsIn = sc(r.m_gapsIn) ? + std::format("\tgapsIn: {} {} {} {}\n", std::to_string(r.m_gapsIn.value().m_top), std::to_string(r.m_gapsIn.value().m_right), + std::to_string(r.m_gapsIn.value().m_bottom), std::to_string(r.m_gapsIn.value().m_left)) : + std::format("\tgapsIn: \n"); + const std::string gapsOut = sc(r.m_gapsOut) ? + std::format("\tgapsOut: {} {} {} {}\n", std::to_string(r.m_gapsOut.value().m_top), std::to_string(r.m_gapsOut.value().m_right), + std::to_string(r.m_gapsOut.value().m_bottom), std::to_string(r.m_gapsOut.value().m_left)) : + std::format("\tgapsOut: \n"); + const std::string borderSize = std::format("\tborderSize: {}\n", sc(r.m_borderSize) ? std::to_string(r.m_borderSize.value()) : ""); + const std::string border = std::format("\tborder: {}\n", sc(r.m_noBorder) ? boolToString(!r.m_noBorder.value()) : ""); + const std::string rounding = std::format("\trounding: {}\n", sc(r.m_noRounding) ? boolToString(!r.m_noRounding.value()) : ""); + const std::string decorate = std::format("\tdecorate: {}\n", sc(r.m_decorate) ? boolToString(r.m_decorate.value()) : ""); + const std::string shadow = std::format("\tshadow: {}\n", sc(r.m_noShadow) ? boolToString(!r.m_noShadow.value()) : ""); + const std::string defaultName = std::format("\tdefaultName: {}\n", r.m_defaultName.value_or("")); - std::string result = std::format("Workspace rule {}:\n{}{}{}{}{}{}{}{}{}{}{}\n", escapeJSONStrings(r.workspaceString), monitor, default_, persistent, gapsIn, gapsOut, - borderSize, border, rounding, decorate, shadow, defaultName); + std::string result = std::format("Workspace rule {}:\n{}{}{}{}{}{}{}{}{}{}{}\n", escapeJSONStrings(r.m_workspaceString), monitor, default_, persistent, gapsIn, gapsOut, + borderSize, border, rounding, decorate, shadow, defaultName); return result; } @@ -562,7 +587,7 @@ static std::string workspaceRulesRequest(eHyprCtlOutputFormat format, std::strin std::string result = ""; if (format == eHyprCtlOutputFormat::FORMAT_JSON) { result += "["; - for (auto const& r : g_pConfigManager->getAllWorkspaceRules()) { + for (auto const& r : Config::workspaceRuleMgr()->getAllWorkspaceRules()) { result += getWorkspaceRuleData(r, format); result += ","; } @@ -570,7 +595,7 @@ static std::string workspaceRulesRequest(eHyprCtlOutputFormat format, std::strin trimTrailingComma(result); result += "]"; } else { - for (auto const& r : g_pConfigManager->getAllWorkspaceRules()) { + for (auto const& r : Config::workspaceRuleMgr()->getAllWorkspaceRules()) { result += getWorkspaceRuleData(r, format); } } @@ -668,31 +693,9 @@ 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(); + std::string currErrors = Config::mgr()->getErrors(); CVarList errLines(currErrors, 0, '\n'); if (format == eHyprCtlOutputFormat::FORMAT_JSON) { result += "["; @@ -800,7 +803,7 @@ static std::string devicesRequest(eHyprCtlOutputFormat format, std::string reque result += std::format( R"#( {{ "address": "0x{:x}", - "type": "tabletTool", + "type": "tabletTool" }},)#", rc(d.get())); } @@ -897,7 +900,7 @@ static std::string animationsRequest(eHyprCtlOutputFormat format, std::string re if (format == eHyprCtlOutputFormat::FORMAT_NORMAL) { ret += "animations:\n"; - for (auto const& ac : g_pConfigManager->getAnimationConfig()) { + for (auto const& ac : Config::animationTree()->getAnimationConfig()) { ret += std::format("\n\tname: {}\n\t\toverriden: {}\n\t\tbezier: {}\n\t\tenabled: {}\n\t\tspeed: {:.2f}\n\t\tstyle: {}\n", ac.first, sc(ac.second->overridden), ac.second->internalBezier, ac.second->internalEnabled, ac.second->internalSpeed, ac.second->internalStyle); } @@ -913,7 +916,7 @@ static std::string animationsRequest(eHyprCtlOutputFormat format, std::string re // json ret += "[["; - for (auto const& ac : g_pConfigManager->getAnimationConfig()) { + for (auto const& ac : Config::animationTree()->getAnimationConfig()) { ret += std::format(R"#( {{ "name": "{}", @@ -957,11 +960,10 @@ static std::string rollinglogRequest(eHyprCtlOutputFormat format, std::string re if (format == eHyprCtlOutputFormat::FORMAT_JSON) { result += "[\n\"log\":\""; - result += escapeJSONStrings(Debug::m_rollingLog); + result += escapeJSONStrings(Log::logger->rolling()); result += "\"]"; - } else { - result = Debug::m_rollingLog; - } + } else + result = Log::logger->rolling(); return result; } @@ -1063,8 +1065,11 @@ std::string versionRequest(eHyprCtlOutputFormat format, std::string request) { result += "\n"; result += getBuiltSystemLibraryNames(); result += "\n"; + result += "Version ABI string: "; + result += __hyprland_api_get_hash(); + result += "\n"; -#if (!ISDEBUG && !defined(NO_XWAYLAND)) +#if (!ISDEBUG && !defined(NO_XWAYLAND) && !defined(BUILT_WITH_NIX)) result += "no flags were set\n"; #else result += "flags set:\n"; @@ -1074,6 +1079,9 @@ std::string versionRequest(eHyprCtlOutputFormat format, std::string request) { #ifdef NO_XWAYLAND result += "no xwayland\n"; #endif +#ifdef BUILT_WITH_NIX + result += "nix\n"; +#endif #endif return result; } else { @@ -1097,10 +1105,12 @@ std::string versionRequest(eHyprCtlOutputFormat format, std::string request) { "systemHyprutils": "{}", "systemHyprcursor": "{}", "systemHyprgraphics": "{}", + "abiHash": "{}", "flags": [)#", GIT_BRANCH, GIT_COMMIT_HASH, HYPRLAND_VERSION, (strcmp(GIT_DIRTY, "dirty") == 0 ? "true" : "false"), escapeJSONStrings(commitMsg), GIT_COMMIT_DATE, GIT_TAG, GIT_COMMITS, AQUAMARINE_VERSION, HYPRLANG_VERSION, HYPRUTILS_VERSION, HYPRCURSOR_VERSION, HYPRGRAPHICS_VERSION, getSystemLibraryVersion("aquamarine"), - getSystemLibraryVersion("hyprlang"), getSystemLibraryVersion("hyprutils"), getSystemLibraryVersion("hyprcursor"), getSystemLibraryVersion("hyprgraphics")); + getSystemLibraryVersion("hyprlang"), getSystemLibraryVersion("hyprutils"), getSystemLibraryVersion("hyprcursor"), getSystemLibraryVersion("hyprgraphics"), + __hyprland_api_get_hash()); #if ISDEBUG result += "\"debug\","; @@ -1108,6 +1118,9 @@ std::string versionRequest(eHyprCtlOutputFormat format, std::string request) { #ifdef NO_XWAYLAND result += "\"no xwayland\","; #endif +#ifdef BUILT_WITH_NIX + result += "\"nix\","; +#endif trimTrailingComma(result); @@ -1226,7 +1239,7 @@ std::string systemInfoRequest(eHyprCtlOutputFormat format, std::string request) if (g_pHyprCtl && g_pHyprCtl->m_currentRequestParams.sysInfoConfig) { result += "\n======Config-Start======\n"; - result += g_pConfigManager->getConfigString(); + result += Config::mgr()->getConfigString(); result += "\n======Config-End========\n"; } @@ -1249,12 +1262,18 @@ static std::string dispatchRequest(eHyprCtlOutputFormat format, std::string in) SDispatchResult res = DISPATCHER->second(DISPATCHARG); - Debug::log(LOG, "Hyprctl: dispatcher {} : {}{}", DISPATCHSTR, DISPATCHARG, res.success ? "" : " -> " + res.error); + Log::logger->log(Log::DEBUG, "Hyprctl: dispatcher {} : {}{}", DISPATCHSTR, DISPATCHARG, res.success ? "" : " -> " + res.error); return res.success ? "ok" : res.error; } static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in) { + + if (Config::mgr()->type() != Config::CONFIG_LEGACY) + return "keyword can't work with non-legacy parsers. Use eval."; + + WP mgr = dynamicPointerCast(WP(Config::mgr())); + // Find the first space to strip the keyword keyword auto const firstSpacePos = in.find_first_of(' '); if (firstSpacePos == std::string::npos) // Handle the case where there's no space found (invalid input) @@ -1278,16 +1297,18 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in) g_pHyprCtl->m_currentRequestParams.isDynamicKeyword = true; - std::string retval = g_pConfigManager->parseKeyword(COMMAND, VALUE); + std::string retval = mgr->parseKeyword(COMMAND, VALUE); g_pHyprCtl->m_currentRequestParams.isDynamicKeyword = false; - // if we are executing a dynamic source we have to reload everything, so every if will have a check for source. - if (COMMAND == "monitor" || COMMAND == "source") - g_pConfigManager->m_wantsMonitorReload = true; // for monitor keywords + if (COMMAND == "source") { + Config::monitorRuleMgr()->scheduleReload(); + g_pEventLoopManager->doLater([mgr] { mgr->reloadRules(); }); + } - if (COMMAND.contains("monitorv2")) - g_pEventLoopManager->doLater([] { g_pConfigManager->m_wantsMonitorReload = true; }); + // if we are executing a dynamic source we have to reload everything, so every if will have a check for source. + if (COMMAND.contains("monitor")) + g_pEventLoopManager->doLater([] { Config::monitorRuleMgr()->scheduleReload(); }); if (COMMAND.contains("input") || COMMAND.contains("device") || COMMAND == "source") { g_pInputManager->setKeyboardLayout(); // update kb layout @@ -1296,22 +1317,21 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in) g_pInputManager->setTabletConfigs(); // update tablets } - static auto PLAYOUT = CConfigValue("general:layout"); - - if (COMMAND.contains("general:layout")) - g_pLayoutManager->switchToLayout(*PLAYOUT); // update layout + if (COMMAND.contains("general:layout") || (COMMAND.contains("workspace") && VALUE.contains("layout:"))) + Layout::Supplementary::algoMatcher()->updateWorkspaceLayouts(); if (COMMAND.contains("decoration:screen_shader") || COMMAND == "source") - g_pHyprOpenGL->m_reloadScreenShader = true; + g_pHyprRenderer->m_reloadScreenShader = true; if (COMMAND.contains("blur") || COMMAND == "source") { - for (auto& [m, rd] : g_pHyprOpenGL->m_monitorRenderResources) { - rd.blurFBDirty = true; + for (auto const& m : g_pCompositor->m_monitors) { + if (m) + m->m_blurFBDirty = true; } } if (COMMAND.contains("misc:disable_autoreload")) - g_pConfigManager->updateWatcher(); + Config::watcher()->update(); // decorations will probably need a repaint if (COMMAND.contains("decoration:") || COMMAND.contains("border") || COMMAND == "workspace" || COMMAND.contains("zoom_factor") || COMMAND == "source") { @@ -1319,17 +1339,26 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in) for (auto const& m : g_pCompositor->m_monitors) { *(m->m_cursorZoom) = *PZOOMFACTOR; g_pHyprRenderer->damageMonitor(m); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id); + if (m->m_activeWorkspace) + m->m_activeWorkspace->m_space->recalculate(); } } if (COMMAND.contains("windowrule ") || COMMAND.contains("windowrule[")) - g_pConfigManager->reloadRules(); + mgr->reloadRules(); + + if (COMMAND.contains("layerrule") || COMMAND.contains("layerrule[")) { + mgr->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(); + g_pCompositor->ensurePersistentWorkspacesPresent(); - Debug::log(LOG, "Hyprctl: keyword {} : {}", COMMAND, VALUE); + Log::logger->log(Log::DEBUG, "Hyprctl: keyword {} : {}", COMMAND, VALUE); if (retval.empty()) return "ok"; @@ -1338,13 +1367,7 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in) } static std::string reloadRequest(eHyprCtlOutputFormat format, std::string request) { - - const auto REQMODE = request.substr(request.find_last_of(' ') + 1); - - if (REQMODE == "config-only") - g_pConfigManager->m_noMonitorReload = true; - - g_pConfigManager->reload(); + Config::mgr()->reload(); return "ok"; } @@ -1508,7 +1531,7 @@ static std::string dispatchSeterror(eHyprCtlOutputFormat format, std::string req std::string errorMessage = ""; if (vars.size() < 3) { - g_pHyprError->destroy(); + ErrorOverlay::overlay()->destroy(); if (vars.size() == 2 && !vars[1].contains("dis")) return "var 1 not color or disable"; @@ -1522,10 +1545,10 @@ static std::string dispatchSeterror(eHyprCtlOutputFormat format, std::string req errorMessage += vars[i] + ' '; if (errorMessage.empty()) { - g_pHyprError->destroy(); + ErrorOverlay::overlay()->destroy(); } else { errorMessage.pop_back(); // pop last space - g_pHyprError->queueCreate(errorMessage, COLOR); + ErrorOverlay::overlay()->queueCreate(errorMessage, COLOR); } return "ok"; @@ -1585,15 +1608,15 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ static auto PGROUPACTIVELOCKEDCOL = CConfigValue("group:col.border_locked_active"); static auto PGROUPINACTIVELOCKEDCOL = CConfigValue("group:col.border_locked_inactive"); - const bool GROUPLOCKED = PWINDOW->m_groupData.pNextWindow.lock() ? PWINDOW->getGroupHead()->m_groupData.locked : false; + const bool GROUPLOCKED = PWINDOW->m_group ? PWINDOW->m_group->locked() : false; if (active) { - auto* const ACTIVECOL = (CGradientValueData*)(PACTIVECOL.ptr())->getData(); - auto* const NOGROUPACTIVECOL = (CGradientValueData*)(PNOGROUPACTIVECOL.ptr())->getData(); - auto* const GROUPACTIVECOL = (CGradientValueData*)(PGROUPACTIVECOL.ptr())->getData(); - auto* const GROUPACTIVELOCKEDCOL = (CGradientValueData*)(PGROUPACTIVELOCKEDCOL.ptr())->getData(); + auto* const ACTIVECOL = (Config::CGradientValueData*)(PACTIVECOL.ptr())->getData(); + auto* const NOGROUPACTIVECOL = (Config::CGradientValueData*)(PNOGROUPACTIVECOL.ptr())->getData(); + auto* const GROUPACTIVECOL = (Config::CGradientValueData*)(PGROUPACTIVECOL.ptr())->getData(); + auto* const GROUPACTIVELOCKEDCOL = (Config::CGradientValueData*)(PGROUPACTIVELOCKEDCOL.ptr())->getData(); const auto* const ACTIVECOLOR = - !PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); + !PWINDOW->m_group ? (!(PWINDOW->m_groupRules & Desktop::View::GROUP_DENY) ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); std::string borderColorString = PWINDOW->m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR).toString(); if (FORMNORM) @@ -1601,12 +1624,12 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ else return std::format(R"({{"{}": "{}"}})", PROP, borderColorString); } else { - auto* const INACTIVECOL = (CGradientValueData*)(PINACTIVECOL.ptr())->getData(); - auto* const NOGROUPINACTIVECOL = (CGradientValueData*)(PNOGROUPINACTIVECOL.ptr())->getData(); - auto* const GROUPINACTIVECOL = (CGradientValueData*)(PGROUPINACTIVECOL.ptr())->getData(); - auto* const GROUPINACTIVELOCKEDCOL = (CGradientValueData*)(PGROUPINACTIVELOCKEDCOL.ptr())->getData(); - const auto* const INACTIVECOLOR = !PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) : - (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); + auto* const INACTIVECOL = (Config::CGradientValueData*)(PINACTIVECOL.ptr())->getData(); + auto* const NOGROUPINACTIVECOL = (Config::CGradientValueData*)(PNOGROUPINACTIVECOL.ptr())->getData(); + auto* const GROUPINACTIVECOL = (Config::CGradientValueData*)(PGROUPINACTIVECOL.ptr())->getData(); + auto* const GROUPINACTIVELOCKEDCOL = (Config::CGradientValueData*)(PGROUPINACTIVELOCKEDCOL.ptr())->getData(); + const auto* const INACTIVECOLOR = !PWINDOW->m_group ? (!(PWINDOW->m_groupRules & Desktop::View::GROUP_DENY) ? INACTIVECOL : NOGROUPINACTIVECOL) : + (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); std::string borderColorString = PWINDOW->m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR).toString(); if (FORMNORM) @@ -1733,37 +1756,42 @@ static std::string dispatchGetOption(eHyprCtlOutputFormat format, std::string re nextItem(); nextItem(); - const auto VAR = g_pConfigManager->getHyprlangConfigValuePtr(curitem); + const auto VAR = Config::mgr()->getConfigValue(curitem); - if (!VAR) + if (!VAR.dataptr) return "no such option"; - const auto VAL = VAR->getValue(); - const auto TYPE = std::type_index(VAL.type()); + const auto VAL = VAR.dataptr; + const auto TYPE = std::type_index(*VAR.type); if (format == FORMAT_NORMAL) { - if (TYPE == typeid(Hyprlang::INT)) - return std::format("int: {}\nset: {}", std::any_cast(VAL), VAR->m_bSetByUser); - else if (TYPE == typeid(Hyprlang::FLOAT)) - return std::format("float: {:2f}\nset: {}", std::any_cast(VAL), VAR->m_bSetByUser); - else if (TYPE == typeid(Hyprlang::VEC2)) - return std::format("vec2: [{}, {}]\nset: {}", std::any_cast(VAL).x, std::any_cast(VAL).y, VAR->m_bSetByUser); + if (TYPE == typeid(Config::INTEGER)) + return std::format("int: {}\nset: {}", **rc(VAL), VAR.setByUser); + else if (TYPE == typeid(Config::FLOAT)) + return std::format("float: {:2f}\nset: {}", **rc(VAL), VAR.setByUser); + else if (TYPE == typeid(Config::VEC2)) + return std::format("vec2: [{}, {}]\nset: {}", (*rc(VAL))->x, (*rc(VAL))->y, VAR.setByUser); else if (TYPE == typeid(Hyprlang::STRING)) - return std::format("str: {}\nset: {}", std::any_cast(VAL), VAR->m_bSetByUser); + return std::format("str: {}\nset: {}", *rc(VAL), VAR.setByUser); + else if (TYPE == typeid(Config::STRING)) + return std::format("str: {}\nset: {}", **rc(VAL), VAR.setByUser); else if (TYPE == typeid(void*)) - return std::format("custom type: {}\nset: {}", sc(std::any_cast(VAL))->toString(), VAR->m_bSetByUser); + return std::format("custom type: {}\nset: {}", rc((*rc(VAL))->getData())->toString(), VAR.setByUser); } else { - if (TYPE == typeid(Hyprlang::INT)) - return std::format(R"({{"option": "{}", "int": {}, "set": {} }})", curitem, std::any_cast(VAL), VAR->m_bSetByUser); - else if (TYPE == typeid(Hyprlang::FLOAT)) - return std::format(R"({{"option": "{}", "float": {:2f}, "set": {} }})", curitem, std::any_cast(VAL), VAR->m_bSetByUser); - else if (TYPE == typeid(Hyprlang::VEC2)) - return std::format(R"({{"option": "{}", "vec2": [{},{}], "set": {} }})", curitem, std::any_cast(VAL).x, std::any_cast(VAL).y, - VAR->m_bSetByUser); + if (TYPE == typeid(Config::INTEGER)) + return std::format(R"({{"option": "{}", "int": {}, "set": {} }})", curitem, **rc(VAL), VAR.setByUser); + else if (TYPE == typeid(Config::FLOAT)) + return std::format(R"({{"option": "{}", "float": {:2f}, "set": {} }})", curitem, **rc(VAL), VAR.setByUser); + else if (TYPE == typeid(Config::VEC2)) + return std::format(R"({{"option": "{}", "vec2": [{},{}], "set": {} }})", curitem, (*rc(VAL))->x, (*rc(VAL))->y, + VAR.setByUser); else if (TYPE == typeid(Hyprlang::STRING)) - return std::format(R"({{"option": "{}", "str": "{}", "set": {} }})", curitem, escapeJSONStrings(std::any_cast(VAL)), VAR->m_bSetByUser); + return std::format(R"({{"option": "{}", "str": "{}", "set": {} }})", curitem, escapeJSONStrings(*rc(VAL)), VAR.setByUser); + else if (TYPE == typeid(Config::STRING)) + return std::format(R"({{"option": "{}", "str": "{}", "set": {} }})", curitem, **rc(VAL), VAR.setByUser); else if (TYPE == typeid(void*)) - return std::format(R"({{"option": "{}", "custom": "{}", "set": {} }})", curitem, sc(std::any_cast(VAL))->toString(), VAR->m_bSetByUser); + return std::format(R"({{"option": "{}", "custom": "{}", "set": {} }})", curitem, + rc((*rc(VAL))->getData())->toString(), VAR.setByUser); } return "invalid type (internal error)"; @@ -1977,7 +2005,7 @@ static std::string dispatchNotify(eHyprCtlOutputFormat format, std::string reque const auto MESSAGE = vars.join(" ", msgidx); - g_pHyprNotificationOverlay->addNotification(MESSAGE, color, time, sc(icon), fontsize); + Notification::overlay()->addNotification(MESSAGE, color, time, sc(icon), fontsize); return "ok"; } @@ -1997,7 +2025,7 @@ static std::string dispatchDismissNotify(eHyprCtlOutputFormat format, std::strin } catch (std::exception& e) { return "invalid arg 1"; } } - g_pHyprNotificationOverlay->dismissNotifications(amount); + Notification::overlay()->dismissNotifications(amount); return "ok"; } @@ -2016,7 +2044,7 @@ static std::string getIsLocked(eHyprCtlOutputFormat format, std::string request) static std::string getDescriptions(eHyprCtlOutputFormat format, std::string request) { std::string json = "["; - const auto& DESCS = g_pConfigManager->getAllDescriptions(); + const auto& DESCS = Config::Supplementary::CONFIG_OPTIONS; for (const auto& d : DESCS) { json += d.jsonify() + ",\n"; @@ -2034,11 +2062,16 @@ static std::string submapRequest(eHyprCtlOutputFormat format, std::string reques if (submap.empty()) submap = "default"; - return format == FORMAT_JSON ? std::format("{{\"{}\"}}\n", escapeJSONStrings(submap)) : (submap + "\n"); + return format == FORMAT_JSON ? std::format("\"{}\"\n", escapeJSONStrings(submap)) : (submap + "\n"); } static std::string reloadShaders(eHyprCtlOutputFormat format, std::string request) { - if (g_pHyprOpenGL->initShaders()) + CVarList vars(request, 0, ' '); + + if (vars.size() > 2) + return "too many args"; + + if (g_pHyprOpenGL && g_pHyprRenderer->reloadShaders(vars.size() == 2 ? vars[1] : "")) return format == FORMAT_JSON ? "{\"ok\": true}" : "ok"; else return format == FORMAT_JSON ? "{\"ok\": false}" : "error"; @@ -2061,13 +2094,12 @@ CHyprCtl::CHyprCtl() { registerCommand(SHyprCtlCommand{"systeminfo", true, systemInfoRequest}); registerCommand(SHyprCtlCommand{"animations", true, animationsRequest}); registerCommand(SHyprCtlCommand{"rollinglog", true, rollinglogRequest}); - registerCommand(SHyprCtlCommand{"layouts", true, layoutsRequest}); registerCommand(SHyprCtlCommand{"configerrors", true, configErrorsRequest}); registerCommand(SHyprCtlCommand{"locked", true, getIsLocked}); registerCommand(SHyprCtlCommand{"descriptions", true, getDescriptions}); registerCommand(SHyprCtlCommand{"submap", true, submapRequest}); - registerCommand(SHyprCtlCommand{.name = "reloadshaders", .exact = true, .fn = reloadShaders}); + registerCommand(SHyprCtlCommand{.name = "reloadshaders", .exact = false, .fn = reloadShaders}); registerCommand(SHyprCtlCommand{"monitors", false, monitorsRequest}); registerCommand(SHyprCtlCommand{"reload", false, reloadRequest}); registerCommand(SHyprCtlCommand{"plugin", false, dispatchPlugin}); @@ -2167,21 +2199,18 @@ std::string CHyprCtl::getReply(std::string request) { return "unknown request"; if (reloadAll) { - g_pConfigManager->m_wantsMonitorReload = true; // for monitor keywords + Config::monitorRuleMgr()->scheduleReload(); g_pInputManager->setKeyboardLayout(); // update kb layout g_pInputManager->setPointerConfigs(); // update mouse cfgs g_pInputManager->setTouchDeviceConfigs(); // update touch device cfgs g_pInputManager->setTabletConfigs(); // update tablets - static auto PLAYOUT = CConfigValue("general:layout"); + g_pHyprRenderer->m_reloadScreenShader = true; - g_pLayoutManager->switchToLayout(*PLAYOUT); // update layout - - g_pHyprOpenGL->m_reloadScreenShader = true; - - for (auto& [m, rd] : g_pHyprOpenGL->m_monitorRenderResources) { - rd.blurFBDirty = true; + for (auto const& m : g_pCompositor->m_monitors) { + if (m) + m->m_blurFBDirty = true; } for (auto const& w : g_pCompositor->m_windows) { @@ -2191,9 +2220,17 @@ std::string CHyprCtl::getReply(std::string request) { Desktop::Rule::ruleEngine()->updateAllRules(); } + for (const auto& ws : g_pCompositor->getWorkspaces()) { + if (!ws) + continue; + + ws->updateWindows(); + ws->updateWindowData(); + ws->updateWindowDecos(); + } + for (auto const& m : g_pCompositor->m_monitors) { g_pHyprRenderer->damageMonitor(m); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id); } } @@ -2205,30 +2242,52 @@ std::string CHyprCtl::makeDynamicCall(const std::string& input) { } static bool successWrite(int fd, const std::string& data, bool needLog = true) { - if (write(fd, data.c_str(), data.length()) > 0) - return true; + size_t totalWritten = 0; + size_t remaining = data.length(); + size_t waitsDone = 0; + constexpr const size_t MAX_WAITS = 20; // 2000µs = 2ms - if (errno == EAGAIN) - return true; + while (totalWritten < data.length()) { + ssize_t written = write(fd, data.c_str() + totalWritten, remaining); - if (needLog) - Debug::log(ERR, "Couldn't write to socket. Error: " + std::string(strerror(errno))); + if (waitsDone > MAX_WAITS) { + Log::logger->log(Log::ERR, "Couldn't write to socket. Buffer was full and the client couldn't read in time."); + return false; + } - return false; + if (written < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // socket buffer full, wait a bit and retry + std::this_thread::sleep_for(std::chrono::microseconds(100)); + waitsDone++; + continue; + } + if (needLog) + Log::logger->log(Log::ERR, "Couldn't write to socket. Error: {}", strerror(errno)); + return false; + } + + waitsDone = 0; + + totalWritten += written; + remaining -= written; + } + + return true; } static void runWritingDebugLogThread(const int conn) { using namespace std::chrono_literals; - Debug::log(LOG, "In followlog thread, got connection, start writing: {}", conn); + Log::logger->log(Log::DEBUG, "In followlog thread, got connection, start writing: {}", conn); //will be finished, when reading side close connection std::thread([conn]() { - while (Debug::SRollingLogFollow::get().isRunning()) { - if (Debug::SRollingLogFollow::get().isEmpty(conn)) { + while (Log::SRollingLogFollow::get().isRunning()) { + if (Log::SRollingLogFollow::get().isEmpty(conn)) { std::this_thread::sleep_for(1000ms); continue; } - auto line = Debug::SRollingLogFollow::get().getLog(conn); + auto line = Log::SRollingLogFollow::get().getLog(conn); if (!successWrite(conn, line)) // We cannot write, when connection is closed. So thread will successfully exit by itself break; @@ -2236,7 +2295,7 @@ static void runWritingDebugLogThread(const int conn) { std::this_thread::sleep_for(100ms); } close(conn); - Debug::SRollingLogFollow::get().stopFor(conn); + Log::SRollingLogFollow::get().stopFor(conn); }).detach(); } @@ -2262,10 +2321,10 @@ static int hyprCtlFDTick(int fd, uint32_t mask, void* data) { CRED_T creds; uint32_t len = sizeof(creds); if (getsockopt(ACCEPTEDCONNECTION, CRED_LVL, CRED_OPT, &creds, &len) == -1) - Debug::log(ERR, "Hyprctl: failed to get peer creds"); + Log::logger->log(Log::ERR, "Hyprctl: failed to get peer creds"); else { g_pHyprCtl->m_currentRequestParams.pid = creds.CRED_PID; - Debug::log(LOG, "Hyprctl: new connection from pid {}", creds.CRED_PID); + Log::logger->log(Log::DEBUG, "Hyprctl: new connection from pid {}", creds.CRED_PID); } // @@ -2300,7 +2359,7 @@ static int hyprCtlFDTick(int fd, uint32_t mask, void* data) { try { reply = g_pHyprCtl->getReply(request); } catch (std::exception& e) { - Debug::log(ERR, "Error in request: {}", e.what()); + Log::logger->log(Log::ERR, "Error in request: {}", e.what()); reply = "Err: " + std::string(e.what()); } @@ -2320,16 +2379,13 @@ static int hyprCtlFDTick(int fd, uint32_t mask, void* data) { successWrite(ACCEPTEDCONNECTION, reply); if (isFollowUpRollingLogRequest(request)) { - Debug::log(LOG, "Followup rollinglog request received. Starting thread to write to socket."); - Debug::SRollingLogFollow::get().startFor(ACCEPTEDCONNECTION); + Log::logger->log(Log::DEBUG, "Followup rollinglog request received. Starting thread to write to socket."); + Log::SRollingLogFollow::get().startFor(ACCEPTEDCONNECTION); runWritingDebugLogThread(ACCEPTEDCONNECTION); - Debug::log(LOG, Debug::SRollingLogFollow::get().debugInfo()); + Log::logger->log(Log::DEBUG, Log::SRollingLogFollow::get().debugInfo()); } else close(ACCEPTEDCONNECTION); - if (g_pConfigManager->m_wantsMonitorReload) - g_pConfigManager->ensureMonitorStatus(); - g_pHyprCtl->m_currentRequestParams.pid = 0; } @@ -2340,7 +2396,7 @@ void CHyprCtl::startHyprCtlSocket() { m_socketFD = CFileDescriptor{socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)}; if (!m_socketFD.isValid()) { - Debug::log(ERR, "Couldn't start the Hyprland Socket. (1) IPC will not work."); + Log::logger->log(Log::ERR, "Couldn't start the Hyprland Socket. (1) IPC will not work."); return; } @@ -2351,14 +2407,14 @@ void CHyprCtl::startHyprCtlSocket() { snprintf(SERVERADDRESS.sun_path, sizeof(SERVERADDRESS.sun_path), "%s", m_socketPath.c_str()); if (bind(m_socketFD.get(), rc(&SERVERADDRESS), SUN_LEN(&SERVERADDRESS)) < 0) { - Debug::log(ERR, "Couldn't start the Hyprland Socket. (2) IPC will not work."); + Log::logger->log(Log::ERR, "Couldn't start the Hyprland Socket. (2) IPC will not work."); return; } // 10 max queued. listen(m_socketFD.get(), 10); - Debug::log(LOG, "Hypr socket started at {}", m_socketPath); + Log::logger->log(Log::DEBUG, "Hypr socket started at {}", m_socketPath); m_eventSource = wl_event_loop_add_fd(g_pCompositor->m_wlEventLoop, m_socketFD.get(), WL_EVENT_READABLE, hyprCtlFDTick, nullptr); } diff --git a/src/debug/HyprCtl.hpp b/src/debug/HyprCtl.hpp index d4f7aa149..a6fa37217 100644 --- a/src/debug/HyprCtl.hpp +++ b/src/debug/HyprCtl.hpp @@ -3,7 +3,7 @@ #include #include "../helpers/MiscFunctions.hpp" #include "../helpers/defer/Promise.hpp" -#include "../desktop/Window.hpp" +#include "../desktop/view/Window.hpp" #include #include #include diff --git a/src/debug/HyprDebugOverlay.cpp b/src/debug/HyprDebugOverlay.cpp deleted file mode 100644 index 8f4189b02..000000000 --- a/src/debug/HyprDebugOverlay.cpp +++ /dev/null @@ -1,276 +0,0 @@ -#include -#include "HyprDebugOverlay.hpp" -#include "config/ConfigValue.hpp" -#include "../Compositor.hpp" -#include "../render/pass/TexPassElement.hpp" -#include "../render/Renderer.hpp" -#include "../managers/animation/AnimationManager.hpp" -#include "../desktop/state/FocusState.hpp" - -CHyprDebugOverlay::CHyprDebugOverlay() { - m_texture = makeShared(); -} - -void CHyprMonitorDebugOverlay::renderData(PHLMONITOR pMonitor, float durationUs) { - static auto PDEBUGOVERLAY = CConfigValue("debug:overlay"); - - if (!*PDEBUGOVERLAY) - return; - - m_lastRenderTimes.emplace_back(durationUs / 1000.f); - - if (m_lastRenderTimes.size() > sc(pMonitor->m_refreshRate)) - m_lastRenderTimes.pop_front(); - - if (!m_monitor) - m_monitor = pMonitor; -} - -void CHyprMonitorDebugOverlay::renderDataNoOverlay(PHLMONITOR pMonitor, float durationUs) { - static auto PDEBUGOVERLAY = CConfigValue("debug:overlay"); - - if (!*PDEBUGOVERLAY) - return; - - m_lastRenderTimesNoOverlay.emplace_back(durationUs / 1000.f); - - if (m_lastRenderTimesNoOverlay.size() > sc(pMonitor->m_refreshRate)) - m_lastRenderTimesNoOverlay.pop_front(); - - if (!m_monitor) - m_monitor = pMonitor; -} - -void CHyprMonitorDebugOverlay::frameData(PHLMONITOR pMonitor) { - static auto PDEBUGOVERLAY = CConfigValue("debug:overlay"); - - if (!*PDEBUGOVERLAY) - return; - - m_lastFrametimes.emplace_back(std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - m_lastFrame).count() / 1000.f); - - if (m_lastFrametimes.size() > sc(pMonitor->m_refreshRate)) - m_lastFrametimes.pop_front(); - - m_lastFrame = std::chrono::high_resolution_clock::now(); - - if (!m_monitor) - m_monitor = pMonitor; - - // anim data too - const auto PMONITORFORTICKS = g_pHyprRenderer->m_mostHzMonitor ? g_pHyprRenderer->m_mostHzMonitor.lock() : Desktop::focusState()->monitor(); - if (PMONITORFORTICKS == pMonitor) { - if (m_lastAnimationTicks.size() > sc(PMONITORFORTICKS->m_refreshRate)) - m_lastAnimationTicks.pop_front(); - - m_lastAnimationTicks.push_back(g_pAnimationManager->m_lastTickTimeMs); - } -} - -int CHyprMonitorDebugOverlay::draw(int offset) { - - if (!m_monitor) - return 0; - - // get avg fps - float avgFrametime = 0; - float maxFrametime = 0; - float minFrametime = 9999; - for (auto const& ft : m_lastFrametimes) { - if (ft > maxFrametime) - maxFrametime = ft; - if (ft < minFrametime) - minFrametime = ft; - avgFrametime += ft; - } - float varFrametime = maxFrametime - minFrametime; - avgFrametime /= m_lastFrametimes.empty() ? 1 : m_lastFrametimes.size(); - - float avgRenderTime = 0; - float maxRenderTime = 0; - float minRenderTime = 9999; - for (auto const& rt : m_lastRenderTimes) { - if (rt > maxRenderTime) - maxRenderTime = rt; - if (rt < minRenderTime) - minRenderTime = rt; - avgRenderTime += rt; - } - float varRenderTime = maxRenderTime - minRenderTime; - avgRenderTime /= m_lastRenderTimes.empty() ? 1 : m_lastRenderTimes.size(); - - float avgRenderTimeNoOverlay = 0; - float maxRenderTimeNoOverlay = 0; - float minRenderTimeNoOverlay = 9999; - for (auto const& rt : m_lastRenderTimesNoOverlay) { - if (rt > maxRenderTimeNoOverlay) - maxRenderTimeNoOverlay = rt; - if (rt < minRenderTimeNoOverlay) - minRenderTimeNoOverlay = rt; - avgRenderTimeNoOverlay += rt; - } - float varRenderTimeNoOverlay = maxRenderTimeNoOverlay - minRenderTimeNoOverlay; - avgRenderTimeNoOverlay /= m_lastRenderTimes.empty() ? 1 : m_lastRenderTimes.size(); - - float avgAnimMgrTick = 0; - float maxAnimMgrTick = 0; - float minAnimMgrTick = 9999; - for (auto const& at : m_lastAnimationTicks) { - if (at > maxAnimMgrTick) - maxAnimMgrTick = at; - if (at < minAnimMgrTick) - minAnimMgrTick = at; - avgAnimMgrTick += at; - } - float varAnimMgrTick = maxAnimMgrTick - minAnimMgrTick; - avgAnimMgrTick /= m_lastAnimationTicks.empty() ? 1 : m_lastAnimationTicks.size(); - - const float FPS = 1.f / (avgFrametime / 1000.f); // frametimes are in ms - const float idealFPS = m_lastFrametimes.size(); - - static auto fontFamily = CConfigValue("misc:font_family"); - PangoLayout* layoutText = pango_cairo_create_layout(g_pDebugOverlay->m_cairo); - PangoFontDescription* pangoFD = pango_font_description_new(); - - pango_font_description_set_family(pangoFD, (*fontFamily).c_str()); - pango_font_description_set_style(pangoFD, PANGO_STYLE_NORMAL); - pango_font_description_set_weight(pangoFD, PANGO_WEIGHT_NORMAL); - - float maxTextW = 0; - int fontSize = 0; - auto cr = g_pDebugOverlay->m_cairo; - - auto showText = [cr, layoutText, pangoFD, &maxTextW, &fontSize](const char* text, int size) { - if (fontSize != size) { - pango_font_description_set_absolute_size(pangoFD, size * PANGO_SCALE); - pango_layout_set_font_description(layoutText, pangoFD); - fontSize = size; - } - - pango_layout_set_text(layoutText, text, -1); - pango_cairo_show_layout(cr, layoutText); - - int textW = 0, textH = 0; - pango_layout_get_size(layoutText, &textW, &textH); - textW /= PANGO_SCALE; - textH /= PANGO_SCALE; - if (textW > maxTextW) - maxTextW = textW; - - // move to next line - cairo_rel_move_to(cr, 0, fontSize + 1); - }; - - const int MARGIN_TOP = 8; - const int MARGIN_LEFT = 4; - cairo_move_to(cr, MARGIN_LEFT, MARGIN_TOP + offset); - cairo_set_source_rgba(g_pDebugOverlay->m_cairo, 1.f, 1.f, 1.f, 1.f); - - std::string text; - showText(m_monitor->m_name.c_str(), 10); - - if (FPS > idealFPS * 0.95f) - cairo_set_source_rgba(g_pDebugOverlay->m_cairo, 0.2f, 1.f, 0.2f, 1.f); - else if (FPS > idealFPS * 0.8f) - cairo_set_source_rgba(g_pDebugOverlay->m_cairo, 1.f, 1.f, 0.2f, 1.f); - else - cairo_set_source_rgba(g_pDebugOverlay->m_cairo, 1.f, 0.2f, 0.2f, 1.f); - - text = std::format("{} FPS", sc(FPS)); - showText(text.c_str(), 16); - - cairo_set_source_rgba(g_pDebugOverlay->m_cairo, 1.f, 1.f, 1.f, 1.f); - - text = std::format("Avg Frametime: {:.2f}ms (var {:.2f}ms)", avgFrametime, varFrametime); - showText(text.c_str(), 10); - - text = std::format("Avg Rendertime: {:.2f}ms (var {:.2f}ms)", avgRenderTime, varRenderTime); - showText(text.c_str(), 10); - - text = std::format("Avg Rendertime (No Overlay): {:.2f}ms (var {:.2f}ms)", avgRenderTimeNoOverlay, varRenderTimeNoOverlay); - showText(text.c_str(), 10); - - text = std::format("Avg Anim Tick: {:.2f}ms (var {:.2f}ms) ({:.2f} TPS)", avgAnimMgrTick, varAnimMgrTick, 1.0 / (avgAnimMgrTick / 1000.0)); - showText(text.c_str(), 10); - - pango_font_description_free(pangoFD); - g_object_unref(layoutText); - - double posX = 0, posY = 0; - cairo_get_current_point(cr, &posX, &posY); - - g_pHyprRenderer->damageBox(m_lastDrawnBox); - m_lastDrawnBox = {sc(g_pCompositor->m_monitors.front()->m_position.x) + MARGIN_LEFT - 1, - sc(g_pCompositor->m_monitors.front()->m_position.y) + offset + MARGIN_TOP - 1, sc(maxTextW) + 2, posY - offset - MARGIN_TOP + 2}; - g_pHyprRenderer->damageBox(m_lastDrawnBox); - - return posY - offset; -} - -void CHyprDebugOverlay::renderData(PHLMONITOR pMonitor, float durationUs) { - static auto PDEBUGOVERLAY = CConfigValue("debug:overlay"); - - if (!*PDEBUGOVERLAY) - return; - - m_monitorOverlays[pMonitor].renderData(pMonitor, durationUs); -} - -void CHyprDebugOverlay::renderDataNoOverlay(PHLMONITOR pMonitor, float durationUs) { - static auto PDEBUGOVERLAY = CConfigValue("debug:overlay"); - - if (!*PDEBUGOVERLAY) - return; - - m_monitorOverlays[pMonitor].renderDataNoOverlay(pMonitor, durationUs); -} - -void CHyprDebugOverlay::frameData(PHLMONITOR pMonitor) { - static auto PDEBUGOVERLAY = CConfigValue("debug:overlay"); - - if (!*PDEBUGOVERLAY) - return; - - m_monitorOverlays[pMonitor].frameData(pMonitor); -} - -void CHyprDebugOverlay::draw() { - - const auto PMONITOR = g_pCompositor->m_monitors.front(); - - if (!m_cairoSurface || !m_cairo) { - m_cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y); - m_cairo = cairo_create(m_cairoSurface); - } - - // clear the pixmap - cairo_save(m_cairo); - cairo_set_operator(m_cairo, CAIRO_OPERATOR_CLEAR); - cairo_paint(m_cairo); - cairo_restore(m_cairo); - - // draw the things - int offsetY = 0; - for (auto const& m : g_pCompositor->m_monitors) { - offsetY += m_monitorOverlays[m].draw(offsetY); - offsetY += 5; // for padding between mons - } - - cairo_surface_flush(m_cairoSurface); - - // copy the data to an OpenGL texture we have - const auto DATA = cairo_image_surface_get_data(m_cairoSurface); - m_texture->allocate(); - m_texture->bind(); - m_texture->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); - m_texture->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); - m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); - m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA); - - CTexPassElement::SRenderData data; - data.tex = m_texture; - data.box = {0, 0, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y}; - g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(data))); -} diff --git a/src/debug/HyprDebugOverlay.hpp b/src/debug/HyprDebugOverlay.hpp deleted file mode 100644 index 72987d94e..000000000 --- a/src/debug/HyprDebugOverlay.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -#include "../defines.hpp" -#include "../render/Texture.hpp" -#include -#include -#include - -class CHyprRenderer; - -class CHyprMonitorDebugOverlay { - public: - int draw(int offset); - - void renderData(PHLMONITOR pMonitor, float durationUs); - void renderDataNoOverlay(PHLMONITOR pMonitor, float durationUs); - void frameData(PHLMONITOR pMonitor); - - private: - std::deque m_lastFrametimes; - std::deque m_lastRenderTimes; - std::deque m_lastRenderTimesNoOverlay; - std::deque m_lastAnimationTicks; - std::chrono::high_resolution_clock::time_point m_lastFrame; - PHLMONITORREF m_monitor; - CBox m_lastDrawnBox; - - friend class CHyprRenderer; -}; - -class CHyprDebugOverlay { - public: - CHyprDebugOverlay(); - void draw(); - void renderData(PHLMONITOR, float durationUs); - void renderDataNoOverlay(PHLMONITOR, float durationUs); - void frameData(PHLMONITOR); - - private: - std::map m_monitorOverlays; - - cairo_surface_t* m_cairoSurface = nullptr; - cairo_t* m_cairo = nullptr; - - SP m_texture; - - friend class CHyprMonitorDebugOverlay; - friend class CHyprRenderer; -}; - -inline UP g_pDebugOverlay; diff --git a/src/debug/HyprNotificationOverlay.cpp b/src/debug/HyprNotificationOverlay.cpp deleted file mode 100644 index 1c66a53b5..000000000 --- a/src/debug/HyprNotificationOverlay.cpp +++ /dev/null @@ -1,256 +0,0 @@ -#include -#include -#include "HyprNotificationOverlay.hpp" -#include "../Compositor.hpp" -#include "../config/ConfigValue.hpp" -#include "../render/pass/TexPassElement.hpp" - -#include "../managers/animation/AnimationManager.hpp" -#include "../managers/HookSystemManager.hpp" -#include "../render/Renderer.hpp" - -static inline auto iconBackendFromLayout(PangoLayout* layout) { - // preference: Nerd > FontAwesome > text - auto eIconBackendChecks = std::array{ICONS_BACKEND_NF, ICONS_BACKEND_FA}; - for (auto iconID : eIconBackendChecks) { - auto iconsText = std::ranges::fold_left(ICONS_ARRAY[iconID], std::string(), std::plus<>()); - pango_layout_set_text(layout, iconsText.c_str(), -1); - if (pango_layout_get_unknown_glyphs_count(layout) == 0) - return iconID; - } - return ICONS_BACKEND_NONE; -} - -CHyprNotificationOverlay::CHyprNotificationOverlay() { - 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() { - if (m_cairo) - cairo_destroy(m_cairo); - if (m_cairoSurface) - cairo_surface_destroy(m_cairoSurface); -} - -void CHyprNotificationOverlay::addNotification(const std::string& text, const CHyprColor& color, const float timeMs, const eIcons icon, const float fontSize) { - const auto PNOTIF = m_notifications.emplace_back(makeUnique()).get(); - - PNOTIF->text = icon != eIcons::ICON_NONE ? " " + text /* tiny bit of padding otherwise icon touches text */ : text; - PNOTIF->color = color == CHyprColor(0) ? ICONS_COLORS[icon] : color; - PNOTIF->started.reset(); - PNOTIF->timeMs = timeMs; - PNOTIF->icon = icon; - PNOTIF->fontSize = fontSize; - - for (auto const& m : g_pCompositor->m_monitors) { - g_pCompositor->scheduleFrameForMonitor(m); - } -} - -void CHyprNotificationOverlay::dismissNotifications(const int amount) { - if (amount == -1) - m_notifications.clear(); - else { - const int AMT = std::min(amount, sc(m_notifications.size())); - - for (int i = 0; i < AMT; ++i) { - m_notifications.erase(m_notifications.begin()); - } - } -} - -CBox CHyprNotificationOverlay::drawNotifications(PHLMONITOR pMonitor) { - static constexpr auto ANIM_DURATION_MS = 600.0; - static constexpr auto ANIM_LAG_MS = 100.0; - static constexpr auto NOTIF_LEFTBAR_SIZE = 5.0; - static constexpr auto ICON_PAD = 3.0; - static constexpr auto ICON_SCALE = 0.9; - static constexpr auto GRADIENT_SIZE = 60.0; - - float offsetY = 10; - float maxWidth = 0; - - const auto SCALE = pMonitor->m_scale; - const auto MONSIZE = pMonitor->m_transformedSize; - - static auto fontFamily = CConfigValue("misc:font_family"); - - PangoLayout* layout = pango_cairo_create_layout(m_cairo); - PangoFontDescription* pangoFD = pango_font_description_new(); - - pango_font_description_set_family(pangoFD, (*fontFamily).c_str()); - pango_font_description_set_style(pangoFD, PANGO_STYLE_NORMAL); - pango_font_description_set_weight(pangoFD, PANGO_WEIGHT_NORMAL); - - const auto iconBackendID = iconBackendFromLayout(layout); - const auto PBEZIER = g_pAnimationManager->getBezier("default"); - - for (auto const& notif : m_notifications) { - const auto ICONPADFORNOTIF = notif->icon == ICON_NONE ? 0 : ICON_PAD; - const auto FONTSIZE = std::clamp(sc(notif->fontSize * ((pMonitor->m_pixelSize.x * SCALE) / 1920.f)), 8, 40); - - // first rect (bg, col) - const float FIRSTRECTANIMP = - (notif->started.getMillis() > (ANIM_DURATION_MS - ANIM_LAG_MS) ? - (notif->started.getMillis() > notif->timeMs - (ANIM_DURATION_MS - ANIM_LAG_MS) ? notif->timeMs - notif->started.getMillis() : (ANIM_DURATION_MS - ANIM_LAG_MS)) : - notif->started.getMillis()) / - (ANIM_DURATION_MS - ANIM_LAG_MS); - - const float FIRSTRECTPERC = FIRSTRECTANIMP >= 0.99f ? 1.f : PBEZIER->getYForPoint(FIRSTRECTANIMP); - - // second rect (fg, black) - const float SECONDRECTANIMP = (notif->started.getMillis() > ANIM_DURATION_MS ? - (notif->started.getMillis() > notif->timeMs - ANIM_DURATION_MS ? notif->timeMs - notif->started.getMillis() : ANIM_DURATION_MS) : - notif->started.getMillis()) / - ANIM_DURATION_MS; - - const float SECONDRECTPERC = SECONDRECTANIMP >= 0.99f ? 1.f : PBEZIER->getYForPoint(SECONDRECTANIMP); - - // third rect (horiz, col) - const float THIRDRECTPERC = notif->started.getMillis() / notif->timeMs; - - // get text size - const auto ICON = ICONS_ARRAY[iconBackendID][notif->icon]; - const auto ICONCOLOR = ICONS_COLORS[notif->icon]; - - int iconW = 0, iconH = 0; - pango_font_description_set_absolute_size(pangoFD, PANGO_SCALE * FONTSIZE * ICON_SCALE); - pango_layout_set_font_description(layout, pangoFD); - pango_layout_set_text(layout, ICON.c_str(), -1); - pango_layout_get_size(layout, &iconW, &iconH); - iconW /= PANGO_SCALE; - iconH /= PANGO_SCALE; - - int textW = 0, textH = 0; - pango_font_description_set_absolute_size(pangoFD, PANGO_SCALE * FONTSIZE); - pango_layout_set_font_description(layout, pangoFD); - pango_layout_set_text(layout, notif->text.c_str(), -1); - pango_layout_get_size(layout, &textW, &textH); - textW /= PANGO_SCALE; - textH /= PANGO_SCALE; - - const auto NOTIFSIZE = Vector2D{textW + 20.0 + iconW + 2 * ICONPADFORNOTIF, textH + 10.0}; - - // draw rects - cairo_set_source_rgba(m_cairo, notif->color.r, notif->color.g, notif->color.b, notif->color.a); - cairo_rectangle(m_cairo, MONSIZE.x - (NOTIFSIZE.x + NOTIF_LEFTBAR_SIZE) * FIRSTRECTPERC, offsetY, (NOTIFSIZE.x + NOTIF_LEFTBAR_SIZE) * FIRSTRECTPERC, NOTIFSIZE.y); - cairo_fill(m_cairo); - - cairo_set_source_rgb(m_cairo, 0.f, 0.f, 0.f); - cairo_rectangle(m_cairo, MONSIZE.x - NOTIFSIZE.x * SECONDRECTPERC, offsetY, NOTIFSIZE.x * SECONDRECTPERC, NOTIFSIZE.y); - cairo_fill(m_cairo); - - cairo_set_source_rgba(m_cairo, notif->color.r, notif->color.g, notif->color.b, notif->color.a); - cairo_rectangle(m_cairo, MONSIZE.x - NOTIFSIZE.x * SECONDRECTPERC + 3, offsetY + NOTIFSIZE.y - 4, THIRDRECTPERC * (NOTIFSIZE.x - 6), 2); - cairo_fill(m_cairo); - - // draw gradient - if (notif->icon != ICON_NONE) { - cairo_pattern_t* pattern; - pattern = cairo_pattern_create_linear(MONSIZE.x - (NOTIFSIZE.x + NOTIF_LEFTBAR_SIZE) * FIRSTRECTPERC, offsetY, - MONSIZE.x - (NOTIFSIZE.x + NOTIF_LEFTBAR_SIZE) * FIRSTRECTPERC + GRADIENT_SIZE, offsetY); - cairo_pattern_add_color_stop_rgba(pattern, 0, ICONCOLOR.r, ICONCOLOR.g, ICONCOLOR.b, ICONCOLOR.a / 3.0); - cairo_pattern_add_color_stop_rgba(pattern, 1, ICONCOLOR.r, ICONCOLOR.g, ICONCOLOR.b, 0); - cairo_rectangle(m_cairo, MONSIZE.x - (NOTIFSIZE.x + NOTIF_LEFTBAR_SIZE) * FIRSTRECTPERC, offsetY, GRADIENT_SIZE, NOTIFSIZE.y); - cairo_set_source(m_cairo, pattern); - cairo_fill(m_cairo); - cairo_pattern_destroy(pattern); - - // draw icon - cairo_set_source_rgb(m_cairo, 1.f, 1.f, 1.f); - cairo_move_to(m_cairo, MONSIZE.x - NOTIFSIZE.x * SECONDRECTPERC + NOTIF_LEFTBAR_SIZE + ICONPADFORNOTIF - 1, offsetY - 2 + std::round((NOTIFSIZE.y - iconH) / 2.0)); - pango_layout_set_text(layout, ICON.c_str(), -1); - pango_cairo_show_layout(m_cairo, layout); - } - - // draw text - cairo_set_source_rgb(m_cairo, 1.f, 1.f, 1.f); - cairo_move_to(m_cairo, MONSIZE.x - NOTIFSIZE.x * SECONDRECTPERC + NOTIF_LEFTBAR_SIZE + iconW + 2 * ICONPADFORNOTIF, offsetY - 2 + std::round((NOTIFSIZE.y - textH) / 2.0)); - pango_layout_set_text(layout, notif->text.c_str(), -1); - pango_cairo_show_layout(m_cairo, layout); - - // adjust offset and move on - offsetY += NOTIFSIZE.y + 10; - - if (maxWidth < NOTIFSIZE.x) - maxWidth = NOTIFSIZE.x; - } - - pango_font_description_free(pangoFD); - g_object_unref(layout); - - // cleanup notifs - std::erase_if(m_notifications, [](const auto& notif) { return notif->started.getMillis() > notif->timeMs; }); - - return CBox{sc(pMonitor->m_position.x + pMonitor->m_size.x - maxWidth - 20), sc(pMonitor->m_position.y), sc(maxWidth) + 20, sc(offsetY) + 10}; -} - -void CHyprNotificationOverlay::draw(PHLMONITOR pMonitor) { - - const auto MONSIZE = pMonitor->m_transformedSize; - - if (m_lastMonitor != pMonitor || m_lastSize != MONSIZE || !m_cairo || !m_cairoSurface) { - - if (m_cairo && m_cairoSurface) { - cairo_destroy(m_cairo); - cairo_surface_destroy(m_cairoSurface); - } - - m_cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, MONSIZE.x, MONSIZE.y); - m_cairo = cairo_create(m_cairoSurface); - m_lastMonitor = pMonitor; - m_lastSize = MONSIZE; - } - - // Draw the notifications - if (m_notifications.empty()) - return; - - // Render to the monitor - - // clear the pixmap - cairo_save(m_cairo); - cairo_set_operator(m_cairo, CAIRO_OPERATOR_CLEAR); - cairo_paint(m_cairo); - cairo_restore(m_cairo); - - cairo_surface_flush(m_cairoSurface); - - CBox damage = drawNotifications(pMonitor); - - g_pHyprRenderer->damageBox(damage); - g_pHyprRenderer->damageBox(m_lastDamage); - - g_pCompositor->scheduleFrameForMonitor(pMonitor); - - m_lastDamage = damage; - - // copy the data to an OpenGL texture we have - const auto DATA = cairo_image_surface_get_data(m_cairoSurface); - m_texture->allocate(); - m_texture->bind(); - m_texture->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); - m_texture->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); - m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); - m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, MONSIZE.x, MONSIZE.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA); - - CTexPassElement::SRenderData data; - data.tex = m_texture; - data.box = {0, 0, MONSIZE.x, MONSIZE.y}; - data.a = 1.F; - - g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(data))); -} - -bool CHyprNotificationOverlay::hasAny() { - return !m_notifications.empty(); -} diff --git a/src/debug/HyprNotificationOverlay.hpp b/src/debug/HyprNotificationOverlay.hpp deleted file mode 100644 index 868eb05bb..000000000 --- a/src/debug/HyprNotificationOverlay.hpp +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#include "../defines.hpp" -#include "../helpers/time/Timer.hpp" -#include "../render/Texture.hpp" -#include "../SharedDefs.hpp" - -#include - -#include - -enum eIconBackend : uint8_t { - ICONS_BACKEND_NONE = 0, - ICONS_BACKEND_NF, - ICONS_BACKEND_FA -}; - -static const std::array, 3 /* backends */> ICONS_ARRAY = { - std::array{"[!]", "[i]", "[Hint]", "[Err]", "[?]", "[ok]", ""}, - std::array{"", "", "", "", "", "󰸞", ""}, std::array{"", "", "", "", "", ""}}; -static const std::array ICONS_COLORS = {CHyprColor{255.0 / 255.0, 204 / 255.0, 102 / 255.0, 1.0}, - 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}, - CHyprColor{255 / 255.0, 204 / 255.0, 153 / 255.0, 1.0}, - CHyprColor{128 / 255.0, 255 / 255.0, 128 / 255.0, 1.0}, - CHyprColor{0, 0, 0, 1.0}}; - -struct SNotification { - std::string text = ""; - CHyprColor color; - CTimer started; - float timeMs = 0; - eIcons icon = ICON_NONE; - float fontSize = 13.f; -}; - -class CHyprNotificationOverlay { - public: - CHyprNotificationOverlay(); - ~CHyprNotificationOverlay(); - - void draw(PHLMONITOR pMonitor); - void addNotification(const std::string& text, const CHyprColor& color, const float timeMs, const eIcons icon = ICON_NONE, const float fontSize = 13.f); - void dismissNotifications(const int amount); - bool hasAny(); - - private: - CBox drawNotifications(PHLMONITOR pMonitor); - CBox m_lastDamage; - - std::vector> m_notifications; - - cairo_surface_t* m_cairoSurface = nullptr; - cairo_t* m_cairo = nullptr; - - PHLMONITORREF m_lastMonitor; - Vector2D m_lastSize = Vector2D(-1, -1); - - SP m_texture; -}; - -inline UP g_pHyprNotificationOverlay; diff --git a/src/debug/Log.cpp b/src/debug/Log.cpp deleted file mode 100644 index e70617d3f..000000000 --- a/src/debug/Log.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "Log.hpp" -#include "../defines.hpp" -#include "RollingLogFollow.hpp" - -#include -#include -#include - -void Debug::init(const std::string& IS) { - m_logFile = IS + (ISDEBUG ? "/hyprlandd.log" : "/hyprland.log"); - m_logOfs.open(m_logFile, std::ios::out | std::ios::app); - auto handle = m_logOfs.native_handle(); - fcntl(handle, F_SETFD, FD_CLOEXEC); -} - -void Debug::close() { - m_logOfs.close(); -} - -void Debug::log(eLogLevel level, std::string str) { - if (level == TRACE && !m_trace) - return; - - if (m_shuttingDown) - return; - - std::lock_guard guard(m_logMutex); - - std::string coloredStr = str; - //NOLINTBEGIN - switch (level) { - case LOG: - str = "[LOG] " + str; - coloredStr = str; - break; - case WARN: - str = "[WARN] " + str; - coloredStr = "\033[1;33m" + str + "\033[0m"; // yellow - break; - case ERR: - str = "[ERR] " + str; - coloredStr = "\033[1;31m" + str + "\033[0m"; // red - break; - case CRIT: - str = "[CRITICAL] " + str; - coloredStr = "\033[1;35m" + str + "\033[0m"; // magenta - break; - case INFO: - str = "[INFO] " + str; - coloredStr = "\033[1;32m" + str + "\033[0m"; // green - break; - case TRACE: - str = "[TRACE] " + str; - coloredStr = "\033[1;34m" + str + "\033[0m"; // blue - break; - default: break; - } - //NOLINTEND - - m_rollingLog += str + "\n"; - if (m_rollingLog.size() > ROLLING_LOG_SIZE) - m_rollingLog = m_rollingLog.substr(m_rollingLog.size() - ROLLING_LOG_SIZE); - - if (SRollingLogFollow::get().isRunning()) - SRollingLogFollow::get().addLog(str); - - if (!m_disableLogs || !**m_disableLogs) { - // log to a file - m_logOfs << str << "\n"; - m_logOfs.flush(); - } - - // log it to the stdout too. - if (!m_disableStdout) { - std::println("{}", ((m_coloredLogs && !**m_coloredLogs) ? str : coloredStr)); - std::fflush(stdout); - } -} diff --git a/src/debug/Log.hpp b/src/debug/Log.hpp deleted file mode 100644 index c31468053..000000000 --- a/src/debug/Log.hpp +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include - -#define LOGMESSAGESIZE 1024 -#define ROLLING_LOG_SIZE 4096 - -enum eLogLevel : int8_t { - NONE = -1, - LOG = 0, - WARN, - ERR, - CRIT, - INFO, - TRACE -}; - -// NOLINTNEXTLINE(readability-identifier-naming) -namespace Debug { - inline std::string m_logFile; - inline std::ofstream m_logOfs; - inline int64_t* const* m_disableLogs = nullptr; - inline int64_t* const* m_disableTime = nullptr; - inline bool m_disableStdout = false; - inline bool m_trace = false; - inline bool m_shuttingDown = false; - inline int64_t* const* m_coloredLogs = nullptr; - - inline std::string m_rollingLog = ""; // rolling log contains the ROLLING_LOG_SIZE tail of the log - inline std::mutex m_logMutex; - - void init(const std::string& IS); - void close(); - - // - void log(eLogLevel level, std::string str); - - template - //NOLINTNEXTLINE - void log(eLogLevel level, std::format_string fmt, Args&&... args) { - if (level == TRACE && !m_trace) - return; - - if (m_shuttingDown) - return; - - std::string logMsg = ""; - - // print date and time to the ofs - if (m_disableTime && !**m_disableTime) { -#ifndef _LIBCPP_VERSION - static auto current_zone = std::chrono::current_zone(); - const auto zt = std::chrono::zoned_time{current_zone, std::chrono::system_clock::now()}; - const auto hms = std::chrono::hh_mm_ss{zt.get_local_time() - std::chrono::floor(zt.get_local_time())}; -#else - // TODO: current clang 17 does not support `zoned_time`, remove this once clang 19 is ready - const auto hms = std::chrono::hh_mm_ss{std::chrono::system_clock::now() - std::chrono::floor(std::chrono::system_clock::now())}; -#endif - logMsg += std::format("[{}] ", hms); - } - - // no need for try {} catch {} because std::format_string ensures that vformat never throw std::format_error - // because - // 1. any faulty format specifier that sucks will cause a compilation error. - // 2. and `std::bad_alloc` is catastrophic, (Almost any operation in stdlib could throw this.) - // 3. this is actually what std::format in stdlib does - logMsg += std::vformat(fmt.get(), std::make_format_args(args...)); - - log(level, logMsg); - } -}; diff --git a/src/debug/Overlay.cpp b/src/debug/Overlay.cpp new file mode 100644 index 000000000..694bb3207 --- /dev/null +++ b/src/debug/Overlay.cpp @@ -0,0 +1,503 @@ +#include "Overlay.hpp" +#include "config/ConfigValue.hpp" +#include "../Compositor.hpp" +#include "../render/pass/RectPassElement.hpp" +#include "../render/pass/TexPassElement.hpp" +#include "../render/Renderer.hpp" +#include "../managers/animation/AnimationManager.hpp" +#include "../desktop/state/FocusState.hpp" +#include +#include +#include + +namespace { + constexpr float OVERLAY_REFRESH_INTERVAL_MS = 200.F; + constexpr int OVERLAY_MARGIN_TOP = 4; + constexpr int OVERLAY_MARGIN_LEFT = 4; + constexpr int OVERLAY_LINE_GAP = 1; + constexpr int OVERLAY_MONITOR_GAP = 5; + constexpr int OVERLAY_BOX_MARGIN = 5; + + constexpr int OVERLAY_FPS_GRAPH_HISTORY_SEC = 30; + constexpr int OVERLAY_FPS_GRAPH_BAR_WIDTH = 3; + constexpr int OVERLAY_FPS_GRAPH_BAR_GAP = 1; + constexpr int OVERLAY_FPS_GRAPH_HEIGHT = 22; + constexpr int OVERLAY_FPS_GRAPH_PADDING = 2; + constexpr int OVERLAY_FPS_GRAPH_GAP_TOP = 2; + constexpr float OVERLAY_FPS_GRAPH_BG_ALPHA = 0.35F; + + const CHyprColor FPS_COLOR_BAD = CHyprColor{1.F, 0.2F, 0.2F, 1.F}; + const CHyprColor FPS_COLOR_GOOD = CHyprColor{0.2F, 1.F, 0.2F, 1.F}; + + struct SFPSGraphLayout { + float innerWidth = 0.F; + float innerHeight = 0.F; + float width = 0.F; + float height = 0.F; + }; + + struct SFPSGraphDrawResult { + float width = 0.F; + float bottomY = 0.F; + }; +} + +static Hyprgraphics::CColor::SOkLab lerp(const Hyprgraphics::CColor::SOkLab& a, const Hyprgraphics::CColor::SOkLab& b, float ratio) { + return Hyprgraphics::CColor::SOkLab{ + .l = std::lerp(a.l, b.l, ratio), + .a = std::lerp(a.a, b.a, ratio), + .b = std::lerp(a.b, b.b, ratio), + }; +} + +static CHyprColor fpsBarColor(float normalizedFPS) { + return CHyprColor{Hyprgraphics::CColor{lerp(FPS_COLOR_BAD.asOkLab(), FPS_COLOR_GOOD.asOkLab(), normalizedFPS)}, 1.F}; +} + +static SFPSGraphLayout fpsGraphLayout() { + const float INNERWIDTH = sc(OVERLAY_FPS_GRAPH_HISTORY_SEC * OVERLAY_FPS_GRAPH_BAR_WIDTH + (OVERLAY_FPS_GRAPH_HISTORY_SEC - 1) * OVERLAY_FPS_GRAPH_BAR_GAP); + const float INNERHEIGHT = sc(OVERLAY_FPS_GRAPH_HEIGHT); + + return { + .innerWidth = INNERWIDTH, + .innerHeight = INNERHEIGHT, + .width = INNERWIDTH + OVERLAY_FPS_GRAPH_PADDING * 2.F, + .height = INNERHEIGHT + OVERLAY_FPS_GRAPH_PADDING * 2.F, + }; +} + +static SFPSGraphDrawResult drawFPSGraph(float x, float y, float idealFPS, const std::deque& fpsHistory) { + const auto LAYOUT = fpsGraphLayout(); + + CRectPassElement::SRectData bgData; + bgData.box = {x, y, LAYOUT.width, LAYOUT.height}; + bgData.color = CHyprColor{0.F, 0.F, 0.F, OVERLAY_FPS_GRAPH_BG_ALPHA}; + bgData.round = 2; + g_pHyprRenderer->m_renderPass.add(makeUnique(bgData)); + + const size_t BARCOUNT = std::min(fpsHistory.size(), sc(OVERLAY_FPS_GRAPH_HISTORY_SEC)); + const size_t LEADINGBLANKBARS = sc(OVERLAY_FPS_GRAPH_HISTORY_SEC) - BARCOUNT; + + for (size_t bar = 0; bar < BARCOUNT; ++bar) { + const float FPSVALUE = fpsHistory[fpsHistory.size() - BARCOUNT + bar]; + const float NORMALIZEDFPS = std::clamp(FPSVALUE / idealFPS, 0.F, 1.F); + const float BARHEIGHT = std::max(1.F, std::round(NORMALIZEDFPS * LAYOUT.innerHeight)); + const float BARX = x + OVERLAY_FPS_GRAPH_PADDING + sc((LEADINGBLANKBARS + bar) * (OVERLAY_FPS_GRAPH_BAR_WIDTH + OVERLAY_FPS_GRAPH_BAR_GAP)); + const float BARY = y + OVERLAY_FPS_GRAPH_PADDING + (LAYOUT.innerHeight - BARHEIGHT); + + CRectPassElement::SRectData barData; + barData.box = {BARX, BARY, sc(OVERLAY_FPS_GRAPH_BAR_WIDTH), BARHEIGHT}; + barData.color = fpsBarColor(NORMALIZEDFPS); + g_pHyprRenderer->m_renderPass.add(makeUnique(barData)); + } + + return { + .width = LAYOUT.width, + .bottomY = y + LAYOUT.height, + }; +} + +using namespace Debug; + +UP& Debug::overlay() { + static UP p = makeUnique(); + return p; +} + +COverlay::COverlay() { + m_frameTimer.reset(); +} + +void CMonitorOverlay::renderData(PHLMONITOR pMonitor, float durationUs) { + static auto PDEBUGOVERLAY = CConfigValue("debug:overlay"); + + if (!*PDEBUGOVERLAY) + return; + + m_monitor = pMonitor; + + m_lastRenderTimes.emplace_back(durationUs / 1000.F); + + const auto SAMPLELIMIT = std::max(1, sc(std::ceil(std::max(1.F, pMonitor->m_refreshRate)))); + + if (m_lastRenderTimes.size() > SAMPLELIMIT) + m_lastRenderTimes.pop_front(); +} + +void CMonitorOverlay::renderDataNoOverlay(PHLMONITOR pMonitor, float durationUs) { + static auto PDEBUGOVERLAY = CConfigValue("debug:overlay"); + + if (!*PDEBUGOVERLAY) + return; + + m_monitor = pMonitor; + + m_lastRenderTimesNoOverlay.emplace_back(durationUs / 1000.F); + + const auto SAMPLELIMIT = std::max(1, sc(std::ceil(std::max(1.F, pMonitor->m_refreshRate)))); + + if (m_lastRenderTimesNoOverlay.size() > SAMPLELIMIT) + m_lastRenderTimesNoOverlay.pop_front(); +} + +void CMonitorOverlay::frameData(PHLMONITOR pMonitor) { + static auto PDEBUGOVERLAY = CConfigValue("debug:overlay"); + + if (!*PDEBUGOVERLAY) + return; + + m_monitor = pMonitor; + + const auto NOW = std::chrono::high_resolution_clock::now(); + if (m_lastFrame.time_since_epoch().count() != 0) + m_lastFrametimes.emplace_back(std::chrono::duration_cast(NOW - m_lastFrame).count() / 1000.F); + + const auto SAMPLELIMIT = std::max(1, sc(std::ceil(std::max(1.F, pMonitor->m_refreshRate)))); + + if (m_lastFrametimes.size() > SAMPLELIMIT) + m_lastFrametimes.pop_front(); + + m_lastFrame = NOW; + + if (m_fpsSecondStart.time_since_epoch().count() == 0) + m_fpsSecondStart = NOW; + + ++m_framesInCurrentSecond; + + const auto SECONDWINDOWMS = std::chrono::duration_cast(NOW - m_fpsSecondStart).count(); + if (SECONDWINDOWMS >= 1000) { + const float ELAPSEDSECONDS = SECONDWINDOWMS / 1000.F; + const float IDEALFPS = std::max(1.F, pMonitor->m_refreshRate); + const float FPSINWINDOW = ELAPSEDSECONDS > 0.F ? m_framesInCurrentSecond / ELAPSEDSECONDS : 0.F; + + m_lastFPSPerSecond.emplace_back(std::clamp(FPSINWINDOW, 0.F, IDEALFPS)); + + if (m_lastFPSPerSecond.size() > OVERLAY_FPS_GRAPH_HISTORY_SEC) + m_lastFPSPerSecond.pop_front(); + + m_framesInCurrentSecond = 0; + m_fpsSecondStart = NOW; + } + + // anim data too + const auto PMONITORFORTICKS = g_pHyprRenderer->m_mostHzMonitor ? g_pHyprRenderer->m_mostHzMonitor.lock() : Desktop::focusState()->monitor(); + if (PMONITORFORTICKS && PMONITORFORTICKS == pMonitor) { + const auto TICKLIMIT = std::max(1, sc(std::ceil(std::max(1.F, PMONITORFORTICKS->m_refreshRate)))); + + if (m_lastAnimationTicks.size() > TICKLIMIT) + m_lastAnimationTicks.pop_front(); + + m_lastAnimationTicks.push_back(g_pAnimationManager->m_lastTickTimeMs); + } +} + +const CBox& CMonitorOverlay::lastDrawnBox() const { + return m_lastDrawnBox; +} + +void CMonitorOverlay::updateLine(size_t idx, const std::string& text, const CHyprColor& color, int fontSize, const std::string& fontFamily) { + if (m_cachedLines.size() <= idx) + m_cachedLines.resize(idx + 1); + + auto& line = m_cachedLines[idx]; + if (line.texture && line.text == text && line.fontSize == fontSize && line.color == color) + return; + + line.text = text; + line.color = color; + line.fontSize = fontSize; + line.texture = g_pHyprRenderer->renderText(text, color, fontSize, false, fontFamily); +} + +void CMonitorOverlay::rebuildCache() { + m_cachedLines.clear(); + + if (!m_monitor) + return; + + const auto PMONITOR = m_monitor.lock(); + if (!PMONITOR) + return; + + auto metricsFromSamples = [](const std::deque& samples) -> SMetricData { + SMetricData metric; + + if (samples.empty()) + return metric; + + metric.min = std::numeric_limits::max(); + metric.max = std::numeric_limits::lowest(); + + for (const auto sample : samples) { + metric.avg += sample; + metric.min = std::min(metric.min, sample); + metric.max = std::max(metric.max, sample); + } + + metric.avg /= samples.size(); + metric.var = metric.max - metric.min; + + return metric; + }; + + const auto FRAMEMETRICS = metricsFromSamples(m_lastFrametimes); + const auto RENDERMETRICS = metricsFromSamples(m_lastRenderTimes); + const auto RENDERMETRICSNOOVL = metricsFromSamples(m_lastRenderTimesNoOverlay); + const auto ANIMATIONTICKMETRICS = metricsFromSamples(m_lastAnimationTicks); + + const float FPS = FRAMEMETRICS.avg <= 0.F ? 0.F : 1000.F / FRAMEMETRICS.avg; + const float IDEALFPS = std::max(1.F, PMONITOR->m_refreshRate); + const float TICKTPS = ANIMATIONTICKMETRICS.avg <= 0.F ? 0.F : 1000.F / ANIMATIONTICKMETRICS.avg; + + static auto FONTFAMILY = CConfigValue("misc:font_family"); + + CHyprColor fpsColor = CHyprColor{1.F, 0.2F, 0.2F, 1.F}; + if (FPS > IDEALFPS * 0.95F) + fpsColor = CHyprColor{0.2F, 1.F, 0.2F, 1.F}; + else if (FPS > IDEALFPS * 0.8F) + fpsColor = CHyprColor{1.F, 1.F, 0.2F, 1.F}; + + size_t idx = 0; + updateLine(idx++, PMONITOR->m_name, CHyprColor{1.F, 1.F, 1.F, 1.F}, 10, *FONTFAMILY); + updateLine(idx++, std::format("{} FPS", sc(std::round(FPS))), fpsColor, 16, *FONTFAMILY); + updateLine(idx++, std::format("Avg Frametime: {:.2f}ms (var {:.2f}ms)", FRAMEMETRICS.avg, FRAMEMETRICS.var), CHyprColor{1.F, 1.F, 1.F, 1.F}, 10, *FONTFAMILY); + updateLine(idx++, std::format("Avg Rendertime: {:.2f}ms (var {:.2f}ms)", RENDERMETRICS.avg, RENDERMETRICS.var), CHyprColor{1.F, 1.F, 1.F, 1.F}, 10, *FONTFAMILY); + updateLine(idx++, std::format("Avg Rendertime (No Overlay): {:.2f}ms (var {:.2f}ms)", RENDERMETRICSNOOVL.avg, RENDERMETRICSNOOVL.var), CHyprColor{1.F, 1.F, 1.F, 1.F}, 10, + *FONTFAMILY); + updateLine(idx++, std::format("Avg Anim Tick: {:.2f}ms (var {:.2f}ms) ({:.2f} TPS)", ANIMATIONTICKMETRICS.avg, ANIMATIONTICKMETRICS.var, TICKTPS), + CHyprColor{1.F, 1.F, 1.F, 1.F}, 10, *FONTFAMILY); + + m_cachedLines.resize(idx); +} + +int CMonitorOverlay::draw(int offset, bool& cacheUpdated) { + cacheUpdated = false; + m_lastDrawnBox = {}; + + if (!m_monitor) + return 0; + + if (!m_cacheValid || m_cacheTimer.getMillis() >= OVERLAY_REFRESH_INTERVAL_MS) { + rebuildCache(); + m_cacheValid = true; + m_cacheTimer.reset(); + cacheUpdated = true; + } + + const auto PMONITOR = m_monitor.lock(); + const float IDEALFPS = PMONITOR ? std::max(1.F, PMONITOR->m_refreshRate) : 1.F; + + float y = offset + OVERLAY_MARGIN_TOP + OVERLAY_BOX_MARGIN; + float maxTextW = 0.F; + + for (size_t i = 0; i < m_cachedLines.size(); ++i) { + const auto& line = m_cachedLines[i]; + if (!line.texture) + continue; + + CTexPassElement::SRenderData data; + data.tex = line.texture; + data.box = {OVERLAY_MARGIN_LEFT + OVERLAY_BOX_MARGIN, y, line.texture->m_size.x, line.texture->m_size.y}; + data.a = 1.F; + g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(data))); + + maxTextW = std::max(maxTextW, sc(line.texture->m_size.x)); + y += line.texture->m_size.y + OVERLAY_LINE_GAP; + + if (i == 1) { + const auto GRAPHDRAW = drawFPSGraph(OVERLAY_MARGIN_LEFT + OVERLAY_BOX_MARGIN, y + OVERLAY_FPS_GRAPH_GAP_TOP, IDEALFPS, m_lastFPSPerSecond); + + maxTextW = std::max(maxTextW, GRAPHDRAW.width); + y = GRAPHDRAW.bottomY + OVERLAY_LINE_GAP; + } + } + + const float HEIGHT = y - offset - OVERLAY_MARGIN_TOP - OVERLAY_BOX_MARGIN; + if (maxTextW <= 0.F || HEIGHT <= 0.F) + return 0; + + m_lastDrawnBox = {OVERLAY_MARGIN_LEFT - 1 + OVERLAY_BOX_MARGIN, offset + OVERLAY_MARGIN_TOP + OVERLAY_BOX_MARGIN - 1, sc(std::ceil(maxTextW)) + 2, + sc(std::ceil(HEIGHT)) + 2}; + return sc(std::ceil(y - offset)); +} + +Vector2D CMonitorOverlay::size() const { + return m_lastDrawnBox.size(); // this shouldn't change much +} + +void COverlay::renderData(PHLMONITOR pMonitor, float durationUs) { + static auto PDEBUGOVERLAY = CConfigValue("debug:overlay"); + + if (!*PDEBUGOVERLAY) + return; + + m_monitorOverlays[pMonitor].renderData(pMonitor, durationUs); +} + +void COverlay::renderDataNoOverlay(PHLMONITOR pMonitor, float durationUs) { + static auto PDEBUGOVERLAY = CConfigValue("debug:overlay"); + + if (!*PDEBUGOVERLAY) + return; + + m_monitorOverlays[pMonitor].renderDataNoOverlay(pMonitor, durationUs); +} + +void COverlay::frameData(PHLMONITOR pMonitor) { + static auto PDEBUGOVERLAY = CConfigValue("debug:overlay"); + + if (!*PDEBUGOVERLAY) + return; + + m_monitorOverlays[pMonitor].frameData(pMonitor); +} + +void COverlay::createWarningTexture(float maxW) { + if (maxW <= 1) { + m_warningTexture.reset(); + m_warningTextureMaxW = 0; + return; + } + + if (maxW == m_warningTextureMaxW) + return; + + static auto FONT = CConfigValue("misc:font_family"); + + m_warningTexture = g_pHyprRenderer->renderText(Hyprgraphics::CTextResource::STextResourceData{ + .text = "[!] FPS might be below your monitor's refresh rate if there are no content updates", + .font = *FONT, + .fontSize = 8, + .color = Colors::YELLOW.asRGB(), + .maxSize = Vector2D{maxW, -1.F}, + }); +} + +void COverlay::draw() { + if (g_pCompositor->m_monitors.empty()) + return; + + const auto PMONITOR = g_pCompositor->m_monitors.front(); + if (!PMONITOR) + return; + + bool haveAnyBox = false; + bool cacheUpdated = false; + int minX = 0; + int minY = 0; + int maxX = 0; + int maxY = 0; + int offsetY = 0; + + float maxWidth = 0; + + // draw background first + { + Vector2D fullSize = {}; + int monitorsWithOverlayData = 0; + + for (const auto& m : g_pCompositor->m_monitors) { + const Vector2D size = m_monitorOverlays[m].size(); + if (size.x <= 0 || size.y <= 0) + continue; + + fullSize.y += size.y + OVERLAY_MONITOR_GAP; + fullSize.x = std::max(fullSize.x, size.x); + ++monitorsWithOverlayData; + } + + if (monitorsWithOverlayData > 0) { + fullSize.y -= OVERLAY_MONITOR_GAP; + + // Each monitor section is offset by OVERLAY_MARGIN_TOP + OVERLAY_BOX_MARGIN in CMonitorOverlay::draw, + // while the per-monitor drawn box height only tracks content (+2 px padding). + // Account for that inter-section offset so the backdrop spans stacked monitor overlays correctly. + fullSize.y += sc((monitorsWithOverlayData - 1) * (OVERLAY_MARGIN_TOP + OVERLAY_BOX_MARGIN - 2)); + } + + maxWidth = fullSize.x; + + if (fullSize.y > 1 && fullSize.x > 1) { + CRectPassElement::SRectData data; + data.box = CBox{{OVERLAY_MARGIN_LEFT, OVERLAY_MARGIN_TOP}, fullSize + Vector2D{OVERLAY_BOX_MARGIN, OVERLAY_BOX_MARGIN} * 2.F}; + data.color = CHyprColor{0.1F, 0.1F, 0.1F, 0.6F}; + data.round = 10; + data.blur = true; + g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(data))); + + createWarningTexture(fullSize.x); + } + } + + for (auto const& monitor : g_pCompositor->m_monitors) { + bool monitorUpdated = false; + offsetY += m_monitorOverlays[monitor].draw(offsetY, monitorUpdated); + cacheUpdated = cacheUpdated || monitorUpdated; + + const auto& BOX = m_monitorOverlays[monitor].lastDrawnBox(); + if (BOX.width > 0 && BOX.height > 0) { + const int boxMinX = sc(std::floor(BOX.x)); + const int boxMinY = sc(std::floor(BOX.y)); + const int boxMaxX = sc(std::ceil(BOX.x + BOX.width)); + const int boxMaxY = sc(std::ceil(BOX.y + BOX.height)); + + if (!haveAnyBox) { + minX = boxMinX; + minY = boxMinY; + maxX = boxMaxX; + maxY = boxMaxY; + haveAnyBox = true; + } else { + minX = std::min(minX, boxMinX); + minY = std::min(minY, boxMinY); + maxX = std::max(maxX, boxMaxX); + maxY = std::max(maxY, boxMaxY); + } + } + + offsetY += OVERLAY_MONITOR_GAP; + } + + offsetY -= OVERLAY_MONITOR_GAP; + + // render warning texture + if (m_warningTexture) { + { + CRectPassElement::SRectData data; + data.box = CBox{{OVERLAY_MARGIN_LEFT, offsetY + (OVERLAY_MARGIN_TOP * 2) + OVERLAY_BOX_MARGIN}, + {maxWidth + (OVERLAY_BOX_MARGIN * 2.F), m_warningTexture->m_size.y + (OVERLAY_BOX_MARGIN * 2.F)}}; + data.color = CHyprColor{0.1F, 0.1F, 0.1F, 0.6F}; + data.round = 10; + data.blur = true; + g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(data))); + } + + { + CTexPassElement::SRenderData data; + data.box = CBox{ + Vector2D{OVERLAY_MARGIN_LEFT + ((maxWidth - m_warningTexture->m_size.x) / 2.F), sc(offsetY) + (OVERLAY_MARGIN_TOP * 2) + (OVERLAY_BOX_MARGIN * 2)}.round(), + m_warningTexture->m_size}; + data.tex = m_warningTexture; + g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(data))); + } + } + + CBox newDrawnBox; + if (haveAnyBox) + newDrawnBox = {sc(PMONITOR->m_position.x) + minX, sc(PMONITOR->m_position.y) + minY, maxX - minX, maxY - minY}; + + if (cacheUpdated || newDrawnBox != m_lastDrawnBox) { + if (m_lastDrawnBox.width > 0 && m_lastDrawnBox.height > 0) + g_pHyprRenderer->damageBox(m_lastDrawnBox); + + if (newDrawnBox.width > 0 && newDrawnBox.height > 0) + g_pHyprRenderer->damageBox(newDrawnBox); + + m_lastDrawnBox = newDrawnBox; + } + + if (m_frameTimer.getMillis() >= OVERLAY_REFRESH_INTERVAL_MS) { + g_pCompositor->scheduleFrameForMonitor(PMONITOR); + m_frameTimer.reset(); + } +} diff --git a/src/debug/Overlay.hpp b/src/debug/Overlay.hpp new file mode 100644 index 000000000..02f7f60f5 --- /dev/null +++ b/src/debug/Overlay.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include "../defines.hpp" +#include "../render/Texture.hpp" +#include "../helpers/time/Timer.hpp" +#include +#include +#include +#include + +namespace Render { + class IHyprRenderer; + class ITexture; +} + +namespace Debug { + + class CMonitorOverlay { + public: + int draw(int offset, bool& cacheUpdated); + const CBox& lastDrawnBox() const; + + void renderData(PHLMONITOR pMonitor, float durationUs); + void renderDataNoOverlay(PHLMONITOR pMonitor, float durationUs); + void frameData(PHLMONITOR pMonitor); + + Vector2D size() const; + + private: + struct STextLine { + std::string text; + CHyprColor color; + int fontSize = 10; + SP texture; + }; + + struct SMetricData { + float avg = 0.F; + float min = 0.F; + float max = 0.F; + float var = 0.F; + }; + + void updateLine(size_t idx, const std::string& text, const CHyprColor& color, int fontSize, const std::string& fontFamily); + void rebuildCache(); + + std::deque m_lastFrametimes; + std::deque m_lastFPSPerSecond; + std::deque m_lastRenderTimes; + std::deque m_lastRenderTimesNoOverlay; + std::deque m_lastAnimationTicks; + std::chrono::high_resolution_clock::time_point m_lastFrame; + std::chrono::high_resolution_clock::time_point m_fpsSecondStart; + size_t m_framesInCurrentSecond = 0; + PHLMONITORREF m_monitor; + + std::vector m_cachedLines; + bool m_cacheValid = false; + CTimer m_cacheTimer; + + CBox m_lastDrawnBox; + + friend class Render::IHyprRenderer; + }; + + class COverlay { + public: + COverlay(); + void draw(); + void renderData(PHLMONITOR, float durationUs); + void renderDataNoOverlay(PHLMONITOR, float durationUs); + void frameData(PHLMONITOR); + + private: + std::map m_monitorOverlays; + CBox m_lastDrawnBox; + CTimer m_frameTimer; + + void createWarningTexture(float maxW); + SP m_warningTexture; + float m_warningTextureMaxW = 0; + + friend class CHyprMonitorDebugOverlay; + friend class Render::IHyprRenderer; + }; + + UP& overlay(); +} diff --git a/src/debug/TracyDefines.hpp b/src/debug/TracyDefines.hpp index 49d296f6c..d06332f33 100644 --- a/src/debug/TracyDefines.hpp +++ b/src/debug/TracyDefines.hpp @@ -2,7 +2,7 @@ #ifdef USE_TRACY_GPU -#include "Log.hpp" +#include "log/Logger.hpp" #include #include diff --git a/src/debug/CrashReporter.cpp b/src/debug/crash/CrashReporter.cpp similarity index 74% rename from src/debug/CrashReporter.cpp rename to src/debug/crash/CrashReporter.cpp index 9e8719037..5b7931f33 100644 --- a/src/debug/CrashReporter.cpp +++ b/src/debug/crash/CrashReporter.cpp @@ -6,54 +6,61 @@ #include #include #include -#include "../helpers/MiscFunctions.hpp" +#include "../../helpers/MiscFunctions.hpp" -#include "../plugins/PluginSystem.hpp" -#include "../signal-safe.hpp" +#include "../../plugins/PluginSystem.hpp" +#include "SignalSafe.hpp" #if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) #include #endif -static char const* const MESSAGES[] = {"Sorry, didn't mean to...", - "This was an accident, I swear!", - "Calm down, it was a misinput! MISINPUT!", - "Oops", - "Vaxry is going to be upset.", - "Who tried dividing by zero?!", - "Maybe you should try dusting your PC in the meantime?", - "I tried so hard, and got so far...", - "I don't feel so good...", - "*thud*", - "Well this is awkward.", - "\"stable\"", - "I hope you didn't have any unsaved progress.", - "All these computers..."}; +static char const* const MESSAGES[] = { + "Sorry, didn't mean to...", + "This was an accident, I swear!", + "Calm down, it was a misinput! MISINPUT!", + "Oops", + "Vaxry is going to be upset.", + "Who tried dividing by zero?!", + "Maybe you should try dusting your PC in the meantime?", + "I tried so hard, and got so far...", + "I don't feel so good...", + "*thud*", + "Well this is awkward.", + "\"stable\"", + "I hope you didn't have any unsaved progress.", + "All these computers...", + "The math isn't mathing...", + "We've got an imposter in the code!", + "Well, at least the crash reporter didn't crash!", + "Everything's just fi-", + "Have you tried asking Hyprland politely not to crash?", +}; // is not async-signal-safe, fake it with time(NULL) instead -char const* getRandomMessage() { +static char const* getRandomMessage() { return MESSAGES[time(nullptr) % (sizeof(MESSAGES) / sizeof(MESSAGES[0]))]; } -[[noreturn]] inline void exitWithError(char const* err) { - write(STDERR_FILENO, err, strlen(err)); +[[noreturn]] static inline void exitWithError(char const* err) { + [[maybe_unused]] auto w = write(STDERR_FILENO, err, strlen(err)); // perror() is not signal-safe, but we use it here // because if the crash-handler already crashed, it can't get any worse. perror(""); abort(); } -void NCrashReporter::createAndSaveCrash(int sig) { +void CrashReporter::createAndSaveCrash(int sig) { int reportFd = -1; // We're in the signal handler, so we *only* have stack memory. // To save as much stack memory as possible, // destroy things as soon as possible. { - CMaxLengthCString<255> reportPath; + SignalSafe::CMaxLengthCString<255> reportPath; - const auto HOME = sigGetenv("HOME"); - const auto CACHE_HOME = sigGetenv("XDG_CACHE_HOME"); + const auto HOME = SignalSafe::getenv("HOME"); + const auto CACHE_HOME = SignalSafe::getenv("XDG_CACHE_HOME"); if (CACHE_HOME && CACHE_HOME[0] != '\0') { reportPath += CACHE_HOME; @@ -67,32 +74,30 @@ void NCrashReporter::createAndSaveCrash(int sig) { } int ret = mkdir(reportPath.getStr(), S_IRWXU); - //__asm__("int $3"); - if (ret < 0 && errno != EEXIST) { + if (ret < 0 && errno != EEXIST) exitWithError("failed to mkdir() crash report directory\n"); - } + reportPath += "/hyprlandCrashReport"; reportPath.writeNum(getpid()); reportPath += ".txt"; { - CBufFileWriter<64> stderr_out(STDERR_FILENO); - stderr_out += "Hyprland has crashed :( Consult the crash report at "; - if (!reportPath.boundsExceeded()) { - stderr_out += reportPath.getStr(); - } else { - stderr_out += "[ERROR: Crash report path does not fit into memory! Check if your $CACHE_HOME/$HOME is too deeply nested. Max 255 characters.]"; - } - stderr_out += " for more information.\n"; - stderr_out.flush(); + SignalSafe::CBufFileWriter<64> stderrOut(STDERR_FILENO); + stderrOut += "Hyprland has crashed :( Consult the crash report at "; + if (!reportPath.boundsExceeded()) + stderrOut += reportPath.getStr(); + else + stderrOut += "[ERROR: Crash report path does not fit into memory! Check if your $CACHE_HOME/$HOME is too deeply nested. Max 255 characters.]"; + + stderrOut += " for more information.\n"; + stderrOut.flush(); } reportFd = open(reportPath.getStr(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); - if (reportFd < 0) { + if (reportFd < 0) exitWithError("Failed to open crash report path for writing"); - } } - CBufFileWriter<512> finalCrashReport(reportFd); + SignalSafe::CBufFileWriter<512> finalCrashReport(reportFd); finalCrashReport += "--------------------------------------------\n Hyprland Crash Report\n--------------------------------------------\n"; finalCrashReport += getRandomMessage(); @@ -101,7 +106,7 @@ void NCrashReporter::createAndSaveCrash(int sig) { finalCrashReport += "Hyprland received signal "; finalCrashReport.writeNum(sig); finalCrashReport += '('; - finalCrashReport += sigStrsignal(sig); + finalCrashReport += SignalSafe::strsignal(sig); finalCrashReport += ")\nVersion: "; finalCrashReport += GIT_COMMIT_HASH; finalCrashReport += "\nTag: "; @@ -243,5 +248,5 @@ void NCrashReporter::createAndSaveCrash(int sig) { finalCrashReport += "\n\nLog tail:\n"; - finalCrashReport += std::string_view(Debug::m_rollingLog).substr(Debug::m_rollingLog.find('\n') + 1); + finalCrashReport += Log::logger->rolling(); } diff --git a/src/debug/CrashReporter.hpp b/src/debug/crash/CrashReporter.hpp similarity index 50% rename from src/debug/CrashReporter.hpp rename to src/debug/crash/CrashReporter.hpp index 0ba48e7c5..661f702f0 100644 --- a/src/debug/CrashReporter.hpp +++ b/src/debug/crash/CrashReporter.hpp @@ -1,7 +1,5 @@ #pragma once -#include "../defines.hpp" - -namespace NCrashReporter { +namespace CrashReporter { void createAndSaveCrash(int sig); }; \ No newline at end of file diff --git a/src/signal-safe.cpp b/src/debug/crash/SignalSafe.cpp similarity index 78% rename from src/signal-safe.cpp rename to src/debug/crash/SignalSafe.cpp index baee7b44b..22717f1b0 100644 --- a/src/signal-safe.cpp +++ b/src/debug/crash/SignalSafe.cpp @@ -1,4 +1,4 @@ -#include "signal-safe.hpp" +#include "SignalSafe.hpp" #ifndef __GLIBC__ #include @@ -7,11 +7,13 @@ #include #include +using namespace SignalSafe; + // NOLINTNEXTLINE extern "C" char** environ; // -char const* sigGetenv(char const* name) { +char const* SignalSafe::getenv(char const* name) { const size_t len = strlen(name); for (char** var = environ; *var != nullptr; var++) { if (strncmp(*var, name, len) == 0 && (*var)[len] == '=') { @@ -21,7 +23,7 @@ char const* sigGetenv(char const* name) { return nullptr; } -char const* sigStrsignal(int sig) { +char const* SignalSafe::strsignal(int sig) { #ifdef __GLIBC__ return sigabbrev_np(sig); #elif defined(__DragonFly__) || defined(__FreeBSD__) diff --git a/src/debug/crash/SignalSafe.hpp b/src/debug/crash/SignalSafe.hpp new file mode 100644 index 000000000..8ec967fe4 --- /dev/null +++ b/src/debug/crash/SignalSafe.hpp @@ -0,0 +1,203 @@ +#pragma once + +#include "defines.hpp" +#include + +namespace SignalSafe { + template + class CMaxLengthCString { + public: + CMaxLengthCString() { + m_str[0] = '\0'; + } + + void operator+=(char const* rhs) { + write(rhs, strlen(rhs)); + } + + void write(char const* data, size_t len) { + if (m_boundsExceeded || m_strPos + len >= N) { + m_boundsExceeded = true; + return; + } + memcpy(m_str + m_strPos, data, len); + m_strPos += len; + m_str[m_strPos] = '\0'; + } + + void write(char c) { + if (m_boundsExceeded || m_strPos + 1 >= N) { + m_boundsExceeded = true; + return; + } + m_str[m_strPos] = c; + m_strPos++; + } + + void writeNum(size_t num) { + size_t d = 1; + + while (num / 10 >= d) { + d *= 10; + } + + while (num > 0) { + char c = '0' + (num / d); + write(c); + num %= d; + d /= 10; + } + } + + char const* getStr() { + return m_str; + } + + bool boundsExceeded() { + return m_boundsExceeded; + } + + private: + char m_str[N]; + size_t m_strPos = 0; + bool m_boundsExceeded = false; + }; + + template + class CBufFileWriter { + public: + CBufFileWriter(int fd_) : m_fd(fd_) { + ; + } + + ~CBufFileWriter() { + flush(); + } + + void write(char const* data, size_t len) { + while (len > 0) { + size_t to_add = std::min(len, sc(BUFSIZE) - m_writeBufPos); + memcpy(m_writeBuf + m_writeBufPos, data, to_add); + data += to_add; + len -= to_add; + m_writeBufPos += to_add; + if (m_writeBufPos == BUFSIZE) + flush(); + } + } + + void write(char c) { + if (m_writeBufPos == BUFSIZE) + flush(); + m_writeBuf[m_writeBufPos] = c; + m_writeBufPos++; + } + + void operator+=(char const* str) { + write(str, strlen(str)); + } + + void operator+=(std::string_view str) { + write(str.data(), str.size()); + } + + void operator+=(char c) { + write(c); + } + + void writeNum(size_t num) { + size_t d = 1; + + while (num / 10 >= d) { + d *= 10; + } + + while (num > 0) { + char c = '0' + (num / d); + write(c); + num %= d; + d /= 10; + } + } + + void writeCmdOutput(const char* cmd) { + int pipefd[2]; + if (pipe(pipefd) < 0) { + *this += "(argv)); + + CBufFileWriter<64> failmsg(pipefd[1]); + failmsg += " 0) { + write(readbuf, len); + } + if (len < 0) { + *this += "m_events.config.reloaded.listen([this]() { recheckCfg(); }); + recheckCfg(); +} + +void CLogger::recheckCfg() { + static auto PDISABLELOGS = CConfigValue("debug:disable_logs"); + static auto PDISABLETIME = CConfigValue("debug:disable_time"); + static auto PENABLESTDOUT = CConfigValue("debug:enable_stdout_logs"); + static auto PENABLECOLOR = CConfigValue("debug:colored_stdout_logs"); + + m_logger.setEnableStdout(!*PDISABLELOGS && *PENABLESTDOUT); + m_logsEnabled = !*PDISABLELOGS; + m_logger.setTime(!*PDISABLETIME); + m_logger.setEnableColor(*PENABLECOLOR); +} + +const std::string& CLogger::rolling() { + return m_logger.rollingLog(); +} + +Hyprutils::CLI::CLogger& CLogger::hu() { + return m_logger; +} diff --git a/src/debug/log/Logger.hpp b/src/debug/log/Logger.hpp new file mode 100644 index 000000000..d4e868deb --- /dev/null +++ b/src/debug/log/Logger.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include + +#include "../../helpers/memory/Memory.hpp" +#include "../../helpers/env/Env.hpp" + +namespace Log { + class CLogger { + public: + CLogger(); + ~CLogger() = default; + + void initIS(const std::string_view& IS); + void initCallbacks(); + + void log(Hyprutils::CLI::eLogLevel level, const std::string_view& str); + + template + //NOLINTNEXTLINE + void log(Hyprutils::CLI::eLogLevel level, std::format_string fmt, Args&&... args) { + static bool TRACE = Env::isTrace(); + + if (!m_logsEnabled) + return; + + if (level == Hyprutils::CLI::LOG_TRACE && !TRACE) + return; + + std::string logMsg = ""; + + // no need for try {} catch {} because std::format_string ensures that vformat never throw std::format_error + // because + // 1. any faulty format specifier that sucks will cause a compilation error. + // 2. and `std::bad_alloc` is catastrophic, (Almost any operation in stdlib could throw this.) + // 3. this is actually what std::format in stdlib does + logMsg += std::vformat(fmt.get(), std::make_format_args(args...)); + + log(level, logMsg); + } + + const std::string& rolling(); + Hyprutils::CLI::CLogger& hu(); + + private: + void recheckCfg(); + + Hyprutils::CLI::CLogger m_logger; + bool m_logsEnabled = true; + }; + + inline UP logger = makeUnique(); + + // + inline constexpr const Hyprutils::CLI::eLogLevel DEBUG = Hyprutils::CLI::LOG_DEBUG; + inline constexpr const Hyprutils::CLI::eLogLevel WARN = Hyprutils::CLI::LOG_WARN; + inline constexpr const Hyprutils::CLI::eLogLevel ERR = Hyprutils::CLI::LOG_ERR; + inline constexpr const Hyprutils::CLI::eLogLevel CRIT = Hyprutils::CLI::LOG_CRIT; + inline constexpr const Hyprutils::CLI::eLogLevel INFO = Hyprutils::CLI::LOG_DEBUG; + inline constexpr const Hyprutils::CLI::eLogLevel TRACE = Hyprutils::CLI::LOG_TRACE; +}; diff --git a/src/debug/RollingLogFollow.hpp b/src/debug/log/RollingLogFollow.hpp similarity index 88% rename from src/debug/RollingLogFollow.hpp rename to src/debug/log/RollingLogFollow.hpp index 07b4387d4..c1cce9eb6 100644 --- a/src/debug/RollingLogFollow.hpp +++ b/src/debug/log/RollingLogFollow.hpp @@ -1,9 +1,11 @@ #pragma once #include +#include +#include +#include -// NOLINTNEXTLINE(readability-identifier-naming) -namespace Debug { +namespace Log { struct SRollingLogFollow { std::unordered_map m_socketToRollingLogFollowQueue; std::shared_mutex m_mutex; @@ -30,12 +32,14 @@ namespace Debug { return ret; }; - void addLog(const std::string& log) { + void addLog(const std::string_view& log) { std::unique_lock w(m_mutex); m_running = true; std::vector to_erase; - for (const auto& p : m_socketToRollingLogFollowQueue) - m_socketToRollingLogFollowQueue[p.first] += log + "\n"; + for (const auto& p : m_socketToRollingLogFollowQueue) { + m_socketToRollingLogFollowQueue[p.first] += log; + m_socketToRollingLogFollowQueue[p.first] += "\n"; + } } bool isRunning() { diff --git a/src/defines.hpp b/src/defines.hpp index 5c70f21a0..571679dc8 100644 --- a/src/defines.hpp +++ b/src/defines.hpp @@ -1,7 +1,11 @@ #pragma once #include "includes.hpp" -#include "debug/Log.hpp" +#include "debug/log/Logger.hpp" #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/DesktopTypes.hpp b/src/desktop/DesktopTypes.hpp index f724c7b96..b52f17cdd 100644 --- a/src/desktop/DesktopTypes.hpp +++ b/src/desktop/DesktopTypes.hpp @@ -1,26 +1,30 @@ #pragma once #include "../helpers/memory/Memory.hpp" + class CWorkspace; -class CWindow; -class CLayerSurface; class CMonitor; +namespace Desktop::View { + class CWindow; + class CLayerSurface; +} + /* Shared pointer to a workspace */ using PHLWORKSPACE = SP; /* Weak pointer to a workspace */ using PHLWORKSPACEREF = WP; /* Shared pointer to a window */ -using PHLWINDOW = SP; +using PHLWINDOW = SP; /* Weak pointer to a window */ -using PHLWINDOWREF = WP; +using PHLWINDOWREF = WP; /* Shared pointer to a layer surface */ -using PHLLS = SP; +using PHLLS = SP; /* Weak pointer to a layer surface */ -using PHLLSREF = WP; +using PHLLSREF = WP; /* Shared pointer to a monitor */ using PHLMONITOR = SP; /* Weak pointer to a monitor */ -using PHLMONITORREF = WP; +using PHLMONITORREF = WP; \ No newline at end of file diff --git a/src/desktop/LayerSurface.hpp b/src/desktop/LayerSurface.hpp deleted file mode 100644 index 5676e4d21..000000000 --- a/src/desktop/LayerSurface.hpp +++ /dev/null @@ -1,95 +0,0 @@ -#pragma once - -#include -#include "../defines.hpp" -#include "WLSurface.hpp" -#include "rule/layerRule/LayerRuleApplicator.hpp" -#include "../helpers/AnimatedVariable.hpp" - -class CLayerShellResource; - -class CLayerSurface { - public: - static PHLLS create(SP); - - private: - CLayerSurface(SP); - - public: - ~CLayerSurface(); - - bool isFadedOut(); - int popupsCount(); - - PHLANIMVAR m_realPosition; - PHLANIMVAR m_realSize; - PHLANIMVAR m_alpha; - - WP m_layerSurface; - - // the header providing the enum type cannot be imported here - int m_interactivity = 0; - - SP m_surface; - - bool m_mapped = false; - uint32_t m_layer = 0; - - PHLMONITORREF m_monitor; - - bool m_fadingOut = false; - bool m_readyToDelete = false; - bool m_noProcess = false; - - UP m_ruleApplicator; - - PHLLSREF m_self; - - CBox m_geometry = {0, 0, 0, 0}; - Vector2D m_position; - std::string m_namespace = ""; - UP m_popupHead; - - pid_t getPID(); - - void onDestroy(); - void onMap(); - void onUnmap(); - void onCommit(); - MONITORID monitorID(); - - private: - struct { - CHyprSignalListener destroy; - CHyprSignalListener map; - CHyprSignalListener unmap; - CHyprSignalListener commit; - } m_listeners; - - void registerCallbacks(); - - // For the list lookup - bool operator==(const CLayerSurface& rhs) const { - return m_layerSurface == rhs.m_layerSurface && m_monitor == rhs.m_monitor; - } -}; - -inline bool valid(PHLLS l) { - return l; -} - -inline bool valid(PHLLSREF l) { - return l; -} - -inline bool validMapped(PHLLS l) { - if (!valid(l)) - return false; - return l->m_mapped; -} - -inline bool validMapped(PHLLSREF l) { - if (!valid(l)) - return false; - return l->m_mapped; -} diff --git a/src/desktop/Popup.hpp b/src/desktop/Popup.hpp deleted file mode 100644 index 964b36b6c..000000000 --- a/src/desktop/Popup.hpp +++ /dev/null @@ -1,96 +0,0 @@ -#pragma once - -#include -#include "Subsurface.hpp" -#include "../helpers/signal/Signal.hpp" -#include "../helpers/memory/Memory.hpp" -#include "../helpers/AnimatedVariable.hpp" - -class CXDGPopupResource; - -class CPopup { - public: - // dummy head nodes - static UP create(PHLWINDOW pOwner); - static UP create(PHLLS pOwner); - - // real nodes - static UP create(SP popup, WP pOwner); - - ~CPopup(); - - SP getT1Owner(); - Vector2D coordsRelativeToParent(); - Vector2D coordsGlobal(); - PHLMONITOR getMonitor(); - - Vector2D size(); - - void onNewPopup(SP popup); - void onDestroy(); - void onMap(); - void onUnmap(); - void onCommit(bool ignoreSiblings = false); - void onReposition(); - - void recheckTree(); - - bool visible(); - bool inert() const; - - // will also loop over this node - void breadthfirst(std::function, void*)> fn, void* data); - WP at(const Vector2D& globalCoords, bool allowsInput = false); - - // - SP m_wlSurface; - WP m_self; - bool m_mapped = false; - - // fade in-out - PHLANIMVAR m_alpha; - bool m_fadingOut = false; - - private: - CPopup() = default; - - // T1 owners, each popup has to have one of these - PHLWINDOWREF m_windowOwner; - PHLLSREF m_layerOwner; - - // T2 owners - WP m_parent; - - WP m_resource; - - Vector2D m_lastSize = {}; - Vector2D m_lastPos = {}; - - bool m_requestedReposition = false; - - bool m_inert = false; - - // - std::vector> m_children; - UP m_subsurfaceHead; - - struct { - CHyprSignalListener newPopup; - CHyprSignalListener destroy; - CHyprSignalListener map; - CHyprSignalListener unmap; - CHyprSignalListener commit; - CHyprSignalListener dismissed; - CHyprSignalListener reposition; - } m_listeners; - - void initAllSignals(); - void reposition(); - void recheckChildrenRecursive(); - void sendScale(); - void fullyDestroy(); - - Vector2D localToGlobal(const Vector2D& rel); - Vector2D t1ParentCoords(); - static void bfHelper(std::vector> const& nodes, std::function, void*)> fn, void* data); -}; diff --git a/src/desktop/Subsurface.hpp b/src/desktop/Subsurface.hpp deleted file mode 100644 index 7c42dad9d..000000000 --- a/src/desktop/Subsurface.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -#include "../defines.hpp" -#include -#include "WLSurface.hpp" - -class CPopup; -class CWLSubsurfaceResource; - -class CSubsurface { - public: - // root dummy nodes - static UP create(PHLWINDOW pOwner); - static UP create(WP pOwner); - - // real nodes - static UP create(SP pSubsurface, PHLWINDOW pOwner); - static UP create(SP pSubsurface, WP pOwner); - - ~CSubsurface() = default; - - Vector2D coordsRelativeToParent(); - Vector2D coordsGlobal(); - - Vector2D size(); - - void onCommit(); - void onDestroy(); - void onNewSubsurface(SP pSubsurface); - void onMap(); - void onUnmap(); - - bool visible(); - - void recheckDamageForSubsurfaces(); - - WP m_self; - - private: - CSubsurface() = default; - - struct { - CHyprSignalListener destroySubsurface; - CHyprSignalListener commitSubsurface; - CHyprSignalListener mapSubsurface; - CHyprSignalListener unmapSubsurface; - CHyprSignalListener newSubsurface; - } m_listeners; - - WP m_subsurface; - SP m_wlSurface; - Vector2D m_lastSize = {}; - Vector2D m_lastPosition = {}; - - // if nullptr, means it's a dummy node - WP m_parent; - - PHLWINDOWREF m_windowParent; - WP m_popupParent; - - std::vector> m_children; - - bool m_inert = false; - - void initSignals(); - void initExistingSubsurfaces(SP pSurface); - void checkSiblingDamage(); - void damageLastArea(); -}; diff --git a/src/desktop/WLSurface.hpp b/src/desktop/WLSurface.hpp deleted file mode 100644 index 4d26d5092..000000000 --- a/src/desktop/WLSurface.hpp +++ /dev/null @@ -1,124 +0,0 @@ -#pragma once - -#include "../defines.hpp" -#include "../helpers/math/Math.hpp" -#include "../helpers/signal/Signal.hpp" - -class CSubsurface; -class CPopup; -class CPointerConstraint; -class CWLSurfaceResource; - -class CWLSurface { - public: - static SP create() { - auto p = SP(new CWLSurface); - p->m_self = p; - return p; - } - ~CWLSurface(); - - // anonymous surfaces are non-desktop components, e.g. a cursor surface or a DnD - void assign(SP pSurface); - void assign(SP pSurface, PHLWINDOW pOwner); - void assign(SP pSurface, PHLLS pOwner); - void assign(SP pSurface, CSubsurface* pOwner); - void assign(SP pSurface, CPopup* pOwner); - void unassign(); - - CWLSurface(const CWLSurface&) = delete; - CWLSurface(CWLSurface&&) = delete; - CWLSurface& operator=(const CWLSurface&) = delete; - CWLSurface& operator=(CWLSurface&&) = delete; - - SP resource() const; - bool exists() const; - bool small() const; // means surface is smaller than the requested size - Vector2D correctSmallVec() const; // returns a corrective vector for small() surfaces - Vector2D correctSmallVecBuf() const; // returns a corrective vector for small() surfaces, in BL coords - Vector2D getViewporterCorrectedSize() const; - CRegion computeDamage() const; // logical coordinates. May be wrong if the surface is unassigned - bool visible(); - bool keyboardFocusable() const; - - // getters for owners. - PHLWINDOW getWindow() const; - PHLLS getLayer() const; - CPopup* getPopup() const; - CSubsurface* getSubsurface() const; - - // desktop components misc utils - std::optional getSurfaceBoxGlobal() const; - void appendConstraint(WP constraint); - SP constraint() const; - - // allow stretching. Useful for plugins. - bool m_fillIgnoreSmall = false; - - // track surface data and avoid dupes - float m_lastScaleFloat = 0; - int m_lastScaleInt = 0; - wl_output_transform m_lastTransform = sc(-1); - - // - CWLSurface& operator=(SP pSurface) { - destroy(); - m_resource = pSurface; - init(); - - return *this; - } - - bool operator==(const CWLSurface& other) const { - return other.resource() == resource(); - } - - bool operator==(const SP other) const { - return other == resource(); - } - - explicit operator bool() const { - return exists(); - } - - static SP fromResource(SP pSurface); - - // used by the alpha-modifier protocol - float m_alphaModifier = 1.F; - - // used by the hyprland-surface protocol - float m_overallOpacity = 1.F; - CRegion m_visibleRegion; - - struct { - CSignalT<> destroy; - } m_events; - - WP m_self; - - private: - CWLSurface() = default; - - bool m_inert = true; - - WP m_resource; - - PHLWINDOWREF m_windowOwner; - PHLLSREF m_layerOwner; - CPopup* m_popupOwner = nullptr; - CSubsurface* m_subsurfaceOwner = nullptr; - - // - WP m_constraint; - - void destroy(); - void init(); - bool desktopComponent() const; - - struct { - CHyprSignalListener destroy; - } m_listeners; - - friend class CPointerConstraint; - friend class CXxColorManagerV4; -}; diff --git a/src/desktop/Window.cpp b/src/desktop/Window.cpp deleted file mode 100644 index 8a0e37a6c..000000000 --- a/src/desktop/Window.cpp +++ /dev/null @@ -1,1879 +0,0 @@ -#include -#include -#include -#include - -#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) -#include -#include -#endif - -#include -#include -#include -#include "Window.hpp" -#include "state/FocusState.hpp" -#include "../Compositor.hpp" -#include "../render/decorations/CHyprDropShadowDecoration.hpp" -#include "../render/decorations/CHyprGroupBarDecoration.hpp" -#include "../render/decorations/CHyprBorderDecoration.hpp" -#include "../config/ConfigValue.hpp" -#include "../config/ConfigManager.hpp" -#include "../managers/TokenManager.hpp" -#include "../managers/animation/AnimationManager.hpp" -#include "../managers/ANRManager.hpp" -#include "../managers/eventLoop/EventLoopManager.hpp" -#include "../protocols/XDGShell.hpp" -#include "../protocols/core/Compositor.hpp" -#include "../protocols/core/Subcompositor.hpp" -#include "../protocols/ContentType.hpp" -#include "../protocols/FractionalScale.hpp" -#include "../xwayland/XWayland.hpp" -#include "../helpers/Color.hpp" -#include "../helpers/math/Expression.hpp" -#include "../events/Events.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 - -using namespace Hyprutils::String; -using namespace Hyprutils::Animation; -using enum NContentType::eContentType; - -PHLWINDOW CWindow::create(SP surface) { - PHLWINDOW pWindow = SP(new CWindow(surface)); - - pWindow->m_self = pWindow; - pWindow->m_isX11 = true; - pWindow->m_ruleApplicator = makeUnique(pWindow); - - g_pAnimationManager->createAnimation(Vector2D(0, 0), pWindow->m_realPosition, g_pConfigManager->getAnimationPropertyConfig("windowsIn"), pWindow, AVARDAMAGE_ENTIRE); - g_pAnimationManager->createAnimation(Vector2D(0, 0), pWindow->m_realSize, g_pConfigManager->getAnimationPropertyConfig("windowsIn"), pWindow, AVARDAMAGE_ENTIRE); - g_pAnimationManager->createAnimation(0.f, pWindow->m_borderFadeAnimationProgress, g_pConfigManager->getAnimationPropertyConfig("border"), pWindow, AVARDAMAGE_BORDER); - g_pAnimationManager->createAnimation(0.f, pWindow->m_borderAngleAnimationProgress, g_pConfigManager->getAnimationPropertyConfig("borderangle"), pWindow, AVARDAMAGE_BORDER); - g_pAnimationManager->createAnimation(1.f, pWindow->m_alpha, g_pConfigManager->getAnimationPropertyConfig("fadeIn"), pWindow, AVARDAMAGE_ENTIRE); - g_pAnimationManager->createAnimation(1.f, pWindow->m_activeInactiveAlpha, g_pConfigManager->getAnimationPropertyConfig("fadeSwitch"), pWindow, AVARDAMAGE_ENTIRE); - g_pAnimationManager->createAnimation(CHyprColor(), pWindow->m_realShadowColor, g_pConfigManager->getAnimationPropertyConfig("fadeShadow"), pWindow, AVARDAMAGE_SHADOW); - g_pAnimationManager->createAnimation(0.f, pWindow->m_dimPercent, g_pConfigManager->getAnimationPropertyConfig("fadeDim"), pWindow, AVARDAMAGE_ENTIRE); - g_pAnimationManager->createAnimation(0.f, pWindow->m_movingToWorkspaceAlpha, g_pConfigManager->getAnimationPropertyConfig("fadeOut"), pWindow, AVARDAMAGE_ENTIRE); - g_pAnimationManager->createAnimation(0.f, pWindow->m_movingFromWorkspaceAlpha, g_pConfigManager->getAnimationPropertyConfig("fadeIn"), pWindow, AVARDAMAGE_ENTIRE); - g_pAnimationManager->createAnimation(0.f, pWindow->m_notRespondingTint, g_pConfigManager->getAnimationPropertyConfig("fade"), pWindow, AVARDAMAGE_ENTIRE); - - pWindow->addWindowDeco(makeUnique(pWindow)); - pWindow->addWindowDeco(makeUnique(pWindow)); - - return pWindow; -} - -PHLWINDOW CWindow::create(SP resource) { - PHLWINDOW pWindow = SP(new CWindow(resource)); - - pWindow->m_self = pWindow; - resource->m_toplevel->m_window = pWindow; - pWindow->m_ruleApplicator = makeUnique(pWindow); - - g_pAnimationManager->createAnimation(Vector2D(0, 0), pWindow->m_realPosition, g_pConfigManager->getAnimationPropertyConfig("windowsIn"), pWindow, AVARDAMAGE_ENTIRE); - g_pAnimationManager->createAnimation(Vector2D(0, 0), pWindow->m_realSize, g_pConfigManager->getAnimationPropertyConfig("windowsIn"), pWindow, AVARDAMAGE_ENTIRE); - g_pAnimationManager->createAnimation(0.f, pWindow->m_borderFadeAnimationProgress, g_pConfigManager->getAnimationPropertyConfig("border"), pWindow, AVARDAMAGE_BORDER); - g_pAnimationManager->createAnimation(0.f, pWindow->m_borderAngleAnimationProgress, g_pConfigManager->getAnimationPropertyConfig("borderangle"), pWindow, AVARDAMAGE_BORDER); - g_pAnimationManager->createAnimation(1.f, pWindow->m_alpha, g_pConfigManager->getAnimationPropertyConfig("fadeIn"), pWindow, AVARDAMAGE_ENTIRE); - g_pAnimationManager->createAnimation(1.f, pWindow->m_activeInactiveAlpha, g_pConfigManager->getAnimationPropertyConfig("fadeSwitch"), pWindow, AVARDAMAGE_ENTIRE); - g_pAnimationManager->createAnimation(CHyprColor(), pWindow->m_realShadowColor, g_pConfigManager->getAnimationPropertyConfig("fadeShadow"), pWindow, AVARDAMAGE_SHADOW); - g_pAnimationManager->createAnimation(0.f, pWindow->m_dimPercent, g_pConfigManager->getAnimationPropertyConfig("fadeDim"), pWindow, AVARDAMAGE_ENTIRE); - g_pAnimationManager->createAnimation(0.f, pWindow->m_movingToWorkspaceAlpha, g_pConfigManager->getAnimationPropertyConfig("fadeOut"), pWindow, AVARDAMAGE_ENTIRE); - g_pAnimationManager->createAnimation(0.f, pWindow->m_movingFromWorkspaceAlpha, g_pConfigManager->getAnimationPropertyConfig("fadeIn"), pWindow, AVARDAMAGE_ENTIRE); - g_pAnimationManager->createAnimation(0.f, pWindow->m_notRespondingTint, g_pConfigManager->getAnimationPropertyConfig("fade"), pWindow, AVARDAMAGE_ENTIRE); - - pWindow->addWindowDeco(makeUnique(pWindow)); - pWindow->addWindowDeco(makeUnique(pWindow)); - - pWindow->m_wlSurface->assign(pWindow->m_xdgSurface->m_surface.lock(), pWindow); - - return pWindow; -} - -CWindow::CWindow(SP resource) : m_xdgSurface(resource) { - m_wlSurface = CWLSurface::create(); - - m_listeners.map = m_xdgSurface->m_events.map.listen([this] { Events::listener_mapWindow(this, nullptr); }); - 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] { Events::listener_unmapWindow(this, nullptr); }); - m_listeners.destroy = m_xdgSurface->m_events.destroy.listen([this] { Events::listener_destroyWindow(this, nullptr); }); - m_listeners.commit = m_xdgSurface->m_events.commit.listen([this] { Events::listener_commitWindow(this, nullptr); }); - m_listeners.updateState = m_xdgSurface->m_toplevel->m_events.stateChanged.listen([this] { onUpdateState(); }); - m_listeners.updateMetadata = m_xdgSurface->m_toplevel->m_events.metadataChanged.listen([this] { onUpdateMeta(); }); -} - -CWindow::CWindow(SP surface) : m_xwaylandSurface(surface) { - m_wlSurface = CWLSurface::create(); - - m_listeners.map = m_xwaylandSurface->m_events.map.listen([this] { Events::listener_mapWindow(this, nullptr); }); - m_listeners.unmap = m_xwaylandSurface->m_events.unmap.listen([this] { Events::listener_unmapWindow(this, nullptr); }); - m_listeners.destroy = m_xwaylandSurface->m_events.destroy.listen([this] { Events::listener_destroyWindow(this, nullptr); }); - m_listeners.commit = m_xwaylandSurface->m_events.commit.listen([this] { Events::listener_commitWindow(this, nullptr); }); - m_listeners.configureRequest = m_xwaylandSurface->m_events.configureRequest.listen([this](const CBox& box) { onX11ConfigureRequest(box); }); - m_listeners.updateState = m_xwaylandSurface->m_events.stateChanged.listen([this] { onUpdateState(); }); - m_listeners.updateMetadata = m_xwaylandSurface->m_events.metadataChanged.listen([this] { onUpdateMeta(); }); - m_listeners.resourceChange = m_xwaylandSurface->m_events.resourceChange.listen([this] { onResourceChangeX11(); }); - m_listeners.activate = m_xwaylandSurface->m_events.activate.listen([this] { Events::listener_activateX11(this, nullptr); }); - - if (m_xwaylandSurface->m_overrideRedirect) - m_listeners.setGeometry = m_xwaylandSurface->m_events.setGeometry.listen([this] { Events::listener_unmanagedSetGeometry(this, nullptr); }); -} - -CWindow::~CWindow() { - if (Desktop::focusState()->window() == m_self) { - Desktop::focusState()->surface().reset(); - Desktop::focusState()->window().reset(); - } - - m_events.destroy.emit(); - - if (!g_pHyprOpenGL) - return; - - g_pHyprRenderer->makeEGLCurrent(); - std::erase_if(g_pHyprOpenGL->m_windowFramebuffers, [&](const auto& other) { return other.first.expired() || other.first.get() == this; }); -} - -SBoxExtents CWindow::getFullWindowExtents() { - if (m_fadingOut) - return m_originalClosedExtents; - - const int BORDERSIZE = getRealBorderSize(); - - if (m_ruleApplicator->dimAround().valueOrDefault()) { - if (const auto PMONITOR = m_monitor.lock(); PMONITOR) - return {.topLeft = {m_realPosition->value().x - PMONITOR->m_position.x, m_realPosition->value().y - PMONITOR->m_position.y}, - .bottomRight = {PMONITOR->m_size.x - (m_realPosition->value().x - PMONITOR->m_position.x), - PMONITOR->m_size.y - (m_realPosition->value().y - PMONITOR->m_position.y)}}; - } - - SBoxExtents maxExtents = {.topLeft = {BORDERSIZE + 2, BORDERSIZE + 2}, .bottomRight = {BORDERSIZE + 2, BORDERSIZE + 2}}; - - const auto EXTENTS = g_pDecorationPositioner->getWindowDecorationExtents(m_self); - - maxExtents.topLeft.x = std::max(EXTENTS.topLeft.x, maxExtents.topLeft.x); - - maxExtents.topLeft.y = std::max(EXTENTS.topLeft.y, maxExtents.topLeft.y); - - maxExtents.bottomRight.x = std::max(EXTENTS.bottomRight.x, maxExtents.bottomRight.x); - - maxExtents.bottomRight.y = std::max(EXTENTS.bottomRight.y, maxExtents.bottomRight.y); - - if (m_wlSurface->exists() && !m_isX11 && m_popupHead) { - CBox surfaceExtents = {0, 0, 0, 0}; - // TODO: this could be better, perhaps make a getFullWindowRegion? - m_popupHead->breadthfirst( - [](WP popup, void* data) { - if (!popup->m_wlSurface || !popup->m_wlSurface->resource()) - return; - - CBox* pSurfaceExtents = sc(data); - CBox surf = CBox{popup->coordsRelativeToParent(), popup->size()}; - pSurfaceExtents->x = std::min(surf.x, pSurfaceExtents->x); - pSurfaceExtents->y = std::min(surf.y, pSurfaceExtents->y); - if (surf.x + surf.w > pSurfaceExtents->width) - pSurfaceExtents->width = surf.x + surf.w - pSurfaceExtents->x; - if (surf.y + surf.h > pSurfaceExtents->height) - pSurfaceExtents->height = surf.y + surf.h - pSurfaceExtents->y; - }, - &surfaceExtents); - - maxExtents.topLeft.x = std::max(-surfaceExtents.x, maxExtents.topLeft.x); - - maxExtents.topLeft.y = std::max(-surfaceExtents.y, maxExtents.topLeft.y); - - if (surfaceExtents.x + surfaceExtents.width > m_wlSurface->resource()->m_current.size.x + maxExtents.bottomRight.x) - maxExtents.bottomRight.x = surfaceExtents.x + surfaceExtents.width - m_wlSurface->resource()->m_current.size.x; - - if (surfaceExtents.y + surfaceExtents.height > m_wlSurface->resource()->m_current.size.y + maxExtents.bottomRight.y) - maxExtents.bottomRight.y = surfaceExtents.y + surfaceExtents.height - m_wlSurface->resource()->m_current.size.y; - } - - return maxExtents; -} - -CBox CWindow::getFullWindowBoundingBox() { - if (m_ruleApplicator->dimAround().valueOrDefault()) { - if (const auto PMONITOR = m_monitor.lock(); PMONITOR) - return {PMONITOR->m_position.x, PMONITOR->m_position.y, PMONITOR->m_size.x, PMONITOR->m_size.y}; - } - - auto maxExtents = getFullWindowExtents(); - - CBox finalBox = {m_realPosition->value().x - maxExtents.topLeft.x, m_realPosition->value().y - maxExtents.topLeft.y, - m_realSize->value().x + maxExtents.topLeft.x + maxExtents.bottomRight.x, m_realSize->value().y + maxExtents.topLeft.y + maxExtents.bottomRight.y}; - - return finalBox; -} - -CBox CWindow::getWindowIdealBoundingBoxIgnoreReserved() { - const auto PMONITOR = m_monitor.lock(); - - if (!PMONITOR) - return {m_position, m_size}; - - auto POS = m_position; - auto SIZE = m_size; - - if (isFullscreen()) { - POS = PMONITOR->m_position; - SIZE = PMONITOR->m_size; - - return CBox{sc(POS.x), sc(POS.y), sc(SIZE.x), sc(SIZE.y)}; - } - - if (DELTALESSTHAN(POS.y - PMONITOR->m_position.y, PMONITOR->m_reservedTopLeft.y, 1)) { - POS.y = PMONITOR->m_position.y; - SIZE.y += PMONITOR->m_reservedTopLeft.y; - } - if (DELTALESSTHAN(POS.x - PMONITOR->m_position.x, PMONITOR->m_reservedTopLeft.x, 1)) { - POS.x = PMONITOR->m_position.x; - SIZE.x += PMONITOR->m_reservedTopLeft.x; - } - if (DELTALESSTHAN(POS.x + SIZE.x - PMONITOR->m_position.x, PMONITOR->m_size.x - PMONITOR->m_reservedBottomRight.x, 1)) { - SIZE.x += PMONITOR->m_reservedBottomRight.x; - } - if (DELTALESSTHAN(POS.y + SIZE.y - PMONITOR->m_position.y, PMONITOR->m_size.y - PMONITOR->m_reservedBottomRight.y, 1)) { - SIZE.y += PMONITOR->m_reservedBottomRight.y; - } - - return CBox{sc(POS.x), sc(POS.y), sc(SIZE.x), sc(SIZE.y)}; -} - -SBoxExtents CWindow::getWindowExtentsUnified(uint64_t properties) { - SBoxExtents extents = {.topLeft = {0, 0}, .bottomRight = {0, 0}}; - if (properties & RESERVED_EXTENTS) - extents.addExtents(g_pDecorationPositioner->getWindowDecorationReserved(m_self)); - if (properties & INPUT_EXTENTS) - extents.addExtents(g_pDecorationPositioner->getWindowDecorationExtents(m_self, true)); - if (properties & FULL_EXTENTS) - extents.addExtents(g_pDecorationPositioner->getWindowDecorationExtents(m_self, false)); - - return extents; -} - -CBox CWindow::getWindowBoxUnified(uint64_t properties) { - if (m_ruleApplicator->dimAround().valueOrDefault()) { - const auto PMONITOR = m_monitor.lock(); - if (PMONITOR) - return {PMONITOR->m_position.x, PMONITOR->m_position.y, PMONITOR->m_size.x, PMONITOR->m_size.y}; - } - - const auto POS = m_realPosition->value(); - const auto SIZE = m_realSize->value(); - - CBox box{POS, SIZE}; - box.addExtents(getWindowExtentsUnified(properties)); - - return box; -} - -SBoxExtents CWindow::getFullWindowReservedArea() { - return g_pDecorationPositioner->getWindowDecorationReserved(m_self); -} - -void CWindow::updateWindowDecos() { - - if (!m_isMapped || isHidden()) - return; - - for (auto const& wd : m_decosToRemove) { - for (auto it = m_windowDecorations.begin(); it != m_windowDecorations.end(); it++) { - if (it->get() == wd) { - g_pDecorationPositioner->uncacheDecoration(it->get()); - it = m_windowDecorations.erase(it); - if (it == m_windowDecorations.end()) - break; - } - } - } - - g_pDecorationPositioner->onWindowUpdate(m_self.lock()); - - m_decosToRemove.clear(); - - // make a copy because updateWindow can remove decos. - std::vector decos; - // reserve to avoid reallocations - decos.reserve(m_windowDecorations.size()); - - for (auto const& wd : m_windowDecorations) { - decos.push_back(wd.get()); - } - - for (auto const& wd : decos) { - if (std::ranges::find_if(m_windowDecorations, [wd](const auto& other) { return other.get() == wd; }) == m_windowDecorations.end()) - continue; - wd->updateWindow(m_self.lock()); - } -} - -void CWindow::addWindowDeco(UP deco) { - m_windowDecorations.emplace_back(std::move(deco)); - g_pDecorationPositioner->forceRecalcFor(m_self.lock()); - updateWindowDecos(); - g_pLayoutManager->getCurrentLayout()->recalculateWindow(m_self.lock()); -} - -void CWindow::removeWindowDeco(IHyprWindowDecoration* deco) { - m_decosToRemove.push_back(deco); - g_pDecorationPositioner->forceRecalcFor(m_self.lock()); - updateWindowDecos(); - g_pLayoutManager->getCurrentLayout()->recalculateWindow(m_self.lock()); -} - -void CWindow::uncacheWindowDecos() { - for (auto const& wd : m_windowDecorations) { - g_pDecorationPositioner->uncacheDecoration(wd.get()); - } -} - -bool CWindow::checkInputOnDecos(const eInputType type, const Vector2D& mouseCoords, std::any data) { - if (type != INPUT_TYPE_DRAG_END && hasPopupAt(mouseCoords)) - return false; - - for (auto const& wd : m_windowDecorations) { - if (!(wd->getDecorationFlags() & DECORATION_ALLOWS_MOUSE_INPUT)) - continue; - - if (!g_pDecorationPositioner->getWindowDecorationBox(wd.get()).containsPoint(mouseCoords)) - continue; - - if (wd->onInputOnDeco(type, mouseCoords, data)) - return true; - } - - return false; -} - -pid_t CWindow::getPID() { - pid_t PID = -1; - if (!m_isX11) { - if (!m_xdgSurface || !m_xdgSurface->m_owner /* happens at unmap */) - return -1; - - wl_client_get_credentials(m_xdgSurface->m_owner->client(), &PID, nullptr, nullptr); - } else { - if (!m_xwaylandSurface) - return -1; - - PID = m_xwaylandSurface->m_pid; - } - - return PID; -} - -IHyprWindowDecoration* CWindow::getDecorationByType(eDecorationType type) { - for (auto const& wd : m_windowDecorations) { - if (wd->getDecorationType() == type) - return wd.get(); - } - - return nullptr; -} - -void CWindow::updateToplevel() { - updateSurfaceScaleTransformDetails(); -} - -void CWindow::updateSurfaceScaleTransformDetails(bool force) { - if (!m_isMapped || m_hidden || g_pCompositor->m_unsafeState) - return; - - const auto PLASTMONITOR = g_pCompositor->getMonitorFromID(m_lastSurfaceMonitorID); - - m_lastSurfaceMonitorID = monitorID(); - - const auto PNEWMONITOR = m_monitor.lock(); - - if (!PNEWMONITOR) - return; - - if (PNEWMONITOR != PLASTMONITOR || force) { - if (PLASTMONITOR && PLASTMONITOR->m_enabled && PNEWMONITOR != PLASTMONITOR) - m_wlSurface->resource()->breadthfirst([PLASTMONITOR](SP s, const Vector2D& offset, void* d) { s->leave(PLASTMONITOR->m_self.lock()); }, nullptr); - - m_wlSurface->resource()->breadthfirst([PNEWMONITOR](SP s, const Vector2D& offset, void* d) { s->enter(PNEWMONITOR->m_self.lock()); }, nullptr); - } - - const auto PMONITOR = m_monitor.lock(); - - m_wlSurface->resource()->breadthfirst( - [PMONITOR](SP s, const Vector2D& offset, void* d) { - const auto PSURFACE = CWLSurface::fromResource(s); - if (PSURFACE && PSURFACE->m_lastScaleFloat == PMONITOR->m_scale) - return; - - PROTO::fractional->sendScale(s, PMONITOR->m_scale); - g_pCompositor->setPreferredScaleForSurface(s, PMONITOR->m_scale); - g_pCompositor->setPreferredTransformForSurface(s, PMONITOR->m_transform); - }, - nullptr); -} - -void CWindow::moveToWorkspace(PHLWORKSPACE pWorkspace) { - if (m_workspace == pWorkspace) - return; - - static auto PINITIALWSTRACKING = CConfigValue("misc:initial_workspace_tracking"); - - if (!m_initialWorkspaceToken.empty()) { - const auto TOKEN = g_pTokenManager->getToken(m_initialWorkspaceToken); - if (TOKEN) { - if (*PINITIALWSTRACKING == 2) { - // persistent - SInitialWorkspaceToken token = std::any_cast(TOKEN->m_data); - if (token.primaryOwner == m_self) { - token.workspace = pWorkspace->getConfigName(); - TOKEN->m_data = token; - } - } - } - } - - static auto PCLOSEONLASTSPECIAL = CConfigValue("misc:close_special_on_empty"); - - const auto OLDWORKSPACE = m_workspace; - - if (OLDWORKSPACE->isVisible()) { - m_movingToWorkspaceAlpha->setValueAndWarp(1.F); - *m_movingToWorkspaceAlpha = 0.F; - m_movingToWorkspaceAlpha->setCallbackOnEnd([this](auto) { m_monitorMovedFrom = -1; }); - m_monitorMovedFrom = OLDWORKSPACE ? OLDWORKSPACE->monitorID() : -1; - } - - m_workspace = pWorkspace; - - setAnimationsToMove(); - - OLDWORKSPACE->updateWindows(); - OLDWORKSPACE->updateWindowData(); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(OLDWORKSPACE->monitorID()); - - pWorkspace->updateWindows(); - pWorkspace->updateWindowData(); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID()); - - g_pCompositor->updateAllWindowsAnimatedDecorationValues(); - - if (valid(pWorkspace)) { - g_pEventManager->postEvent(SHyprIPCEvent{.event = "movewindow", .data = std::format("{:x},{}", rc(this), pWorkspace->m_name)}); - g_pEventManager->postEvent(SHyprIPCEvent{.event = "movewindowv2", .data = std::format("{:x},{},{}", rc(this), pWorkspace->m_id, pWorkspace->m_name)}); - EMIT_HOOK_EVENT("moveWindow", (std::vector{m_self.lock(), pWorkspace})); - } - - if (const auto SWALLOWED = m_swallowed.lock()) { - if (SWALLOWED->m_currentlySwallowed) { - SWALLOWED->moveToWorkspace(pWorkspace); - SWALLOWED->m_monitor = m_monitor; - } - } - - if (OLDWORKSPACE && g_pCompositor->isWorkspaceSpecial(OLDWORKSPACE->m_id) && OLDWORKSPACE->getWindows() == 0 && *PCLOSEONLASTSPECIAL) { - if (const auto PMONITOR = OLDWORKSPACE->m_monitor.lock(); PMONITOR) - PMONITOR->setSpecialWorkspace(nullptr); - } -} - -PHLWINDOW CWindow::x11TransientFor() { - if (!m_xwaylandSurface || !m_xwaylandSurface->m_parent) - return nullptr; - - auto s = m_xwaylandSurface->m_parent; - std::vector> visited; - while (s) { - // break loops. Some X apps make them, and it seems like it's valid behavior?!?!?! - // TODO: we should reject loops being created in the first place. - if (std::ranges::find(visited.begin(), visited.end(), s) != visited.end()) - break; - - visited.emplace_back(s.lock()); - s = s->m_parent; - } - - if (s == m_xwaylandSurface) - return nullptr; // dead-ass circle - - for (auto const& w : g_pCompositor->m_windows) { - if (w->m_xwaylandSurface != s) - continue; - return w; - } - - return nullptr; -} - -void CWindow::onUnmap() { - static auto PCLOSEONLASTSPECIAL = CConfigValue("misc:close_special_on_empty"); - static auto PINITIALWSTRACKING = CConfigValue("misc:initial_workspace_tracking"); - - if (!m_initialWorkspaceToken.empty()) { - const auto TOKEN = g_pTokenManager->getToken(m_initialWorkspaceToken); - if (TOKEN) { - if (*PINITIALWSTRACKING == 2) { - // persistent token, but the first window got removed so the token is gone - SInitialWorkspaceToken token = std::any_cast(TOKEN->m_data); - if (token.primaryOwner == m_self) - g_pTokenManager->removeToken(TOKEN); - } - } - } - - m_lastWorkspace = m_workspace->m_id; - - // if the special workspace now has 0 windows, it will be closed, and this - // window will no longer pass render checks, cuz the workspace will be nuked. - // throw it into the main one for the fadeout. - if (m_workspace->m_isSpecialWorkspace && m_workspace->getWindows() == 0) - m_lastWorkspace = m_monitor->activeWorkspaceID(); - - if (*PCLOSEONLASTSPECIAL && m_workspace && m_workspace->getWindows() == 0 && onSpecialWorkspace()) { - const auto PMONITOR = m_monitor.lock(); - if (PMONITOR && PMONITOR->m_activeSpecialWorkspace && PMONITOR->m_activeSpecialWorkspace == m_workspace) - PMONITOR->setSpecialWorkspace(nullptr); - } - - const auto PMONITOR = m_monitor.lock(); - - if (PMONITOR && PMONITOR->m_solitaryClient == m_self) - PMONITOR->m_solitaryClient.reset(); - - if (m_workspace) { - m_workspace->updateWindows(); - m_workspace->updateWindowData(); - } - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID()); - g_pCompositor->updateAllWindowsAnimatedDecorationValues(); - - m_workspace.reset(); - - if (m_isX11) - return; - - m_subsurfaceHead.reset(); - m_popupHead.reset(); -} - -void CWindow::onMap() { - // JIC, reset the callbacks. If any are set, we'll make sure they are cleared so we don't accidentally unset them. (In case a window got remapped) - m_realPosition->resetAllCallbacks(); - m_realSize->resetAllCallbacks(); - m_borderFadeAnimationProgress->resetAllCallbacks(); - m_borderAngleAnimationProgress->resetAllCallbacks(); - m_activeInactiveAlpha->resetAllCallbacks(); - m_alpha->resetAllCallbacks(); - m_realShadowColor->resetAllCallbacks(); - m_dimPercent->resetAllCallbacks(); - m_movingToWorkspaceAlpha->resetAllCallbacks(); - m_movingFromWorkspaceAlpha->resetAllCallbacks(); - - m_movingFromWorkspaceAlpha->setValueAndWarp(1.F); - - if (m_borderAngleAnimationProgress->enabled()) { - m_borderAngleAnimationProgress->setValueAndWarp(0.f); - m_borderAngleAnimationProgress->setCallbackOnEnd([&](WP p) { onBorderAngleAnimEnd(p); }, false); - *m_borderAngleAnimationProgress = 1.f; - } - - m_realSize->setCallbackOnBegin( - [this](auto) { - if (!m_isMapped || isX11OverrideRedirect()) - return; - - g_pEventLoopManager->doLater([this, self = m_self] { - if (!self) - return; - - sendWindowSize(); - }); - }, - false); - - m_movingFromWorkspaceAlpha->setValueAndWarp(1.F); - - m_reportedSize = m_pendingReportedSize; - m_animatingIn = true; - - updateSurfaceScaleTransformDetails(true); - - if (m_isX11) - return; - - m_subsurfaceHead = CSubsurface::create(m_self.lock()); - m_popupHead = CPopup::create(m_self.lock()); -} - -void CWindow::onBorderAngleAnimEnd(WP pav) { - const auto PAV = pav.lock(); - if (!PAV) - return; - - if (PAV->getStyle() != "loop" || !PAV->enabled()) - return; - - const auto PANIMVAR = dc*>(PAV.get()); - - PANIMVAR->setCallbackOnEnd(nullptr); // we remove the callback here because otherwise setvalueandwarp will recurse this - - PANIMVAR->setValueAndWarp(0); - *PANIMVAR = 1.f; - - PANIMVAR->setCallbackOnEnd([&](WP pav) { onBorderAngleAnimEnd(pav); }, false); -} - -void CWindow::setHidden(bool hidden) { - m_hidden = hidden; - - if (hidden && Desktop::focusState()->window() == m_self) - Desktop::focusState()->window().reset(); - - setSuspended(hidden); -} - -bool CWindow::isHidden() { - return m_hidden; -} - -// check if the point is "hidden" under a rounded corner of the window -// it is assumed that the point is within the real window box (m_vRealPosition, m_vRealSize) -// otherwise behaviour is undefined -bool CWindow::isInCurvedCorner(double x, double y) { - const int ROUNDING = rounding(); - const int ROUNDINGPOWER = roundingPower(); - if (getRealBorderSize() >= ROUNDING) - return false; - - // (x0, y0), (x0, y1), ... are the center point of rounding at each corner - double x0 = m_realPosition->value().x + ROUNDING; - double y0 = m_realPosition->value().y + ROUNDING; - double x1 = m_realPosition->value().x + m_realSize->value().x - ROUNDING; - double y1 = m_realPosition->value().y + m_realSize->value().y - ROUNDING; - - if (x < x0 && y < y0) { - return std::pow(x0 - x, ROUNDINGPOWER) + std::pow(y0 - y, ROUNDINGPOWER) > std::pow(sc(ROUNDING), ROUNDINGPOWER); - } - if (x > x1 && y < y0) { - return std::pow(x - x1, ROUNDINGPOWER) + std::pow(y0 - y, ROUNDINGPOWER) > std::pow(sc(ROUNDING), ROUNDINGPOWER); - } - if (x < x0 && y > y1) { - return std::pow(x0 - x, ROUNDINGPOWER) + std::pow(y - y1, ROUNDINGPOWER) > std::pow(sc(ROUNDING), ROUNDINGPOWER); - } - if (x > x1 && y > y1) { - return std::pow(x - x1, ROUNDINGPOWER) + std::pow(y - y1, ROUNDINGPOWER) > std::pow(sc(ROUNDING), ROUNDINGPOWER); - } - - return false; -} - -// checks if the wayland window has a popup at pos -bool CWindow::hasPopupAt(const Vector2D& pos) { - if (m_isX11) - return false; - - auto popup = m_popupHead->at(pos); - - return popup && popup->m_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) { - Debug::log(LOG, "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) { - Debug::log(LOG, "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; -} - -bool CWindow::opaque() { - if (m_alpha->value() != 1.f || m_activeInactiveAlpha->value() != 1.f) - return false; - - const auto PWORKSPACE = m_workspace; - - if (m_wlSurface->small() && !m_wlSurface->m_fillIgnoreSmall) - return false; - - if (PWORKSPACE && PWORKSPACE->m_alpha->value() != 1.f) - return false; - - if (m_isX11 && m_xwaylandSurface && m_xwaylandSurface->m_surface && m_xwaylandSurface->m_surface->m_current.texture) - return m_xwaylandSurface->m_surface->m_current.texture->m_opaque; - - auto solitaryResource = getSolitaryResource(); - if (!solitaryResource || !solitaryResource->m_current.texture) - return false; - - // TODO: this is wrong - const auto EXTENTS = m_xdgSurface->m_surface->m_current.opaque.getExtents(); - if (EXTENTS.w >= m_xdgSurface->m_surface->m_current.bufferSize.x && EXTENTS.h >= m_xdgSurface->m_surface->m_current.bufferSize.y) - return true; - - return solitaryResource->m_current.texture->m_opaque; -} - -float CWindow::rounding() { - static auto PROUNDING = CConfigValue("decoration:rounding"); - static auto PROUNDINGPOWER = CConfigValue("decoration:rounding_power"); - - float roundingPower = m_ruleApplicator->roundingPower().valueOr(*PROUNDINGPOWER); - float rounding = m_ruleApplicator->rounding().valueOr(*PROUNDING) * (roundingPower / 2.0); /* Make perceived roundness consistent. */ - - return rounding; -} - -float CWindow::roundingPower() { - static auto PROUNDINGPOWER = CConfigValue("decoration:rounding_power"); - - return m_ruleApplicator->roundingPower().valueOr(std::clamp(*PROUNDINGPOWER, 1.F, 10.F)); -} - -void CWindow::updateWindowData() { - const auto PWORKSPACE = m_workspace; - const auto WORKSPACERULE = PWORKSPACE ? g_pConfigManager->getWorkspaceRuleFor(PWORKSPACE) : SWorkspaceRule{}; - updateWindowData(WORKSPACERULE); -} - -void CWindow::updateWindowData(const SWorkspaceRule& workspaceRule) { - 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); -} - -int CWindow::getRealBorderSize() { - if ((m_workspace && isEffectiveInternalFSMode(FSMODE_FULLSCREEN)) || !m_ruleApplicator->decorate().valueOrDefault()) - return 0; - - static auto PBORDERSIZE = CConfigValue("general:border_size"); - - return m_ruleApplicator->borderSize().valueOr(*PBORDERSIZE); -} - -float CWindow::getScrollMouse() { - static auto PINPUTSCROLLFACTOR = CConfigValue("input:scroll_factor"); - return m_ruleApplicator->scrollMouse().valueOr(*PINPUTSCROLLFACTOR); -} - -float CWindow::getScrollTouchpad() { - static auto PTOUCHPADSCROLLFACTOR = CConfigValue("input:touchpad:scroll_factor"); - return m_ruleApplicator->scrollTouchpad().valueOr(*PTOUCHPADSCROLLFACTOR); -} - -bool CWindow::isScrollMouseOverridden() { - return m_ruleApplicator->scrollMouse().hasValue(); -} - -bool CWindow::isScrollTouchpadOverridden() { - return m_ruleApplicator->scrollTouchpad().hasValue(); -} - -bool CWindow::canBeTorn() { - static auto PTEARING = CConfigValue("general:allow_tearing"); - return m_ruleApplicator->tearing().valueOr(m_tearingHint) && *PTEARING; -} - -void CWindow::setSuspended(bool suspend) { - if (suspend == m_suspended) - return; - - if (m_isX11 || !m_xdgSurface || !m_xdgSurface->m_toplevel) - return; - - m_xdgSurface->m_toplevel->setSuspeneded(suspend); - m_suspended = suspend; -} - -bool CWindow::visibleOnMonitor(PHLMONITOR pMonitor) { - CBox wbox = {m_realPosition->value(), m_realSize->value()}; - - if (m_isFloating) - wbox = getFullWindowBoundingBox(); - - return !wbox.intersection({pMonitor->m_position, pMonitor->m_size}).empty(); -} - -void CWindow::setAnimationsToMove() { - m_realPosition->setConfig(g_pConfigManager->getAnimationPropertyConfig("windowsMove")); - m_animatingIn = false; -} - -void CWindow::onWorkspaceAnimUpdate() { - // clip box for animated offsets - if (!m_isFloating || m_pinned || isFullscreen() || m_draggingTiled) { - m_floatingOffset = Vector2D(0, 0); - return; - } - - Vector2D offset; - const auto PWORKSPACE = m_workspace; - if (!PWORKSPACE) - return; - - const auto PWSMON = m_monitor.lock(); - if (!PWSMON) - return; - - const auto WINBB = getFullWindowBoundingBox(); - if (PWORKSPACE->m_renderOffset->value().x != 0) { - const auto PROGRESS = PWORKSPACE->m_renderOffset->value().x / PWSMON->m_size.x; - - if (WINBB.x < PWSMON->m_position.x) - offset.x += (PWSMON->m_position.x - WINBB.x) * PROGRESS; - - if (WINBB.x + WINBB.width > PWSMON->m_position.x + PWSMON->m_size.x) - offset.x += (WINBB.x + WINBB.width - PWSMON->m_position.x - PWSMON->m_size.x) * PROGRESS; - } else if (PWORKSPACE->m_renderOffset->value().y != 0) { - const auto PROGRESS = PWORKSPACE->m_renderOffset->value().y / PWSMON->m_size.y; - - if (WINBB.y < PWSMON->m_position.y) - offset.y += (PWSMON->m_position.y - WINBB.y) * PROGRESS; - - if (WINBB.y + WINBB.height > PWSMON->m_position.y + PWSMON->m_size.y) - offset.y += (WINBB.y + WINBB.height - PWSMON->m_position.y - PWSMON->m_size.y) * PROGRESS; - } - - m_floatingOffset = offset; -} - -void CWindow::onFocusAnimUpdate() { - // borderangle once - if (m_borderAngleAnimationProgress->enabled() && !m_borderAngleAnimationProgress->isBeingAnimated()) { - m_borderAngleAnimationProgress->setValueAndWarp(0.f); - *m_borderAngleAnimationProgress = 1.f; - } -} - -int CWindow::popupsCount() { - if (m_isX11 || !m_popupHead) - return 0; - - int no = -1; - m_popupHead->breadthfirst([](WP p, void* d) { *sc(d) += 1; }, &no); - return no; -} - -int CWindow::surfacesCount() { - if (m_isX11) - return 1; - - int no = 0; - m_wlSurface->resource()->breadthfirst([](SP r, const Vector2D& offset, void* d) { *sc(d) += 1; }, &no); - return no; -} - -void CWindow::clampWindowSize(const std::optional minSize, const std::optional maxSize) { - const Vector2D REALSIZE = m_realSize->goal(); - const Vector2D MAX = isFullscreen() ? Vector2D{INFINITY, INFINITY} : maxSize.value_or(Vector2D{INFINITY, INFINITY}); - const Vector2D NEWSIZE = REALSIZE.clamp(minSize.value_or(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}), MAX); - const Vector2D DELTA = REALSIZE - NEWSIZE; - - *m_realPosition = m_realPosition->goal() + DELTA / 2.0; - *m_realSize = NEWSIZE; -} - -bool CWindow::isFullscreen() { - return m_fullscreenState.internal != FSMODE_NONE; -} - -bool CWindow::isEffectiveInternalFSMode(const eFullscreenMode MODE) { - return sc(std::bit_floor(sc(m_fullscreenState.internal))) == MODE; -} - -WORKSPACEID CWindow::workspaceID() { - return m_workspace ? m_workspace->m_id : m_lastWorkspace; -} - -MONITORID CWindow::monitorID() { - return m_monitor ? m_monitor->m_id : MONITOR_INVALID; -} - -bool CWindow::onSpecialWorkspace() { - return m_workspace ? m_workspace->m_isSpecialWorkspace : g_pCompositor->isWorkspaceSpecial(m_lastWorkspace); -} - -std::unordered_map CWindow::getEnv() { - - const auto PID = getPID(); - - if (PID <= 1) - return {}; - - std::unordered_map results; - - std::vector buffer; - size_t needle = 0; - -#if defined(__linux__) - // - std::string environFile = "/proc/" + std::to_string(PID) + "/environ"; - std::ifstream ifs(environFile, std::ios::binary); - - if (!ifs.good()) - return {}; - - buffer.resize(512, '\0'); - while (ifs.read(buffer.data() + needle, 512)) { - buffer.resize(buffer.size() + 512, '\0'); - needle += 512; - } -#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) - int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ENV, static_cast(PID)}; - size_t len = 0; - - if (sysctl(mib, 4, nullptr, &len, nullptr, 0) < 0 || len == 0) - return {}; - - buffer.resize(len, '\0'); - - if (sysctl(mib, 4, buffer.data(), &len, nullptr, 0) < 0) - return {}; - - needle = len; -#endif - - if (needle <= 1) - return {}; - - std::replace(buffer.begin(), buffer.end() - 1, '\0', '\n'); - - CVarList envs(std::string{buffer.data(), buffer.size() - 1}, 0, '\n', true); - - for (auto const& e : envs) { - if (!e.contains('=')) - continue; - - const auto EQ = e.find_first_of('='); - results[e.substr(0, EQ)] = e.substr(EQ + 1); - } - - return results; -} - -void CWindow::activate(bool force) { - if (Desktop::focusState()->window() == m_self) - return; - - static auto PFOCUSONACTIVATE = CConfigValue("misc:focus_on_activate"); - - m_isUrgent = true; - - g_pEventManager->postEvent(SHyprIPCEvent{.event = "urgent", .data = std::format("{:x}", rc(this))}); - EMIT_HOOK_EVENT("urgent", m_self.lock()); - - if (!force && - (!m_ruleApplicator->focusOnActivate().valueOr(*PFOCUSONACTIVATE) || (m_suppressedEvents & SUPPRESS_ACTIVATE_FOCUSONLY) || (m_suppressedEvents & SUPPRESS_ACTIVATE))) - return; - - if (!m_isMapped) { - Debug::log(LOG, "Ignoring CWindow::activate focus/warp, window is not mapped yet."); - return; - } - - if (m_isFloating) - g_pCompositor->changeWindowZOrder(m_self.lock(), true); - - Desktop::focusState()->fullWindowFocus(m_self.lock()); - warpCursor(); -} - -void CWindow::onUpdateState() { - std::optional requestsFS = m_xdgSurface ? m_xdgSurface->m_toplevel->m_state.requestsFullscreen : m_xwaylandSurface->m_state.requestsFullscreen; - std::optional requestsID = m_xdgSurface ? m_xdgSurface->m_toplevel->m_state.requestsFullscreenMonitor : MONITOR_INVALID; - std::optional requestsMX = m_xdgSurface ? m_xdgSurface->m_toplevel->m_state.requestsMaximize : m_xwaylandSurface->m_state.requestsMaximize; - - if (requestsFS.has_value() && !(m_suppressedEvents & SUPPRESS_FULLSCREEN)) { - if (requestsID.has_value() && (requestsID.value() != MONITOR_INVALID) && !(m_suppressedEvents & SUPPRESS_FULLSCREEN_OUTPUT)) { - if (m_isMapped) { - const auto monitor = g_pCompositor->getMonitorFromID(requestsID.value()); - g_pCompositor->moveWindowToWorkspaceSafe(m_self.lock(), monitor->m_activeWorkspace); - Desktop::focusState()->rawMonitorFocus(monitor); - } - - if (!m_isMapped) - m_wantsInitialFullscreenMonitor = requestsID.value(); - } - - bool fs = requestsFS.value(); - if (m_isMapped) - g_pCompositor->changeWindowFullscreenModeClient(m_self.lock(), FSMODE_FULLSCREEN, requestsFS.value()); - - if (!m_isMapped) - m_wantsInitialFullscreen = fs; - } - - if (requestsMX.has_value() && !(m_suppressedEvents & SUPPRESS_MAXIMIZE)) { - if (m_isMapped) { - auto window = m_self.lock(); - auto state = sc(window->m_fullscreenState.client); - bool maximized = (state & sc(FSMODE_MAXIMIZED)) != 0; - g_pCompositor->changeWindowFullscreenModeClient(window, FSMODE_MAXIMIZED, !maximized); - } - } -} - -void CWindow::onUpdateMeta() { - const auto NEWTITLE = fetchTitle(); - bool doUpdate = false; - - if (m_title != NEWTITLE) { - m_title = NEWTITLE; - g_pEventManager->postEvent(SHyprIPCEvent{.event = "windowtitle", .data = std::format("{:x}", rc(this))}); - g_pEventManager->postEvent(SHyprIPCEvent{.event = "windowtitlev2", .data = std::format("{:x},{}", rc(this), m_title)}); - EMIT_HOOK_EVENT("windowTitle", m_self.lock()); - - if (m_self == Desktop::focusState()->window()) { // if it's the active, let's post an event to update others - g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = m_class + "," + m_title}); - g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = std::format("{:x}", rc(this))}); - EMIT_HOOK_EVENT("activeWindow", m_self.lock()); - } - - Debug::log(LOG, "Window {:x} set title to {}", rc(this), m_title); - doUpdate = true; - } - - const auto NEWCLASS = fetchClass(); - if (m_class != NEWCLASS) { - m_class = NEWCLASS; - - if (m_self == Desktop::focusState()->window()) { // if it's the active, let's post an event to update others - g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = m_class + "," + m_title}); - g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = std::format("{:x}", rc(this))}); - EMIT_HOOK_EVENT("activeWindow", m_self.lock()); - } - - Debug::log(LOG, "Window {:x} set class to {}", rc(this), m_class); - doUpdate = true; - } - - if (doUpdate) { - m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_TITLE | Desktop::Rule::RULE_PROP_CLASS); - updateToplevel(); - } -} - -std::string CWindow::fetchTitle() { - if (!m_isX11) { - if (m_xdgSurface && m_xdgSurface->m_toplevel) - return m_xdgSurface->m_toplevel->m_state.title; - } else { - if (m_xwaylandSurface) - return m_xwaylandSurface->m_state.title; - } - - return ""; -} - -std::string CWindow::fetchClass() { - if (!m_isX11) { - if (m_xdgSurface && m_xdgSurface->m_toplevel) - return m_xdgSurface->m_toplevel->m_state.appid; - } else { - if (m_xwaylandSurface) - return m_xwaylandSurface->m_state.appid; - } - - return ""; -} - -void CWindow::onAck(uint32_t serial) { - const auto SERIAL = std::ranges::find_if(m_pendingSizeAcks | std::views::reverse, [serial](const auto& e) { return e.first <= serial; }); - - if (SERIAL == m_pendingSizeAcks.rend()) - return; - - m_pendingSizeAck = *SERIAL; - std::erase_if(m_pendingSizeAcks, [&](const auto& el) { return el.first <= SERIAL->first; }); - - if (m_isX11) - return; - - m_wlSurface->resource()->m_pending.ackedSize = m_pendingSizeAck->second; // apply pending size. We pinged, the window ponged. - m_wlSurface->resource()->m_pending.updated.bits.acked = true; - m_pendingSizeAck.reset(); -} - -void CWindow::onResourceChangeX11() { - if (m_xwaylandSurface->m_surface && !m_wlSurface->resource()) - m_wlSurface->assign(m_xwaylandSurface->m_surface.lock(), m_self.lock()); - else if (!m_xwaylandSurface->m_surface && m_wlSurface->resource()) - m_wlSurface->unassign(); - - // update metadata as well, - // could be first assoc and we need to catch the class - onUpdateMeta(); - - Debug::log(LOG, "xwayland window {:x} -> association to {:x}", rc(m_xwaylandSurface.get()), rc(m_wlSurface->resource().get())); -} - -void CWindow::onX11ConfigureRequest(CBox box) { - - if (!m_xwaylandSurface->m_surface || !m_xwaylandSurface->m_mapped || !m_isMapped) { - m_xwaylandSurface->configure(box); - m_pendingReportedSize = box.size(); - m_reportedSize = box.size(); - m_reportedPosition = box.pos(); - updateX11SurfaceScale(); - return; - } - - g_pHyprRenderer->damageWindow(m_self.lock()); - - if (!m_isFloating || isFullscreen() || g_pInputManager->m_currentlyDraggedWindow == m_self) { - sendWindowSize(true); - g_pInputManager->refocus(); - g_pHyprRenderer->damageWindow(m_self.lock()); - return; - } - - if (box.size() > Vector2D{1, 1}) - setHidden(false); - else - setHidden(true); - - m_realPosition->setValueAndWarp(xwaylandPositionToReal(box.pos())); - m_realSize->setValueAndWarp(xwaylandSizeToReal(box.size())); - - m_position = m_realPosition->goal(); - m_size = m_realSize->goal(); - - if (m_pendingReportedSize != box.size() || m_reportedPosition != box.pos()) { - m_xwaylandSurface->configure(box); - m_reportedSize = box.size(); - m_pendingReportedSize = box.size(); - m_reportedPosition = box.pos(); - } - - updateX11SurfaceScale(); - updateWindowDecos(); - - if (!m_workspace || !m_workspace->isVisible()) - return; // further things are only for visible windows - - m_workspace = g_pCompositor->getMonitorFromVector(m_realPosition->goal() + m_realSize->goal() / 2.f)->m_activeWorkspace; - - g_pCompositor->changeWindowZOrder(m_self.lock(), true); - - m_createdOverFullscreen = true; - - g_pHyprRenderer->damageWindow(m_self.lock()); -} - -void CWindow::warpCursor(bool force) { - static auto PERSISTENTWARPS = CConfigValue("cursor:persistent_warps"); - const auto coords = m_relativeCursorCoordsOnLastWarp; - m_relativeCursorCoordsOnLastWarp.x = -1; // reset m_vRelativeCursorCoordsOnLastWarp - - if (*PERSISTENTWARPS && coords.x > 0 && coords.y > 0 && coords < m_size) // don't warp cursor outside the window - g_pCompositor->warpCursorTo(m_position + coords, force); - else - g_pCompositor->warpCursorTo(middle(), force); -} - -PHLWINDOW CWindow::getSwallower() { - static auto PSWALLOWREGEX = CConfigValue("misc:swallow_regex"); - static auto PSWALLOWEXREGEX = CConfigValue("misc:swallow_exception_regex"); - static auto PSWALLOW = CConfigValue("misc:enable_swallow"); - - if (!*PSWALLOW || std::string{*PSWALLOWREGEX} == STRVAL_EMPTY || (*PSWALLOWREGEX).empty()) - return nullptr; - - // check parent - std::vector candidates; - pid_t currentPid = getPID(); - // walk up the tree until we find someone, 25 iterations max. - for (size_t i = 0; i < 25; ++i) { - currentPid = getPPIDof(currentPid); - - if (!currentPid) - break; - - for (auto const& w : g_pCompositor->m_windows) { - if (!w->m_isMapped || w->isHidden()) - continue; - - if (w->getPID() == currentPid) - candidates.push_back(w); - } - } - - if (!(*PSWALLOWREGEX).empty()) - std::erase_if(candidates, [&](const auto& other) { return !RE2::FullMatch(other->m_class, *PSWALLOWREGEX); }); - - if (candidates.empty()) - return nullptr; - - if (!(*PSWALLOWEXREGEX).empty()) - std::erase_if(candidates, [&](const auto& other) { return RE2::FullMatch(other->m_title, *PSWALLOWEXREGEX); }); - - if (candidates.empty()) - return nullptr; - - if (candidates.size() == 1) - return candidates[0]; - - // walk up the focus history and find the last focused - for (auto const& w : Desktop::focusState()->windowHistory()) { - if (!w) - continue; - - if (std::ranges::find(candidates.begin(), candidates.end(), w.lock()) != candidates.end()) - return w.lock(); - } - - // if none are found (??) then just return the first one - return candidates[0]; -} - -bool CWindow::isX11OverrideRedirect() { - return m_xwaylandSurface && m_xwaylandSurface->m_overrideRedirect; -} - -bool CWindow::isModal() { - return (m_xwaylandSurface && m_xwaylandSurface->m_modal); -} - -Vector2D CWindow::requestedMinSize() { - bool hasSizeHints = m_xwaylandSurface ? m_xwaylandSurface->m_sizeHints : false; - bool hasTopLevel = m_xdgSurface ? m_xdgSurface->m_toplevel : false; - if ((m_isX11 && !hasSizeHints) || (!m_isX11 && !hasTopLevel)) - return Vector2D(1, 1); - - Vector2D minSize = m_isX11 ? Vector2D(m_xwaylandSurface->m_sizeHints->min_width, m_xwaylandSurface->m_sizeHints->min_height) : m_xdgSurface->m_toplevel->layoutMinSize(); - - minSize = minSize.clamp({1, 1}); - - return minSize; -} - -Vector2D CWindow::requestedMaxSize() { - constexpr int NO_MAX_SIZE_LIMIT = 99999; - if (((m_isX11 && !m_xwaylandSurface->m_sizeHints) || (!m_isX11 && (!m_xdgSurface || !m_xdgSurface->m_toplevel)) || m_ruleApplicator->noMaxSize().valueOrDefault())) - return Vector2D(NO_MAX_SIZE_LIMIT, NO_MAX_SIZE_LIMIT); - - Vector2D maxSize = m_isX11 ? Vector2D(m_xwaylandSurface->m_sizeHints->max_width, m_xwaylandSurface->m_sizeHints->max_height) : m_xdgSurface->m_toplevel->layoutMaxSize(); - - if (maxSize.x < 5) - maxSize.x = NO_MAX_SIZE_LIMIT; - if (maxSize.y < 5) - maxSize.y = NO_MAX_SIZE_LIMIT; - - return maxSize; -} - -Vector2D CWindow::realToReportSize() { - if (!m_isX11) - return m_realSize->goal().clamp(Vector2D{0, 0}, Vector2D{std::numeric_limits::infinity(), std::numeric_limits::infinity()}); - - static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); - - const auto REPORTSIZE = m_realSize->goal().clamp(Vector2D{1, 1}, Vector2D{std::numeric_limits::infinity(), std::numeric_limits::infinity()}); - const auto PMONITOR = m_monitor.lock(); - - if (*PXWLFORCESCALEZERO && PMONITOR) - return REPORTSIZE * PMONITOR->m_scale; - - return REPORTSIZE; -} - -Vector2D CWindow::realToReportPosition() { - if (!m_isX11) - return m_realPosition->goal(); - - return g_pXWaylandManager->waylandToXWaylandCoords(m_realPosition->goal()); -} - -Vector2D CWindow::xwaylandSizeToReal(Vector2D size) { - static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); - - const auto PMONITOR = m_monitor.lock(); - const auto SIZE = size.clamp(Vector2D{1, 1}, Vector2D{std::numeric_limits::infinity(), std::numeric_limits::infinity()}); - const auto SCALE = *PXWLFORCESCALEZERO ? PMONITOR->m_scale : 1.0f; - - return SIZE / SCALE; -} - -Vector2D CWindow::xwaylandPositionToReal(Vector2D pos) { - return g_pXWaylandManager->xwaylandToWaylandCoords(pos); -} - -void CWindow::updateX11SurfaceScale() { - static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); - - m_X11SurfaceScaledBy = 1.0f; - if (m_isX11 && *PXWLFORCESCALEZERO) { - if (const auto PMONITOR = m_monitor.lock(); PMONITOR) - m_X11SurfaceScaledBy = PMONITOR->m_scale; - } -} - -void CWindow::sendWindowSize(bool force) { - const auto PMONITOR = m_monitor.lock(); - - Debug::log(TRACE, "sendWindowSize: window:{:x},title:{} with real pos {}, real size {} (force: {})", rc(this), this->m_title, m_realPosition->goal(), - m_realSize->goal(), force); - - // TODO: this should be decoupled from setWindowSize IMO - const auto REPORTPOS = realToReportPosition(); - - const auto REPORTSIZE = realToReportSize(); - - if (!force && m_pendingReportedSize == REPORTSIZE && (m_reportedPosition == REPORTPOS || !m_isX11)) - return; - - m_reportedPosition = REPORTPOS; - m_pendingReportedSize = REPORTSIZE; - updateX11SurfaceScale(); - - if (m_isX11 && m_xwaylandSurface) - m_xwaylandSurface->configure({REPORTPOS, REPORTSIZE}); - else if (m_xdgSurface && m_xdgSurface->m_toplevel) - m_pendingSizeAcks.emplace_back(m_xdgSurface->m_toplevel->setSize(REPORTSIZE), REPORTSIZE.floor()); -} - -NContentType::eContentType CWindow::getContentType() { - if (!m_wlSurface || !m_wlSurface->resource() || !m_wlSurface->resource()->m_contentType.valid()) - return CONTENT_TYPE_NONE; - - return m_wlSurface->resource()->m_contentType->m_value; -} - -void CWindow::setContentType(NContentType::eContentType contentType) { - if (!m_wlSurface->resource()->m_contentType.valid()) - m_wlSurface->resource()->m_contentType = PROTO::contentType->getContentType(m_wlSurface->resource()); - // else disallow content type change if proto is used? - - Debug::log(INFO, "ContentType for window {}", sc(contentType)); - m_wlSurface->resource()->m_contentType->m_value = contentType; -} - -void CWindow::deactivateGroupMembers() { - 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 (!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; - } -} - -bool CWindow::isNotResponding() { - return g_pANRManager->isNotResponding(m_self.lock()); -} - -std::optional CWindow::xdgTag() { - if (!m_xdgSurface || !m_xdgSurface->m_toplevel) - return std::nullopt; - - return m_xdgSurface->m_toplevel->m_toplevelTag; -} - -std::optional CWindow::xdgDescription() { - if (!m_xdgSurface || !m_xdgSurface->m_toplevel) - return std::nullopt; - - return m_xdgSurface->m_toplevel->m_toplevelDescription; -} - -PHLWINDOW CWindow::parent() { - if (m_isX11) { - auto t = x11TransientFor(); - - // don't return a parent that's not mapped - if (!validMapped(t)) - return nullptr; - - return t; - } - - if (!m_xdgSurface || !m_xdgSurface->m_toplevel || !m_xdgSurface->m_toplevel->m_parent) - return nullptr; - - // don't return a parent that's not mapped - if (!m_xdgSurface->m_toplevel->m_parent->m_window || !validMapped(m_xdgSurface->m_toplevel->m_parent->m_window)) - return nullptr; - - return m_xdgSurface->m_toplevel->m_parent->m_window.lock(); -} - -bool CWindow::priorityFocus() { - return !m_isX11 && CAsyncDialogBox::isPriorityDialogBox(getPID()); -} - -SP CWindow::getSolitaryResource() { - if (!m_wlSurface || !m_wlSurface->resource()) - return nullptr; - - auto res = m_wlSurface->resource(); - if (m_isX11) - return res; - - if (popupsCount()) - return nullptr; - - if (res->m_subsurfaces.size() == 0) - return res; - - if (res->m_subsurfaces.size() >= 1) { - if (!res->hasVisibleSubsurface()) - return res; - - if (res->m_subsurfaces.size() == 1) { - if (res->m_subsurfaces[0].expired() || res->m_subsurfaces[0]->m_surface.expired()) - return nullptr; - auto surf = res->m_subsurfaces[0]->m_surface.lock(); - if (!surf || surf->m_subsurfaces.size() != 0 || surf->extends() != res->extends() || !surf->m_current.texture || !surf->m_current.texture->m_opaque) - return nullptr; - return surf; - } - } - - return nullptr; -} - -Vector2D CWindow::getReportedSize() { - if (m_isX11) - return m_reportedSize; - if (m_wlSurface && m_wlSurface->resource()) - return m_wlSurface->resource()->m_current.ackedSize; - return m_reportedSize; -} - -void CWindow::updateDecorationValues() { - static auto PACTIVECOL = CConfigValue("general:col.active_border"); - static auto PINACTIVECOL = CConfigValue("general:col.inactive_border"); - static auto PNOGROUPACTIVECOL = CConfigValue("general:col.nogroup_border_active"); - static auto PNOGROUPINACTIVECOL = CConfigValue("general:col.nogroup_border"); - static auto PGROUPACTIVECOL = CConfigValue("group:col.border_active"); - static auto PGROUPINACTIVECOL = CConfigValue("group:col.border_inactive"); - static auto PGROUPACTIVELOCKEDCOL = CConfigValue("group:col.border_locked_active"); - static auto PGROUPINACTIVELOCKEDCOL = CConfigValue("group:col.border_locked_inactive"); - static auto PINACTIVEALPHA = CConfigValue("decoration:inactive_opacity"); - static auto PACTIVEALPHA = CConfigValue("decoration:active_opacity"); - static auto PFULLSCREENALPHA = CConfigValue("decoration:fullscreen_opacity"); - static auto PSHADOWCOL = CConfigValue("decoration:shadow:color"); - static auto PSHADOWCOLINACTIVE = CConfigValue("decoration:shadow:color_inactive"); - static auto PDIMSTRENGTH = CConfigValue("decoration:dim_strength"); - static auto PDIMENABLED = CConfigValue("decoration:dim_inactive"); - static auto PDIMMODAL = CConfigValue("decoration:dim_modal"); - - auto* const ACTIVECOL = sc((PACTIVECOL.ptr())->getData()); - auto* const INACTIVECOL = sc((PINACTIVECOL.ptr())->getData()); - auto* const NOGROUPACTIVECOL = sc((PNOGROUPACTIVECOL.ptr())->getData()); - auto* const NOGROUPINACTIVECOL = sc((PNOGROUPINACTIVECOL.ptr())->getData()); - auto* const GROUPACTIVECOL = sc((PGROUPACTIVECOL.ptr())->getData()); - auto* const GROUPINACTIVECOL = sc((PGROUPINACTIVECOL.ptr())->getData()); - auto* const GROUPACTIVELOCKEDCOL = sc((PGROUPACTIVELOCKEDCOL.ptr())->getData()); - auto* const GROUPINACTIVELOCKEDCOL = sc((PGROUPINACTIVELOCKEDCOL.ptr())->getData()); - - auto setBorderColor = [&](CGradientValueData grad) -> void { - if (grad == m_realBorderColor) - return; - - m_realBorderColorPrevious = m_realBorderColor; - m_realBorderColor = grad; - m_borderFadeAnimationProgress->setValueAndWarp(0.f); - *m_borderFadeAnimationProgress = 1.f; - }; - - const bool IS_SHADOWED_BY_MODAL = m_xdgSurface && m_xdgSurface->m_toplevel && m_xdgSurface->m_toplevel->anyChildModal(); - - // border - const auto RENDERDATA = g_pLayoutManager->getCurrentLayout()->requestRenderHints(m_self.lock()); - if (RENDERDATA.isBorderGradient) - setBorderColor(*RENDERDATA.borderGradient); - else { - const bool GROUPLOCKED = m_groupData.pNextWindow.lock() ? getGroupHead()->m_groupData.locked : false; - if (m_self == Desktop::focusState()->window()) { - const auto* const ACTIVECOLOR = - !m_groupData.pNextWindow.lock() ? (!m_groupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); - setBorderColor(m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR)); - } else { - const auto* const INACTIVECOLOR = - !m_groupData.pNextWindow.lock() ? (!m_groupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) : (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); - setBorderColor(m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR)); - } - } - - // opacity - const auto PWORKSPACE = m_workspace; - if (isEffectiveInternalFSMode(FSMODE_FULLSCREEN)) { - *m_activeInactiveAlpha = m_ruleApplicator->alphaFullscreen().valueOrDefault().applyAlpha(*PFULLSCREENALPHA); - } else { - if (m_self == Desktop::focusState()->window()) - *m_activeInactiveAlpha = m_ruleApplicator->alpha().valueOrDefault().applyAlpha(*PACTIVEALPHA); - else - *m_activeInactiveAlpha = m_ruleApplicator->alphaInactive().valueOrDefault().applyAlpha(*PINACTIVEALPHA); - } - - // dim - float goalDim = 1.F; - if (m_self == Desktop::focusState()->window() || m_ruleApplicator->noDim().valueOrDefault() || !*PDIMENABLED) - goalDim = 0; - else - goalDim = *PDIMSTRENGTH; - - if (IS_SHADOWED_BY_MODAL && *PDIMMODAL) - goalDim += (1.F - goalDim) / 2.F; - - *m_dimPercent = goalDim; - - // shadow - if (!isX11OverrideRedirect() && !m_X11DoesntWantBorders) { - if (m_self == Desktop::focusState()->window()) - *m_realShadowColor = CHyprColor(*PSHADOWCOL); - else - *m_realShadowColor = CHyprColor(*PSHADOWCOLINACTIVE != -1 ? *PSHADOWCOLINACTIVE : *PSHADOWCOL); - } else - m_realShadowColor->setValueAndWarp(CHyprColor(0, 0, 0, 0)); // no shadow - - updateWindowDecos(); -} - -std::optional CWindow::calculateSingleExpr(const std::string& s) { - const auto PMONITOR = m_monitor ? m_monitor : Desktop::focusState()->monitor(); - const auto CURSOR_LOCAL = g_pInputManager->getMouseCoordsInternal() - (PMONITOR ? PMONITOR->m_position : Vector2D{}); - - Math::CExpression expr; - expr.addVariable("window_w", m_realSize->goal().x); - expr.addVariable("window_h", m_realSize->goal().y); - expr.addVariable("window_x", m_realPosition->goal().x - (PMONITOR ? PMONITOR->m_position.x : 0)); - expr.addVariable("window_y", m_realPosition->goal().y - (PMONITOR ? PMONITOR->m_position.y : 0)); - - expr.addVariable("monitor_w", PMONITOR ? PMONITOR->m_size.x : 1920); - expr.addVariable("monitor_h", PMONITOR ? PMONITOR->m_size.y : 1080); - - expr.addVariable("cursor_x", CURSOR_LOCAL.x); - expr.addVariable("cursor_y", CURSOR_LOCAL.y); - - return expr.compute(s); -} - -std::optional CWindow::calculateExpression(const std::string& s) { - auto spacePos = s.find(' '); - if (spacePos == std::string::npos) - return std::nullopt; - - const auto LHS = calculateSingleExpr(s.substr(0, spacePos)); - const auto RHS = calculateSingleExpr(s.substr(spacePos + 1)); - - if (!LHS || !RHS) - return std::nullopt; - - return Vector2D{*LHS, *RHS}; -} diff --git a/src/desktop/Window.hpp b/src/desktop/Window.hpp deleted file mode 100644 index 63492682a..000000000 --- a/src/desktop/Window.hpp +++ /dev/null @@ -1,438 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "../config/ConfigDataValues.hpp" -#include "../helpers/AnimatedVariable.hpp" -#include "../helpers/TagKeeper.hpp" -#include "../macros.hpp" -#include "../managers/XWaylandManager.hpp" -#include "../render/decorations/IHyprWindowDecoration.hpp" -#include "../render/Transformer.hpp" -#include "DesktopTypes.hpp" -#include "Popup.hpp" -#include "Subsurface.hpp" -#include "WLSurface.hpp" -#include "Workspace.hpp" -#include "rule/windowRule/WindowRuleApplicator.hpp" -#include "../protocols/types/ContentType.hpp" - -class CXDGSurfaceResource; -class CXWaylandSurface; - -enum eGroupRules : uint8_t { - // effective only during first map, except for _ALWAYS variant - GROUP_NONE = 0, - GROUP_SET = 1 << 0, // Open as new group or add to focused group - GROUP_SET_ALWAYS = 1 << 1, - GROUP_BARRED = 1 << 2, // Don't insert to focused group. - GROUP_LOCK = 1 << 3, // Lock m_sGroupData.lock - 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 -}; - -enum eGetWindowProperties : uint8_t { - WINDOW_ONLY = 0, - RESERVED_EXTENTS = 1 << 0, - INPUT_EXTENTS = 1 << 1, - FULL_EXTENTS = 1 << 2, - FLOATING_ONLY = 1 << 3, - ALLOW_FLOATING = 1 << 4, - USE_PROP_TILED = 1 << 5, - SKIP_FULLSCREEN_PRIORITY = 1 << 6, - FOCUS_PRIORITY = 1 << 7, -}; - -enum eSuppressEvents : uint8_t { - SUPPRESS_NONE = 0, - SUPPRESS_FULLSCREEN = 1 << 0, - SUPPRESS_MAXIMIZE = 1 << 1, - SUPPRESS_ACTIVATE = 1 << 2, - SUPPRESS_ACTIVATE_FOCUSONLY = 1 << 3, - SUPPRESS_FULLSCREEN_OUTPUT = 1 << 4, -}; - -class IWindowTransformer; - -struct SInitialWorkspaceToken { - PHLWINDOWREF primaryOwner; - std::string workspace; -}; - -struct SFullscreenState { - eFullscreenMode internal = FSMODE_NONE; - eFullscreenMode client = FSMODE_NONE; -}; - -class CWindow { - public: - static PHLWINDOW create(SP); - static PHLWINDOW create(SP); - - private: - CWindow(SP resource); - CWindow(SP surface); - - public: - ~CWindow(); - - SP m_wlSurface; - - struct { - CSignalT<> destroy; - } m_events; - - WP m_xdgSurface; - WP m_xwaylandSurface; - - // this is the position and size of the "bounding box" - Vector2D m_position = Vector2D(0, 0); - Vector2D m_size = Vector2D(0, 0); - - // this is the real position and size used to draw the thing - PHLANIMVAR m_realPosition; - PHLANIMVAR m_realSize; - - // for not spamming the protocols - Vector2D m_reportedPosition; - Vector2D m_reportedSize; - Vector2D m_pendingReportedSize; - 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; - - bool m_isMapped = false; - - bool m_requestsFloat = false; - - // This is for fullscreen apps - bool m_createdOverFullscreen = false; - - // XWayland stuff - bool m_isX11 = false; - bool m_X11DoesntWantBorders = false; - bool m_X11ShouldntFocus = false; - float m_X11SurfaceScaledBy = 1.f; - // - - // For nofocus - bool m_noInitialFocus = false; - - // Fullscreen and Maximize - bool m_wantsInitialFullscreen = false; - MONITORID m_wantsInitialFullscreenMonitor = MONITOR_INVALID; - - // bitfield suppressEvents - uint64_t m_suppressedEvents = SUPPRESS_NONE; - - // desktop components - UP m_subsurfaceHead; - UP m_popupHead; - - // Animated border - CGradientValueData m_realBorderColor = {0}; - CGradientValueData m_realBorderColorPrevious = {0}; - PHLANIMVAR m_borderFadeAnimationProgress; - PHLANIMVAR m_borderAngleAnimationProgress; - - // Fade in-out - PHLANIMVAR m_alpha; - bool m_fadingOut = false; - bool m_readyToDelete = false; - Vector2D m_originalClosedPos; // these will be used for calculations later on in - Vector2D m_originalClosedSize; // drawing the closing animations - SBoxExtents m_originalClosedExtents; - bool m_animatingIn = false; - - // For pinned (sticky) windows - bool m_pinned = false; - - // For preserving pinned state when fullscreening a pinned window - bool m_pinFullscreened = false; - - // urgency hint - bool m_isUrgent = false; - - // for proper cycling. While cycling we can't just move the pointers, so we need to keep track of the last cycled window. - PHLWINDOWREF m_lastCycledWindow; - - // Window decorations - // TODO: make this a SP. - std::vector> m_windowDecorations; - std::vector m_decosToRemove; - - // Special render data, rules, etc - UP m_ruleApplicator; - - // Transformers - std::vector> m_transformers; - - // for alpha - PHLANIMVAR m_activeInactiveAlpha; - PHLANIMVAR m_movingFromWorkspaceAlpha; - - // animated shadow color - PHLANIMVAR m_realShadowColor; - - // animated tint - PHLANIMVAR m_dimPercent; - - // animate moving to an invisible workspace - int m_monitorMovedFrom = -1; // -1 means not moving - PHLANIMVAR m_movingToWorkspaceAlpha; - - // swallowing - PHLWINDOWREF m_swallowed; - bool m_currentlySwallowed = false; - bool m_groupSwallowed = false; - - // for toplevel monitor events - MONITORID m_lastSurfaceMonitorID = -1; - - // initial token. Will be unregistered on workspace change or timeout of 2 minutes - std::string m_initialWorkspaceToken = ""; - - // for groups - struct SGroupData { - PHLWINDOWREF pNextWindow; // nullptr means no grouping. Self means single group. - bool head = false; - bool locked = false; // per group lock - bool deny = false; // deny window from enter a group or made a group - } m_groupData; - uint16_t m_groupRules = GROUP_NONE; - - bool m_tearingHint = false; - - // ANR - PHLANIMVAR m_notRespondingTint; - - // For the noclosefor windowrule - Time::steady_tp m_closeableSince = Time::steadyNow(); - - // For the list lookup - bool operator==(const CWindow& rhs) const { - return m_xdgSurface == rhs.m_xdgSurface && m_xwaylandSurface == rhs.m_xwaylandSurface && m_position == rhs.m_position && m_size == rhs.m_size && - m_fadingOut == rhs.m_fadingOut; - } - - // methods - CBox getFullWindowBoundingBox(); - SBoxExtents getFullWindowExtents(); - CBox getWindowBoxUnified(uint64_t props); - SBoxExtents getWindowExtentsUnified(uint64_t props); - CBox getWindowIdealBoundingBoxIgnoreReserved(); - void addWindowDeco(UP deco); - void updateWindowDecos(); - void removeWindowDeco(IHyprWindowDecoration* deco); - void uncacheWindowDecos(); - bool checkInputOnDecos(const eInputType, const Vector2D&, std::any = {}); - pid_t getPID(); - IHyprWindowDecoration* getDecorationByType(eDecorationType); - void updateToplevel(); - void updateSurfaceScaleTransformDetails(bool force = false); - void moveToWorkspace(PHLWORKSPACE); - PHLWINDOW x11TransientFor(); - void onUnmap(); - void onMap(); - void setHidden(bool hidden); - bool isHidden(); - void updateDecorationValues(); - SBoxExtents getFullWindowReservedArea(); - Vector2D middle(); - bool opaque(); - float rounding(); - float roundingPower(); - bool canBeTorn(); - void setSuspended(bool suspend); - bool visibleOnMonitor(PHLMONITOR pMonitor); - WORKSPACEID workspaceID(); - MONITORID monitorID(); - bool onSpecialWorkspace(); - void activate(bool force = false); - int surfacesCount(); - void clampWindowSize(const std::optional minSize, const std::optional maxSize); - bool isFullscreen(); - bool isEffectiveInternalFSMode(const eFullscreenMode); - int getRealBorderSize(); - float getScrollMouse(); - float getScrollTouchpad(); - bool isScrollMouseOverridden(); - bool isScrollTouchpadOverridden(); - void updateWindowData(); - void updateWindowData(const struct SWorkspaceRule&); - void onBorderAngleAnimEnd(WP pav); - 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(); - void onUpdateState(); - void onUpdateMeta(); - void onX11ConfigureRequest(CBox box); - void onResourceChangeX11(); - std::string fetchTitle(); - std::string fetchClass(); - void warpCursor(bool force = false); - PHLWINDOW getSwallower(); - bool isX11OverrideRedirect(); - bool isModal(); - Vector2D requestedMinSize(); - Vector2D requestedMaxSize(); - Vector2D realToReportSize(); - Vector2D realToReportPosition(); - Vector2D xwaylandSizeToReal(Vector2D size); - Vector2D xwaylandPositionToReal(Vector2D size); - void updateX11SurfaceScale(); - void sendWindowSize(bool force = false); - NContentType::eContentType getContentType(); - void setContentType(NContentType::eContentType contentType); - void deactivateGroupMembers(); - bool isNotResponding(); - std::optional xdgTag(); - std::optional xdgDescription(); - PHLWINDOW parent(); - bool priorityFocus(); - SP getSolitaryResource(); - Vector2D getReportedSize(); - std::optional calculateExpression(const std::string& s); - - CBox getWindowMainSurfaceBox() const { - return {m_realPosition->value().x, m_realPosition->value().y, m_realSize->value().x, m_realSize->value().y}; - } - - // listeners - void onAck(uint32_t serial); - - // - std::unordered_map getEnv(); - - // - PHLWINDOWREF m_self; - - // make private once we move listeners to inside CWindow - struct { - CHyprSignalListener map; - CHyprSignalListener ack; - CHyprSignalListener unmap; - CHyprSignalListener commit; - CHyprSignalListener destroy; - CHyprSignalListener activate; - CHyprSignalListener configureRequest; - CHyprSignalListener setGeometry; - CHyprSignalListener updateState; - CHyprSignalListener updateMetadata; - CHyprSignalListener resourceChange; - } m_listeners; - - private: - std::optional calculateSingleExpr(const std::string& s); - - // For hidden windows and stuff - bool m_hidden = false; - bool m_suspended = false; - WORKSPACEID m_lastWorkspace = WORKSPACE_INVALID; -}; - -inline bool valid(PHLWINDOW w) { - return w.get(); -} - -inline bool valid(PHLWINDOWREF w) { - return !w.expired(); -} - -inline bool validMapped(PHLWINDOW w) { - if (!valid(w)) - return false; - return w->m_isMapped; -} - -inline bool validMapped(PHLWINDOWREF w) { - if (!valid(w)) - return false; - return w->m_isMapped; -} - -/** - format specification - - 'x', only address, equivalent of (uintpr_t)CWindow* - - 'm', with monitor id - - 'w', with workspace id - - 'c', with application class -*/ - -template -struct std::formatter : std::formatter { - bool formatAddressOnly = false; - bool formatWorkspace = false; - bool formatMonitor = false; - bool formatClass = false; - FORMAT_PARSE( // - FORMAT_FLAG('x', formatAddressOnly) // - FORMAT_FLAG('m', formatMonitor) // - FORMAT_FLAG('w', formatWorkspace) // - FORMAT_FLAG('c', formatClass), - PHLWINDOW) - - template - auto format(PHLWINDOW const& w, FormatContext& ctx) const { - auto&& out = ctx.out(); - if (formatAddressOnly) - return std::format_to(out, "{:x}", rc(w.get())); - if (!w) - return std::format_to(out, "[Window nullptr]"); - - std::format_to(out, "["); - std::format_to(out, "Window {:x}: title: \"{}\"", rc(w.get()), w->m_title); - if (formatWorkspace) - std::format_to(out, ", workspace: {}", w->m_workspace ? w->workspaceID() : WORKSPACE_INVALID); - if (formatMonitor) - std::format_to(out, ", monitor: {}", w->monitorID()); - if (formatClass) - std::format_to(out, ", class: {}", w->m_class); - return std::format_to(out, "]"); - } -}; diff --git a/src/desktop/Workspace.cpp b/src/desktop/Workspace.cpp index 7e1dcd5b6..cb2da6b3c 100644 --- a/src/desktop/Workspace.cpp +++ b/src/desktop/Workspace.cpp @@ -1,10 +1,15 @@ #include "Workspace.hpp" +#include "view/Group.hpp" #include "../Compositor.hpp" -#include "../config/ConfigValue.hpp" -#include "config/ConfigManager.hpp" +#include "../config/shared/animation/AnimationTree.hpp" +#include "../config/shared/workspace/WorkspaceRuleManager.hpp" +#include "../config/supplementary/executor/Executor.hpp" #include "managers/animation/AnimationManager.hpp" #include "../managers/EventManager.hpp" -#include "../managers/HookSystemManager.hpp" +#include "../helpers/Monitor.hpp" +#include "../layout/space/Space.hpp" +#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp" +#include "../event/EventBus.hpp" #include #include @@ -25,53 +30,49 @@ CWorkspace::CWorkspace(WORKSPACEID id, PHLMONITOR monitor, std::string name, boo void CWorkspace::init(PHLWORKSPACE self) { m_self = self; - g_pAnimationManager->createAnimation(Vector2D(0, 0), m_renderOffset, g_pConfigManager->getAnimationPropertyConfig(m_isSpecialWorkspace ? "specialWorkspaceIn" : "workspacesIn"), - self, AVARDAMAGE_ENTIRE); - g_pAnimationManager->createAnimation(1.f, m_alpha, g_pConfigManager->getAnimationPropertyConfig(m_isSpecialWorkspace ? "specialWorkspaceIn" : "workspacesIn"), self, + g_pAnimationManager->createAnimation( + Vector2D(0, 0), m_renderOffset, Config::animationTree()->getAnimationPropertyConfig(m_isSpecialWorkspace ? "specialWorkspaceIn" : "workspacesIn"), self, AVARDAMAGE_ENTIRE); + g_pAnimationManager->createAnimation(1.f, m_alpha, Config::animationTree()->getAnimationPropertyConfig(m_isSpecialWorkspace ? "specialWorkspaceIn" : "workspacesIn"), self, AVARDAMAGE_ENTIRE); - const auto RULEFORTHIS = g_pConfigManager->getWorkspaceRuleFor(self); - if (RULEFORTHIS.defaultName.has_value()) - m_name = RULEFORTHIS.defaultName.value(); + const auto RULEFORTHIS = Config::workspaceRuleMgr()->getWorkspaceRuleFor(self).value_or(Config::CWorkspaceRule{}); + if (RULEFORTHIS.m_defaultName.has_value()) + m_name = RULEFORTHIS.m_defaultName.value(); + if (RULEFORTHIS.m_animationStyle.has_value()) + m_animationStyle = RULEFORTHIS.m_animationStyle.value(); - m_focusedWindowHook = g_pHookSystem->hookDynamic("closeWindow", [this](void* self, SCallbackInfo& info, std::any param) { - const auto PWINDOW = std::any_cast(param); - - if (PWINDOW == m_lastFocusedWindow.lock()) + m_focusedWindowHook = Event::bus()->m_events.window.close.listen([this](PHLWINDOW pWindow) { + if (pWindow == m_lastFocusedWindow.lock()) m_lastFocusedWindow.reset(); }); + m_space = Layout::CSpace::create(m_self.lock()); + m_space->setAlgorithmProvider(Layout::Supplementary::algoMatcher()->createAlgorithmForWorkspace(m_self.lock())); + m_inert = false; - const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(self); - setPersistent(WORKSPACERULE.isPersistent); + const auto WORKSPACERULE = Config::workspaceRuleMgr()->getWorkspaceRuleFor(self).value_or(Config::CWorkspaceRule{}); + setPersistent(WORKSPACERULE.m_isPersistent); if (self->m_wasCreatedEmpty) - if (auto cmd = WORKSPACERULE.onCreatedEmptyRunCmd) - CKeybindManager::spawnWithRules(*cmd, self); + if (auto cmd = WORKSPACERULE.m_onCreatedEmptyRunCmd) + Config::Supplementary::executor()->spawnWithRules(*cmd, self); g_pEventManager->postEvent({.event = "createworkspace", .data = m_name}); g_pEventManager->postEvent({.event = "createworkspacev2", .data = std::format("{},{}", m_id, m_name)}); - EMIT_HOOK_EVENT("createWorkspace", this); -} - -SWorkspaceIDName CWorkspace::getPrevWorkspaceIDName() const { - return m_prevWorkspace; + Event::bus()->m_events.workspace.created.emit(self); } CWorkspace::~CWorkspace() { - Debug::log(LOG, "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); + Log::logger->log(Log::DEBUG, "Destroying workspace ID {}", m_id); 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(); } @@ -82,24 +83,6 @@ PHLWINDOW CWorkspace::getLastFocusedWindow() { return m_lastFocusedWindow.lock(); } -void CWorkspace::rememberPrevWorkspace(const PHLWORKSPACE& prev) { - if (!prev) { - m_prevWorkspace.id = -1; - m_prevWorkspace.name = ""; - return; - } - - if (prev->m_id == m_id) { - Debug::log(LOG, "Tried to set prev workspace to the same as current one"); - return; - } - - m_prevWorkspace.id = prev->m_id; - m_prevWorkspace.name = prev->m_name; - - prev->m_monitor->addPrevWorkspaceID(prev->m_id); -} - std::string CWorkspace::getConfigName() { if (m_isSpecialWorkspace) { return m_name; @@ -156,14 +139,14 @@ bool CWorkspace::matchesStaticSelector(const std::string& selector_) { if (cur == 'r') { WORKSPACEID from = 0, to = 0; if (!prop.starts_with("r[") || !prop.ends_with("]")) { - Debug::log(LOG, "Invalid selector {}", selector); + Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } prop = prop.substr(2, prop.length() - 3); if (!prop.contains("-")) { - Debug::log(LOG, "Invalid selector {}", selector); + Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } @@ -171,7 +154,7 @@ bool CWorkspace::matchesStaticSelector(const std::string& selector_) { const auto LHS = prop.substr(0, DASHPOS), RHS = prop.substr(DASHPOS + 1); if (!isNumber(LHS) || !isNumber(RHS)) { - Debug::log(LOG, "Invalid selector {}", selector); + Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } @@ -179,12 +162,12 @@ bool CWorkspace::matchesStaticSelector(const std::string& selector_) { from = std::stoll(LHS); to = std::stoll(RHS); } catch (std::exception& e) { - Debug::log(LOG, "Invalid selector {}", selector); + Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } if (to < from || to < 1 || from < 1) { - Debug::log(LOG, "Invalid selector {}", selector); + Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } @@ -195,7 +178,7 @@ bool CWorkspace::matchesStaticSelector(const std::string& selector_) { if (cur == 's') { if (!prop.starts_with("s[") || !prop.ends_with("]")) { - Debug::log(LOG, "Invalid selector {}", selector); + Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } @@ -210,7 +193,7 @@ bool CWorkspace::matchesStaticSelector(const std::string& selector_) { if (cur == 'm') { if (!prop.starts_with("m[") || !prop.ends_with("]")) { - Debug::log(LOG, "Invalid selector {}", selector); + Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } @@ -225,7 +208,7 @@ bool CWorkspace::matchesStaticSelector(const std::string& selector_) { if (cur == 'n') { if (!prop.starts_with("n[") || !prop.ends_with("]")) { - Debug::log(LOG, "Invalid selector {}", selector); + Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } @@ -246,7 +229,7 @@ bool CWorkspace::matchesStaticSelector(const std::string& selector_) { if (cur == 'w') { WORKSPACEID from = 0, to = 0; if (!prop.starts_with("w[") || !prop.ends_with("]")) { - Debug::log(LOG, "Invalid selector {}", selector); + Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } @@ -284,14 +267,14 @@ bool CWorkspace::matchesStaticSelector(const std::string& selector_) { // try single if (!isNumber(prop)) { - Debug::log(LOG, "Invalid selector {}", selector); + Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } try { from = std::stoll(prop); } catch (std::exception& e) { - Debug::log(LOG, "Invalid selector {}", selector); + Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } @@ -314,7 +297,7 @@ bool CWorkspace::matchesStaticSelector(const std::string& selector_) { const auto LHS = prop.substr(0, DASHPOS), RHS = prop.substr(DASHPOS + 1); if (!isNumber(LHS) || !isNumber(RHS)) { - Debug::log(LOG, "Invalid selector {}", selector); + Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } @@ -322,12 +305,12 @@ bool CWorkspace::matchesStaticSelector(const std::string& selector_) { from = std::stoll(LHS); to = std::stoll(RHS); } catch (std::exception& e) { - Debug::log(LOG, "Invalid selector {}", selector); + Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } if (to < from || to < 1 || from < 1) { - Debug::log(LOG, "Invalid selector {}", selector); + Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } @@ -348,7 +331,7 @@ bool CWorkspace::matchesStaticSelector(const std::string& selector_) { if (cur == 'f') { if (!prop.starts_with("f[") || !prop.ends_with("]")) { - Debug::log(LOG, "Invalid selector {}", selector); + Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } @@ -357,7 +340,7 @@ bool CWorkspace::matchesStaticSelector(const std::string& selector_) { try { FSSTATE = std::stoi(prop); } catch (std::exception& e) { - Debug::log(LOG, "Invalid selector {}", selector); + Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } @@ -379,7 +362,7 @@ bool CWorkspace::matchesStaticSelector(const std::string& selector_) { continue; } - Debug::log(LOG, "Invalid selector {}", selector); + Log::logger->log(Log::DEBUG, "Invalid selector {}", selector); return false; } @@ -420,6 +403,9 @@ bool CWorkspace::isVisible() { bool CWorkspace::isVisibleNotCovered() { const auto PMONITOR = m_monitor.lock(); + if (!PMONITOR) + return false; + if (PMONITOR->m_activeSpecialWorkspace) return PMONITOR->m_activeSpecialWorkspace->m_id == m_id; @@ -428,14 +414,19 @@ bool CWorkspace::isVisibleNotCovered() { int CWorkspace::getWindows(std::optional onlyTiled, std::optional onlyPinned, std::optional onlyVisible) { int no = 0; - for (auto const& w : g_pCompositor->m_windows) { - if (w->workspaceID() != m_id || !w->m_isMapped) + + if (!m_space) + return 0; + + for (auto const& t : m_space->targets()) { + if (!t) continue; - if (onlyTiled.has_value() && w->m_isFloating == onlyTiled.value()) + + if (onlyTiled.has_value() && t->floating() == onlyTiled.value()) continue; - if (onlyPinned.has_value() && w->m_pinned != onlyPinned.value()) + if (onlyPinned.has_value() && (!t->window() || t->window()->m_pinned != onlyPinned.value())) continue; - if (onlyVisible.has_value() && w->isHidden() == onlyVisible.value()) + if (onlyVisible.has_value() && (!t->window() || t->window()->isHidden() == onlyVisible.value())) continue; no++; } @@ -445,16 +436,16 @@ int CWorkspace::getWindows(std::optional onlyTiled, std::optional on int CWorkspace::getGroups(std::optional onlyTiled, std::optional onlyPinned, std::optional onlyVisible) { int no = 0; - for (auto const& w : g_pCompositor->m_windows) { - if (w->workspaceID() != m_id || !w->m_isMapped) + for (auto const& g : Desktop::View::groups()) { + const auto HEAD = g->head(); + + if (HEAD->workspaceID() != m_id || !HEAD->m_isMapped) continue; - if (!w->m_groupData.head) + if (onlyTiled.has_value() && HEAD->m_isFloating == onlyTiled.value()) continue; - if (onlyTiled.has_value() && w->m_isFloating == onlyTiled.value()) + if (onlyPinned.has_value() && HEAD->m_pinned != onlyPinned.value()) continue; - if (onlyPinned.has_value() && w->m_pinned != onlyPinned.value()) - continue; - if (onlyVisible.has_value() && w->isHidden() == onlyVisible.value()) + if (onlyVisible.has_value() && g->current()->isHidden() == onlyVisible.value()) continue; no++; } @@ -499,13 +490,13 @@ void CWorkspace::updateWindowDecos() { } void CWorkspace::updateWindowData() { - const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(m_self.lock()); + const auto WORKSPACERULE = Config::workspaceRuleMgr()->getWorkspaceRuleFor(m_self.lock()); for (auto const& w : g_pCompositor->m_windows) { if (w->m_workspace != m_self) continue; - w->updateWindowData(WORKSPACERULE); + w->updateWindowData(WORKSPACERULE.value_or(Config::CWorkspaceRule{})); } } @@ -522,27 +513,25 @@ void CWorkspace::rename(const std::string& name) { if (g_pCompositor->isWorkspaceSpecial(m_id)) return; - Debug::log(LOG, "CWorkspace::rename: Renaming workspace {} to '{}'", m_id, name); + Log::logger->log(Log::DEBUG, "CWorkspace::rename: Renaming workspace {} to '{}'", m_id, name); m_name = name; - const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(m_self.lock()); - setPersistent(WORKSPACERULE.isPersistent); + const auto WORKSPACERULE = Config::workspaceRuleMgr()->getWorkspaceRuleFor(m_self.lock()).value_or(Config::CWorkspaceRule{}); + setPersistent(WORKSPACERULE.m_isPersistent); - if (WORKSPACERULE.isPersistent) - g_pCompositor->ensurePersistentWorkspacesPresent(std::vector{WORKSPACERULE}, m_self.lock()); + if (WORKSPACERULE.m_isPersistent) + g_pCompositor->ensurePersistentWorkspacesPresent(std::vector{WORKSPACERULE}, m_self.lock()); g_pEventManager->postEvent({.event = "renameworkspace", .data = std::to_string(m_id) + "," + m_name}); m_events.renamed.emit(); } void CWorkspace::updateWindows() { - m_hasFullscreenWindow = std::ranges::any_of(g_pCompositor->m_windows, [this](const auto& w) { return w->m_isMapped && w->m_workspace == m_self && w->isFullscreen(); }); + m_hasFullscreenWindow = std::ranges::any_of(m_space->targets(), [](const auto& t) { return t->fullscreenMode() != FSMODE_NONE; }); - for (auto const& w : g_pCompositor->m_windows) { - if (!w->m_isMapped || w->m_workspace != m_self) - continue; - - w->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE); + for (auto const& t : m_space->targets()) { + if (t->window()) + t->window()->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE); } } diff --git a/src/desktop/Workspace.hpp b/src/desktop/Workspace.hpp index 72bc3a67e..81af38cd1 100644 --- a/src/desktop/Workspace.hpp +++ b/src/desktop/Workspace.hpp @@ -6,6 +6,10 @@ #include "../helpers/MiscFunctions.hpp" #include "../helpers/signal/Signal.hpp" +namespace Layout { + class CSpace; +}; + enum eFullscreenMode : int8_t { FSMODE_NONE = 0, FSMODE_MAXIMIZED = 1 << 0, @@ -13,8 +17,6 @@ enum eFullscreenMode : int8_t { FSMODE_MAX = (1 << 2) - 1 }; -class CWindow; - class CWorkspace { public: static PHLWORKSPACE create(WORKSPACEID id, PHLMONITOR monitor, std::string name, bool special = false, bool isEmpty = true); @@ -22,7 +24,9 @@ class CWorkspace { CWorkspace(WORKSPACEID id, PHLMONITOR monitor, std::string name, bool special = false, bool isEmpty = true); ~CWorkspace(); - WP m_self; + WP m_self; + + SP m_space; // Workspaces ID-based have IDs > 0 // and workspaces name-based have IDs starting with -1337 @@ -36,9 +40,10 @@ class CWorkspace { wl_array m_wlrCoordinateArr; // for animations - PHLANIMVAR m_renderOffset; - PHLANIMVAR m_alpha; - bool m_forceRendering = false; + PHLANIMVAR m_renderOffset; + PHLANIMVAR m_alpha; + bool m_forceRendering = false; + std::optional m_animationStyle; // allows damage to propagate. bool m_visible = false; @@ -59,29 +64,27 @@ class CWorkspace { bool m_wasCreatedEmpty = true; // Inert: destroyed and invalid. If this is true, release the ptr you have. - bool inert(); - MONITORID monitorID(); - PHLWINDOW getLastFocusedWindow(); - void rememberPrevWorkspace(const PHLWORKSPACE& prevWorkspace); - std::string getConfigName(); - bool matchesStaticSelector(const std::string& selector); - void markInert(); - SWorkspaceIDName getPrevWorkspaceIDName() const; - void updateWindowDecos(); - void updateWindowData(); - int getWindows(std::optional onlyTiled = {}, std::optional onlyPinned = {}, std::optional onlyVisible = {}); - int getGroups(std::optional onlyTiled = {}, std::optional onlyPinned = {}, std::optional onlyVisible = {}); - bool hasUrgentWindow(); - PHLWINDOW getFirstWindow(); - PHLWINDOW getTopLeftWindow(); - PHLWINDOW getFullscreenWindow(); - bool isVisible(); - bool isVisibleNotCovered(); - void rename(const std::string& name = ""); - void forceReportSizesToWindows(); - void updateWindows(); - void setPersistent(bool persistent); - bool isPersistent(); + bool inert(); + MONITORID monitorID(); + PHLWINDOW getLastFocusedWindow(); + std::string getConfigName(); + bool matchesStaticSelector(const std::string& selector); + void markInert(); + void updateWindowDecos(); + void updateWindowData(); + int getWindows(std::optional onlyTiled = {}, std::optional onlyPinned = {}, std::optional onlyVisible = {}); + int getGroups(std::optional onlyTiled = {}, std::optional onlyPinned = {}, std::optional onlyVisible = {}); + bool hasUrgentWindow(); + PHLWINDOW getFirstWindow(); + PHLWINDOW getTopLeftWindow(); + PHLWINDOW getFullscreenWindow(); + bool isVisible(); + bool isVisibleNotCovered(); + void rename(const std::string& name = ""); + void forceReportSizesToWindows(); + void updateWindows(); + void setPersistent(bool persistent); + bool isPersistent(); struct { CSignalT<> destroy; @@ -91,16 +94,13 @@ class CWorkspace { } m_events; private: - void init(PHLWORKSPACE self); - // Previous workspace ID and name is stored during a workspace change, allowing travel - // to the previous workspace. - SWorkspaceIDName m_prevWorkspace; + void init(PHLWORKSPACE self); - SP m_focusedWindowHook; - bool m_inert = true; + CHyprSignalListener m_focusedWindowHook; + bool m_inert = true; - SP m_selfPersistent; // for persistent workspaces. - bool m_persistent = false; + SP m_selfPersistent; // for persistent workspaces. + bool m_persistent = false; }; inline bool valid(const PHLWORKSPACE& ref) { diff --git a/src/desktop/history/WindowHistoryTracker.cpp b/src/desktop/history/WindowHistoryTracker.cpp new file mode 100644 index 000000000..1dd321642 --- /dev/null +++ b/src/desktop/history/WindowHistoryTracker.cpp @@ -0,0 +1,49 @@ +#include "WindowHistoryTracker.hpp" + +#include "../view/Window.hpp" +#include "../../event/EventBus.hpp" + +using namespace Desktop; +using namespace Desktop::History; + +SP History::windowTracker() { + static SP tracker = makeShared(); + return tracker; +} + +CWindowHistoryTracker::CWindowHistoryTracker() { + static auto P = Event::bus()->m_events.window.openEarly.listen([this](PHLWINDOW pWindow) { + // add a last track + m_history.insert(m_history.begin(), pWindow); + }); + + static auto P1 = Event::bus()->m_events.window.active.listen([this](PHLWINDOW window, uint8_t reason) { track(window); }); +} + +void CWindowHistoryTracker::track(PHLWINDOW w) { + std::erase(m_history, w); + m_history.emplace_back(w); +} + +const std::vector& CWindowHistoryTracker::fullHistory() { + gc(); + return m_history; +} + +std::vector CWindowHistoryTracker::historyForWorkspace(PHLWORKSPACE ws) { + gc(); + std::vector windows; + + for (const auto& w : m_history) { + if (w->m_workspace != ws) + continue; + + windows.emplace_back(w); + } + + return windows; +} + +void CWindowHistoryTracker::gc() { + std::erase_if(m_history, [](const auto& e) { return !e; }); +} diff --git a/src/desktop/history/WindowHistoryTracker.hpp b/src/desktop/history/WindowHistoryTracker.hpp new file mode 100644 index 000000000..926456839 --- /dev/null +++ b/src/desktop/history/WindowHistoryTracker.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "../DesktopTypes.hpp" + +#include + +namespace Desktop::History { + class CWindowHistoryTracker { + public: + CWindowHistoryTracker(); + ~CWindowHistoryTracker() = default; + + CWindowHistoryTracker(const CWindowHistoryTracker&) = delete; + CWindowHistoryTracker(CWindowHistoryTracker&) = delete; + CWindowHistoryTracker(CWindowHistoryTracker&&) = delete; + + // History is ordered old -> new, meaning .front() is oldest, while .back() is newest + + const std::vector& fullHistory(); + std::vector historyForWorkspace(PHLWORKSPACE ws); + + private: + std::vector m_history; + + void track(PHLWINDOW w); + void gc(); + }; + + SP windowTracker(); +}; \ No newline at end of file diff --git a/src/desktop/history/WorkspaceHistoryTracker.cpp b/src/desktop/history/WorkspaceHistoryTracker.cpp new file mode 100644 index 000000000..d4e8e5008 --- /dev/null +++ b/src/desktop/history/WorkspaceHistoryTracker.cpp @@ -0,0 +1,134 @@ +#include "WorkspaceHistoryTracker.hpp" + +#include "../../helpers/Monitor.hpp" +#include "../Workspace.hpp" +#include "../state/FocusState.hpp" +#include "../../managers/eventLoop/EventLoopManager.hpp" +#include "../../event/EventBus.hpp" +#include "../../config/ConfigValue.hpp" + +#include + +#include + +using namespace Desktop; +using namespace Desktop::History; + +SP History::workspaceTracker() { + static SP tracker = makeShared(); + return tracker; +} + +CWorkspaceHistoryTracker::CWorkspaceHistoryTracker() { + static auto P = Event::bus()->m_events.workspace.active.listen([this](PHLWORKSPACE workspace) { 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); + }); + }); +} + +void CWorkspaceHistoryTracker::track(PHLWORKSPACE ws) { + if (!ws || !ws->m_monitor) + return; + + static auto PALLOWWORKSPACECYCLES = CConfigValue("binds:allow_workspace_cycles"); + + if (!m_history.empty() && m_history.front().workspace == ws && !*PALLOWWORKSPACECYCLES) + return; + + // Erase from timeline if it exists so we can move it to the very front + std::erase_if(m_history, [&](const auto& entry) { return entry.workspace == ws; }); + + // Push the newly focused workspace to the top of our MRU list + m_history.push_front(SHistoryEntry{.workspace = ws, .monitor = ws->m_monitor, .name = ws->m_name, .id = ws->m_id}); + + Hyprutils::Utils::CScopeGuard x([&] { setLastWorkspaceData(ws); }); +} + +void CWorkspaceHistoryTracker::gc() { + std::vector monitorCounts; + std::erase_if(m_history, [&](const auto& entry) { + // Search if the monitor has been seen already + for (auto& mon : monitorCounts | std::views::drop(1)) { + // Remove entry + if (mon == entry.monitor) + return !entry.workspace; + } + // Add monitor to seen monitors + monitorCounts.emplace_back(entry.monitor); + return false; + }); +} + +const CWorkspaceHistoryTracker::SHistoryEntry CWorkspaceHistoryTracker::previousWorkspace(PHLWORKSPACE ws) { + gc(); + auto it = std::ranges::find_if(m_history, [&](const auto& entry) { return entry.workspace == ws; }); + + // If the workspace is found in history, the previous one is simply the next element down the timeline + if (it != m_history.end() && std::next(it) != m_history.end()) + return *std::next(it); + + // No prior history found + return SHistoryEntry{.id = WORKSPACE_INVALID}; +} + +SWorkspaceIDName CWorkspaceHistoryTracker::previousWorkspaceIDName(PHLWORKSPACE ws) { + const auto DATA = previousWorkspace(ws); + + if (DATA.id == WORKSPACE_INVALID) + return SWorkspaceIDName{.id = WORKSPACE_INVALID}; + + return SWorkspaceIDName{.id = DATA.id, .name = DATA.name, .isAutoIDd = DATA.id <= 0}; +} + +const CWorkspaceHistoryTracker::SHistoryEntry CWorkspaceHistoryTracker::previousWorkspace(PHLWORKSPACE ws, PHLMONITOR restrict) { + if (!restrict) + return previousWorkspace(ws); + + gc(); + + auto it = std::ranges::find_if(m_history, [&](const auto& entry) { return entry.workspace == ws; }); + + // Start looking from the element immediately following `ws` in the list + if (it != m_history.end()) + it++; + else + it = m_history.begin(); + + // Scan down the timeline until we hit a workspace mapped to the restricted monitor + while (it != m_history.end()) { + if (it->monitor == restrict) + return *it; + + it++; + } + + // Entry not found + return SHistoryEntry{.id = WORKSPACE_INVALID}; +} + +SWorkspaceIDName CWorkspaceHistoryTracker::previousWorkspaceIDName(PHLWORKSPACE ws, PHLMONITOR restrict) { + const auto DATA = previousWorkspace(ws, restrict); + if (DATA.id == WORKSPACE_INVALID) + return SWorkspaceIDName{.id = WORKSPACE_INVALID}; + + return SWorkspaceIDName{.id = DATA.id, .name = DATA.name, .isAutoIDd = DATA.id <= 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 new file mode 100644 index 000000000..e80a51524 --- /dev/null +++ b/src/desktop/history/WorkspaceHistoryTracker.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include "../DesktopTypes.hpp" +#include "../../SharedDefs.hpp" +#include "../../macros.hpp" +#include "../../helpers/MiscFunctions.hpp" + +#include + +namespace Desktop::History { + class CWorkspaceHistoryTracker { + public: + CWorkspaceHistoryTracker(); + ~CWorkspaceHistoryTracker() = default; + + CWorkspaceHistoryTracker(const CWorkspaceHistoryTracker&) = delete; + CWorkspaceHistoryTracker(CWorkspaceHistoryTracker&) = delete; + CWorkspaceHistoryTracker(CWorkspaceHistoryTracker&&) = delete; + + struct SHistoryEntry { + PHLWORKSPACEREF workspace; + PHLMONITORREF monitor; + std::string name = ""; + WORKSPACEID id = WORKSPACE_INVALID; + }; + + const SHistoryEntry previousWorkspace(PHLWORKSPACE ws); + SWorkspaceIDName previousWorkspaceIDName(PHLWORKSPACE ws); + + const SHistoryEntry previousWorkspace(PHLWORKSPACE ws, PHLMONITOR restrict); + SWorkspaceIDName previousWorkspaceIDName(PHLWORKSPACE ws, PHLMONITOR restrict); + + private: + struct SLastWorkspaceData { + PHLMONITORREF monitor; + PHLWORKSPACEREF workspace; + std::string workspaceName = ""; + WORKSPACEID workspaceID = WORKSPACE_INVALID; + } m_lastWorkspaceData; + + std::deque m_history; + + void track(PHLWORKSPACE w); + void gc(); + void setLastWorkspaceData(PHLWORKSPACE w); + }; + + SP workspaceTracker(); +}; diff --git a/src/desktop/reserved/ReservedArea.cpp b/src/desktop/reserved/ReservedArea.cpp new file mode 100644 index 000000000..103beae3a --- /dev/null +++ b/src/desktop/reserved/ReservedArea.cpp @@ -0,0 +1,105 @@ +#include "ReservedArea.hpp" +#include "../../macros.hpp" +#include "../../debug/log/Logger.hpp" + +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})) { + 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)) { + calculate(); +} + +CReservedArea::CReservedArea(const CBox& parent, const CBox& child) { + if (parent.empty() || child.empty()) + return; // empty reserved area + + if (!parent.containsPoint(child.pos() + Vector2D{0.0001, 0.0001}) // + || !parent.containsPoint(child.pos() + child.size() - Vector2D{0.0001, 0.0001})) { + + Log::logger->log(Log::ERR, "CReservedArea: attempted to create a reserved area from parent [{}, {}] and child [{}, {}] which is invalid", parent.pos(), parent.size(), + child.pos(), child.size()); + + m_ok = false; + return; + } + + m_initialTopLeft = child.pos() - parent.pos(); + m_initialBottomRight = (parent.pos() + parent.size()) - (child.pos() + child.size()); + + calculate(); +} + +bool CReservedArea::ok() const { + return m_ok; +} + +void CReservedArea::calculate() { + m_bottomRight = m_initialBottomRight; + m_topLeft = m_initialTopLeft; + + for (const auto& e : m_dynamicReserved) { + m_bottomRight += e.bottomRight; + m_topLeft += e.topLeft; + } +} + +CBox CReservedArea::apply(const CBox& other) const { + auto c = other.copy(); + c.x += m_topLeft.x; + c.y += m_topLeft.y; + c.w -= m_topLeft.x + m_bottomRight.x; + c.h -= m_topLeft.y + m_bottomRight.y; + return c; +} + +void CReservedArea::applyip(CBox& other) const { + other.x += m_topLeft.x; + other.y += m_topLeft.y; + other.w -= m_topLeft.x + m_bottomRight.x; + other.h -= m_topLeft.y + m_bottomRight.y; +} + +bool CReservedArea::operator==(const CReservedArea& other) const { + return other.m_bottomRight == m_bottomRight && other.m_topLeft == m_topLeft; +} + +double CReservedArea::left() const { + return m_topLeft.x; +} + +double CReservedArea::right() const { + return m_bottomRight.x; +} + +double CReservedArea::top() const { + return m_topLeft.y; +} + +double CReservedArea::bottom() const { + return m_bottomRight.y; +} + +void CReservedArea::resetType(eReservedDynamicType t) { + m_dynamicReserved[t] = {}; + calculate(); +} + +void CReservedArea::addType(eReservedDynamicType t, const Vector2D& topLeft, const Vector2D& bottomRight) { + 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(); +} + +void CReservedArea::addType(eReservedDynamicType t, const CReservedArea& area) { + addType(t, {area.left(), area.top()}, {area.right(), area.bottom()}); +} diff --git a/src/desktop/reserved/ReservedArea.hpp b/src/desktop/reserved/ReservedArea.hpp new file mode 100644 index 000000000..ca5978a7c --- /dev/null +++ b/src/desktop/reserved/ReservedArea.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include "../../helpers/math/Math.hpp" +#include + +namespace Desktop { + enum eReservedDynamicType : uint8_t { + RESERVED_DYNAMIC_TYPE_LS = 0, + RESERVED_DYNAMIC_TYPE_ERROR_BAR, + + RESERVED_DYNAMIC_TYPE_END, + }; + + class CReservedArea { + public: + CReservedArea() = default; + CReservedArea(const Vector2D& tl, const Vector2D& br); + CReservedArea(double top, double right, double bottom, double left); + CReservedArea(const CBox& parent, const CBox& child); + ~CReservedArea() = default; + + CBox apply(const CBox& other) const; + void applyip(CBox& other) const; + + void resetType(eReservedDynamicType); + void addType(eReservedDynamicType, const Vector2D& topLeft, const Vector2D& bottomRight); + void addType(eReservedDynamicType, const CReservedArea& area); + + double left() const; + double right() const; + double top() const; + double bottom() const; + + bool ok() const; + + bool operator==(const CReservedArea& other) const; + + private: + void calculate(); + + Vector2D m_topLeft, m_bottomRight; + Vector2D m_initialTopLeft, m_initialBottomRight; + + bool m_ok = true; + + struct SDynamicData { + Vector2D topLeft, bottomRight; + }; + + std::array m_dynamicReserved; + }; +}; \ No newline at end of file diff --git a/src/desktop/rule/Engine.cpp b/src/desktop/rule/Engine.cpp index 3232035d6..fa0c2e27b 100644 --- a/src/desktop/rule/Engine.cpp +++ b/src/desktop/rule/Engine.cpp @@ -1,6 +1,6 @@ #include "Engine.hpp" #include "Rule.hpp" -#include "../LayerSurface.hpp" +#include "../view/LayerSurface.hpp" #include "../../Compositor.hpp" using namespace Desktop; diff --git a/src/desktop/rule/Rule.cpp b/src/desktop/rule/Rule.cpp index fe7271a67..5e3141cdd 100644 --- a/src/desktop/rule/Rule.cpp +++ b/src/desktop/rule/Rule.cpp @@ -1,5 +1,5 @@ #include "Rule.hpp" -#include "../../debug/Log.hpp" +#include "../../debug/log/Logger.hpp" #include #include "matchEngine/RegexMatchEngine.hpp" @@ -48,10 +48,11 @@ static const std::unordered_map RULE_ENGINES = {RULE_PROP_FULLSCREENSTATE_INTERNAL, RULE_MATCH_ENGINE_INT}, // {RULE_PROP_FULLSCREENSTATE_CLIENT, RULE_MATCH_ENGINE_INT}, // {RULE_PROP_ON_WORKSPACE, RULE_MATCH_ENGINE_WORKSPACE}, // - {RULE_PROP_CONTENT, RULE_MATCH_ENGINE_INT}, // + {RULE_PROP_CONTENT, RULE_MATCH_ENGINE_REGEX}, // {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() { @@ -84,7 +85,7 @@ IRule::IRule(const std::string& name) : m_name(name) { void IRule::registerMatch(eRuleProperty p, const std::string& s) { if (!RULE_ENGINES.contains(p)) { - Debug::log(ERR, "BUG THIS: IRule: RULE_ENGINES does not contain rule idx {}", sc>(p)); + Log::logger->log(Log::ERR, "BUG THIS: IRule: RULE_ENGINES does not contain rule idx {}", sc>(p)); return; } @@ -125,10 +126,11 @@ const std::string& IRule::name() { return m_name; } -void IRule::markAsExecRule(const std::string& token, bool persistent) { +void IRule::markAsExecRule(const std::string& token, uint64_t pid, bool persistent) { m_execData.isExecRule = true; m_execData.isExecPersistent = persistent; m_execData.token = token; + m_execData.pid = pid; m_execData.expiresAt = Time::steadyNow() + std::chrono::minutes(1); } diff --git a/src/desktop/rule/Rule.hpp b/src/desktop/rule/Rule.hpp index 2b852b3a5..efd3cb39a 100644 --- a/src/desktop/rule/Rule.hpp +++ b/src/desktop/rule/Rule.hpp @@ -6,7 +6,6 @@ #include "../../helpers/time/Time.hpp" #include #include -#include #include namespace Desktop::Rule { @@ -31,6 +30,7 @@ namespace Desktop::Rule { RULE_PROP_XDG_TAG = (1 << 16), RULE_PROP_NAMESPACE = (1 << 17), RULE_PROP_EXEC_TOKEN = (1 << 18), + RULE_PROP_EXEC_PID = (1 << 19), RULE_PROP_ALL = std::numeric_limits>::max(), }; @@ -52,7 +52,7 @@ namespace Desktop::Rule { virtual std::underlying_type_t getPropertiesMask(); void registerMatch(eRuleProperty, const std::string&); - void markAsExecRule(const std::string& token, bool persistent = false); + void markAsExecRule(const std::string& token, uint64_t pid, bool persistent = false); bool isExecRule(); bool isExecPersistent(); bool execExpired(); @@ -78,7 +78,8 @@ namespace Desktop::Rule { bool isExecRule = false; bool isExecPersistent = false; std::string token; + uint64_t pid = 0; Time::steady_tp expiresAt; } m_execData; }; -} \ No newline at end of file +} diff --git a/src/desktop/rule/effect/EffectContainer.hpp b/src/desktop/rule/effect/EffectContainer.hpp index cb2157a6b..51cae07ed 100644 --- a/src/desktop/rule/effect/EffectContainer.hpp +++ b/src/desktop/rule/effect/EffectContainer.hpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace Desktop::Rule { template @@ -78,4 +79,4 @@ namespace Desktop::Rule { std::vector m_keys; size_t m_originalSize = 0; }; -}; \ No newline at end of file +}; diff --git a/src/desktop/rule/layerRule/LayerRule.cpp b/src/desktop/rule/layerRule/LayerRule.cpp index be7576729..eccd99142 100644 --- a/src/desktop/rule/layerRule/LayerRule.cpp +++ b/src/desktop/rule/layerRule/LayerRule.cpp @@ -1,6 +1,6 @@ #include "LayerRule.hpp" -#include "../../../debug/Log.hpp" -#include "../../LayerSurface.hpp" +#include "../../../debug/log/Logger.hpp" +#include "../../view/LayerSurface.hpp" using namespace Desktop; using namespace Desktop::Rule; @@ -28,7 +28,7 @@ bool CLayerRule::matches(PHLLS ls) { for (const auto& [prop, engine] : m_matchEngines) { switch (prop) { default: { - Debug::log(TRACE, "CLayerRule::matches: skipping prop entry {}", sc>(prop)); + Log::logger->log(Log::TRACE, "CLayerRule::matches: skipping prop entry {}", sc>(prop)); break; } diff --git a/src/desktop/rule/layerRule/LayerRuleApplicator.cpp b/src/desktop/rule/layerRule/LayerRuleApplicator.cpp index bb7da97fb..8c157dae8 100644 --- a/src/desktop/rule/layerRule/LayerRuleApplicator.cpp +++ b/src/desktop/rule/layerRule/LayerRuleApplicator.cpp @@ -1,44 +1,70 @@ #include "LayerRuleApplicator.hpp" #include "LayerRule.hpp" #include "../Engine.hpp" -#include "../../LayerSurface.hpp" +#include "../../view/LayerSurface.hpp" #include "../../types/OverridableVar.hpp" #include "../../../helpers/MiscFunctions.hpp" +#include "../../../event/EventBus.hpp" +#include using namespace Desktop; using namespace Desktop::Rule; +namespace { + template + void resetRuleProp(std::pair, std::underlying_type_t>& prop, + std::underlying_type_t props, Desktop::Types::eOverridePriority prio) { + auto& [value, propMask] = prop; + + if (!(propMask & props)) + return; + + if (prio == Desktop::Types::PRIORITY_WINDOW_RULE) + propMask &= ~props; + + value.unset(prio); + } +} + CLayerRuleApplicator::CLayerRuleApplicator(PHLLS ls) : m_ls(ls) { ; } void CLayerRuleApplicator::resetProps(std::underlying_type_t props, Types::eOverridePriority prio) { - // TODO: fucking kill me, is there a better way to do this? + std::apply([&](auto&... prop) { (resetRuleProp(prop, props, prio), ...); }, + std::forward_as_tuple(m_noanim, m_blur, m_blurPopups, m_dimAround, m_xray, m_noScreenShare, m_order, m_aboveLock, m_ignoreAlpha, m_animationStyle)); -#define UNSET(x) \ - if (m_##x.second & props) { \ - if (prio == Types::PRIORITY_WINDOW_RULE) \ - m_##x.second &= ~props; \ - m_##x.first.unset(prio); \ - } - - UNSET(noanim) - UNSET(blur) - UNSET(blurPopups) - UNSET(dimAround) - UNSET(xray) - UNSET(noScreenShare) - UNSET(order) - UNSET(aboveLock) - UNSET(ignoreAlpha) - UNSET(animationStyle) + 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: { - Debug::log(ERR, "CLayerRuleApplicator::applyDynamicRule: BUG THIS: LAYER_RULE_EFFECT_NONE??"); + Log::logger->log(Log::ERR, "CLayerRuleApplicator::applyDynamicRule: BUG THIS: LAYER_RULE_EFFECT_NONE??"); break; } case LAYER_RULE_EFFECT_NO_ANIM: { @@ -73,23 +99,23 @@ void CLayerRuleApplicator::applyDynamicRule(const SP& rule) { } case LAYER_RULE_EFFECT_ORDER: { try { - m_noScreenShare.first.set(std::stoi(effect), Types::PRIORITY_WINDOW_RULE); - m_noScreenShare.second |= rule->getPropertiesMask(); - } catch (...) { Debug::log(ERR, "CLayerRuleApplicator::applyDynamicRule: invalid order {}", effect); } + m_order.first.set(std::stoi(effect), Types::PRIORITY_WINDOW_RULE); + m_order.second |= rule->getPropertiesMask(); + } catch (...) { Log::logger->log(Log::ERR, "CLayerRuleApplicator::applyDynamicRule: invalid order {}", effect); } break; } case LAYER_RULE_EFFECT_ABOVE_LOCK: { try { m_aboveLock.first.set(std::clamp(std::stoull(effect), 0ULL, 2ULL), Types::PRIORITY_WINDOW_RULE); m_aboveLock.second |= rule->getPropertiesMask(); - } catch (...) { Debug::log(ERR, "CLayerRuleApplicator::applyDynamicRule: invalid order {}", effect); } + } catch (...) { Log::logger->log(Log::ERR, "CLayerRuleApplicator::applyDynamicRule: invalid order {}", effect); } break; } case LAYER_RULE_EFFECT_IGNORE_ALPHA: { try { m_ignoreAlpha.first.set(std::clamp(std::stof(effect), 0.F, 1.F), Types::PRIORITY_WINDOW_RULE); m_ignoreAlpha.second |= rule->getPropertiesMask(); - } catch (...) { Debug::log(ERR, "CLayerRuleApplicator::applyDynamicRule: invalid order {}", effect); } + } catch (...) { Log::logger->log(Log::ERR, "CLayerRuleApplicator::applyDynamicRule: invalid order {}", effect); } break; } case LAYER_RULE_EFFECT_ANIMATION: { @@ -125,4 +151,7 @@ void CLayerRuleApplicator::propertiesChanged(std::underlying_type_tm_events.layer.updateRules.emit(m_ls.lock()); } diff --git a/src/desktop/rule/layerRule/LayerRuleApplicator.hpp b/src/desktop/rule/layerRule/LayerRuleApplicator.hpp index 97f15b043..5669ff870 100644 --- a/src/desktop/rule/layerRule/LayerRuleApplicator.hpp +++ b/src/desktop/rule/layerRule/LayerRuleApplicator.hpp @@ -1,10 +1,11 @@ #pragma once +#include "LayerRuleEffectContainer.hpp" #include "../../DesktopTypes.hpp" #include "../Rule.hpp" #include "../../types/OverridableVar.hpp" #include "../../../helpers/math/Math.hpp" -#include "../../../config/ConfigDataValues.hpp" +#include "../../../config/shared/complex/ComplexDataTypes.hpp" namespace Desktop::Rule { class CLayerRule; @@ -21,6 +22,17 @@ namespace Desktop::Rule { void propertiesChanged(std::underlying_type_t props); void resetProps(std::underlying_type_t props, Types::eOverridePriority prio = Types::PRIORITY_WINDOW_RULE); + struct SCustomPropContainer { + CLayerRuleEffectContainer::storageType idx = LAYER_RULE_EFFECT_NONE; + std::underlying_type_t propMask = RULE_PROP_NONE; + std::string effect; + }; + + // This struct holds props that were dynamically registered. Plugins may read this. + struct { + std::unordered_map> props; + } m_otherProps; + #define COMMA , #define DEFINE_PROP(type, name, def) \ private: \ diff --git a/src/desktop/rule/matchEngine/IntMatchEngine.cpp b/src/desktop/rule/matchEngine/IntMatchEngine.cpp index c5bc87f68..c8f3c09eb 100644 --- a/src/desktop/rule/matchEngine/IntMatchEngine.cpp +++ b/src/desktop/rule/matchEngine/IntMatchEngine.cpp @@ -1,12 +1,12 @@ #include "IntMatchEngine.hpp" -#include "../../../debug/Log.hpp" +#include "../../../debug/log/Logger.hpp" using namespace Desktop::Rule; CIntMatchEngine::CIntMatchEngine(const std::string& s) { try { m_value = std::stoi(s); - } catch (...) { Debug::log(ERR, "CIntMatchEngine: invalid input {}", s); } + } catch (...) { Log::logger->log(Log::ERR, "CIntMatchEngine: invalid input {}", s); } } bool CIntMatchEngine::match(int other) { diff --git a/src/desktop/rule/matchEngine/TagMatchEngine.cpp b/src/desktop/rule/matchEngine/TagMatchEngine.cpp index d669822a8..6b38c9808 100644 --- a/src/desktop/rule/matchEngine/TagMatchEngine.cpp +++ b/src/desktop/rule/matchEngine/TagMatchEngine.cpp @@ -1,5 +1,6 @@ #include "TagMatchEngine.hpp" #include "../../../helpers/TagKeeper.hpp" +#include using namespace Desktop::Rule; @@ -9,4 +10,4 @@ CTagMatchEngine::CTagMatchEngine(const std::string& tag) : m_tag(tag) { bool CTagMatchEngine::match(const CTagKeeper& keeper) { return keeper.isTagged(m_tag); -} \ No newline at end of file +} diff --git a/src/desktop/rule/matchEngine/TagMatchEngine.hpp b/src/desktop/rule/matchEngine/TagMatchEngine.hpp index f8ef3e22a..e5e65c9b7 100644 --- a/src/desktop/rule/matchEngine/TagMatchEngine.hpp +++ b/src/desktop/rule/matchEngine/TagMatchEngine.hpp @@ -1,6 +1,7 @@ #pragma once #include "MatchEngine.hpp" +#include namespace Desktop::Rule { class CTagMatchEngine : public IMatchEngine { @@ -13,4 +14,4 @@ namespace Desktop::Rule { private: std::string m_tag; }; -} \ No newline at end of file +} diff --git a/src/desktop/rule/matchEngine/WorkspaceMatchEngine.cpp b/src/desktop/rule/matchEngine/WorkspaceMatchEngine.cpp index abaa16577..fea5c384b 100644 --- a/src/desktop/rule/matchEngine/WorkspaceMatchEngine.cpp +++ b/src/desktop/rule/matchEngine/WorkspaceMatchEngine.cpp @@ -8,5 +8,5 @@ CWorkspaceMatchEngine::CWorkspaceMatchEngine(const std::string& s) : m_value(s) } bool CWorkspaceMatchEngine::match(PHLWORKSPACE ws) { - return ws->matchesStaticSelector(m_value); + return ws && ws->matchesStaticSelector(m_value); } diff --git a/src/desktop/rule/matchEngine/WorkspaceMatchEngine.hpp b/src/desktop/rule/matchEngine/WorkspaceMatchEngine.hpp index c70bc8b4d..dcdf41367 100644 --- a/src/desktop/rule/matchEngine/WorkspaceMatchEngine.hpp +++ b/src/desktop/rule/matchEngine/WorkspaceMatchEngine.hpp @@ -1,6 +1,7 @@ #pragma once #include "MatchEngine.hpp" +#include namespace Desktop::Rule { class CWorkspaceMatchEngine : public IMatchEngine { @@ -13,4 +14,4 @@ namespace Desktop::Rule { private: std::string m_value = ""; }; -} \ No newline at end of file +} diff --git a/src/desktop/rule/windowRule/WindowRule.cpp b/src/desktop/rule/windowRule/WindowRule.cpp index 893243b09..b53caedc1 100644 --- a/src/desktop/rule/windowRule/WindowRule.cpp +++ b/src/desktop/rule/windowRule/WindowRule.cpp @@ -1,12 +1,15 @@ #include "WindowRule.hpp" -#include "../../Window.hpp" +#include "../../view/Window.hpp" #include "../../../helpers/Monitor.hpp" #include "../../../Compositor.hpp" #include "../../../managers/TokenManager.hpp" #include "../../../desktop/state/FocusState.hpp" +#include + using namespace Desktop; using namespace Desktop::Rule; +using namespace Hyprutils::String; CWindowRule::CWindowRule(const std::string& name) : IRule(name) { ; @@ -32,7 +35,7 @@ bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) { for (const auto& [prop, engine] : m_matchEngines) { switch (prop) { default: { - Debug::log(TRACE, "CWindowRule::matches: skipping prop entry {}", sc>(prop)); + Log::logger->log(Log::TRACE, "CWindowRule::matches: skipping prop entry {}", sc>(prop)); break; } @@ -77,7 +80,7 @@ bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) { return false; break; case RULE_PROP_GROUP: - if (!engine->match(w->m_groupData.pNextWindow)) + if (!engine->match(!!w->m_group)) return false; break; case RULE_PROP_MODAL: @@ -97,27 +100,31 @@ bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) { return false; break; case RULE_PROP_CONTENT: - if (!engine->match(NContentType::toString(w->getContentType()))) + if (!engine->match(std::format("{}", sc(w->getContentType()))) && !engine->match(NContentType::toString(w->getContentType()))) return false; break; case RULE_PROP_XDG_TAG: if (!w->xdgTag().has_value() || !engine->match(*w->xdgTag())) return false; break; + case RULE_PROP_EXEC_TOKEN: - // this is only allowed on static rules, we don't need it on dynamic plus it's expensive if (!allowEnvLookup) break; - const auto ENV = w->getEnv(); - if (ENV.contains(EXEC_RULE_ENV_NAME)) { - const auto TKN = ENV.at(EXEC_RULE_ENV_NAME); - if (!engine->match(TKN)) - return false; - break; - } + const auto ENV = w->getEnv(); + bool match = false; - return false; + if (ENV.contains(EXEC_RULE_ENV_NAME)) { + if (engine->match(ENV.at(EXEC_RULE_ENV_NAME))) + match = true; + } else if (m_matchEngines.contains(RULE_PROP_EXEC_PID)) { + if (m_matchEngines.at(RULE_PROP_EXEC_PID)->match(w->getPID())) + match = true; + } + if (!match) + return false; + break; } } @@ -153,11 +160,6 @@ SP CWindowRule::buildFromExecString(std::string&& s) { wr->addEffect(*EFFECT, std::string{"1"}); } - const auto TOKEN = g_pTokenManager->registerNewToken(nullptr, std::chrono::seconds(1)); - - wr->markAsExecRule(TOKEN, false /* TODO: could be nice. */); - wr->registerMatch(RULE_PROP_EXEC_TOKEN, TOKEN); - return wr; } diff --git a/src/desktop/rule/windowRule/WindowRuleApplicator.cpp b/src/desktop/rule/windowRule/WindowRuleApplicator.cpp index 3474f2400..4af0d2ba1 100644 --- a/src/desktop/rule/windowRule/WindowRuleApplicator.cpp +++ b/src/desktop/rule/windowRule/WindowRuleApplicator.cpp @@ -2,76 +2,69 @@ #include "WindowRule.hpp" #include "../Engine.hpp" #include "../utils/SetUtils.hpp" -#include "../../Window.hpp" +#include "../../view/Window.hpp" #include "../../types/OverridableVar.hpp" -#include "../../../managers/LayoutManager.hpp" -#include "../../../managers/HookSystemManager.hpp" +#include "../../../event/EventBus.hpp" #include +#include +#include +#include using namespace Hyprutils::String; using namespace Desktop; using namespace Desktop::Rule; +namespace { + template + void resetRuleProp(std::pair, std::underlying_type_t>& prop, + std::underlying_type_t props, Desktop::Types::eOverridePriority prio, + std::unordered_set& effectsNuked, TEffect&& effect) { + auto& [value, propMask] = prop; + + if (!(propMask & props)) + return; + + if (prio == Desktop::Types::PRIORITY_WINDOW_RULE) { + effectsNuked.emplace(effect()); + propMask &= ~props; + } + + value.unset(prio); + } +} + CWindowRuleApplicator::CWindowRuleApplicator(PHLWINDOW w) : m_window(w) { ; } std::unordered_set CWindowRuleApplicator::resetProps(std::underlying_type_t props, Types::eOverridePriority prio) { - // TODO: fucking kill me, is there a better way to do this? - std::unordered_set effectsNuked; -#define UNSET(x) \ - if (m_##x.second & props) { \ - if (prio == Types::PRIORITY_WINDOW_RULE) { \ - effectsNuked.emplace(x##Effect()); \ - m_##x.second &= ~props; \ - } \ - m_##x.first.unset(prio); \ - } - - UNSET(alpha) - UNSET(alphaInactive) - UNSET(alphaFullscreen) - UNSET(allowsInput) - UNSET(decorate) - UNSET(focusOnActivate) - UNSET(keepAspectRatio) - UNSET(nearestNeighbor) - UNSET(noAnim) - UNSET(noBlur) - UNSET(noDim) - UNSET(noFocus) - UNSET(noMaxSize) - UNSET(noShadow) - UNSET(noShortcutsInhibit) - UNSET(opaque) - UNSET(dimAround) - UNSET(RGBX) - UNSET(syncFullscreen) - UNSET(tearing) - UNSET(xray) - UNSET(renderUnfocused) - UNSET(noFollowMouse) - UNSET(noScreenShare) - UNSET(noVRR) - UNSET(persistentSize) - UNSET(stayFocused) - UNSET(idleInhibitMode) - UNSET(borderSize) - UNSET(rounding) - UNSET(roundingPower) - UNSET(scrollMouse) - UNSET(scrollTouchpad) - UNSET(animationStyle) - UNSET(maxSize) - UNSET(minSize) - UNSET(activeBorderColor) - UNSET(inactiveBorderColor) - -#undef UNSET + std::apply([&](auto&&... prop) { (resetRuleProp(prop.first.get(), props, prio, effectsNuked, prop.second), ...); }, + std::make_tuple( + std::pair{std::ref(m_alpha), [this] { return alphaEffect(); }}, std::pair{std::ref(m_alphaInactive), [this] { return alphaInactiveEffect(); }}, + std::pair{std::ref(m_alphaFullscreen), [this] { return alphaFullscreenEffect(); }}, std::pair{std::ref(m_allowsInput), [this] { return allowsInputEffect(); }}, + std::pair{std::ref(m_decorate), [this] { return decorateEffect(); }}, std::pair{std::ref(m_focusOnActivate), [this] { return focusOnActivateEffect(); }}, + std::pair{std::ref(m_keepAspectRatio), [this] { return keepAspectRatioEffect(); }}, + std::pair{std::ref(m_nearestNeighbor), [this] { return nearestNeighborEffect(); }}, std::pair{std::ref(m_noAnim), [this] { return noAnimEffect(); }}, + std::pair{std::ref(m_noBlur), [this] { return noBlurEffect(); }}, std::pair{std::ref(m_noDim), [this] { return noDimEffect(); }}, + std::pair{std::ref(m_noFocus), [this] { return noFocusEffect(); }}, std::pair{std::ref(m_noMaxSize), [this] { return noMaxSizeEffect(); }}, + std::pair{std::ref(m_noShadow), [this] { return noShadowEffect(); }}, std::pair{std::ref(m_noShortcutsInhibit), [this] { return noShortcutsInhibitEffect(); }}, + std::pair{std::ref(m_opaque), [this] { return opaqueEffect(); }}, std::pair{std::ref(m_dimAround), [this] { return dimAroundEffect(); }}, + std::pair{std::ref(m_RGBX), [this] { return RGBXEffect(); }}, std::pair{std::ref(m_syncFullscreen), [this] { return syncFullscreenEffect(); }}, + std::pair{std::ref(m_tearing), [this] { return tearingEffect(); }}, std::pair{std::ref(m_xray), [this] { return xrayEffect(); }}, + std::pair{std::ref(m_renderUnfocused), [this] { return renderUnfocusedEffect(); }}, + std::pair{std::ref(m_noFollowMouse), [this] { return noFollowMouseEffect(); }}, std::pair{std::ref(m_noScreenShare), [this] { return noScreenShareEffect(); }}, + std::pair{std::ref(m_noVRR), [this] { return noVRREffect(); }}, std::pair{std::ref(m_persistentSize), [this] { return persistentSizeEffect(); }}, + std::pair{std::ref(m_stayFocused), [this] { return stayFocusedEffect(); }}, std::pair{std::ref(m_idleInhibitMode), [this] { return idleInhibitModeEffect(); }}, + std::pair{std::ref(m_borderSize), [this] { return borderSizeEffect(); }}, std::pair{std::ref(m_rounding), [this] { return roundingEffect(); }}, + std::pair{std::ref(m_roundingPower), [this] { return roundingPowerEffect(); }}, std::pair{std::ref(m_scrollMouse), [this] { return scrollMouseEffect(); }}, + std::pair{std::ref(m_scrollTouchpad), [this] { return scrollTouchpadEffect(); }}, + std::pair{std::ref(m_animationStyle), [this] { return animationStyleEffect(); }}, std::pair{std::ref(m_maxSize), [this] { return maxSizeEffect(); }}, + std::pair{std::ref(m_minSize), [this] { return minSizeEffect(); }}, std::pair{std::ref(m_activeBorderColor), [this] { return activeBorderColorEffect(); }}, + std::pair{std::ref(m_inactiveBorderColor), [this] { return inactiveBorderColorEffect(); }})); if (prio == Types::PRIORITY_WINDOW_RULE) { std::erase_if(m_dynamicTags, [props, this](const auto& el) { @@ -96,7 +89,7 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const switch (key) { default: { if (key <= WINDOW_RULE_EFFECT_LAST_STATIC) { - Debug::log(TRACE, "CWindowRuleApplicator::applyDynamicRule: Skipping effect {}, not dynamic", sc>(key)); + Log::logger->log(Log::TRACE, "CWindowRuleApplicator::applyDynamicRule: Skipping effect {}, not dynamic", sc>(key)); break; } @@ -118,21 +111,21 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const } case WINDOW_RULE_EFFECT_NONE: { - Debug::log(ERR, "CWindowRuleApplicator::applyDynamicRule: BUG THIS: WINDOW_RULE_EFFECT_NONE??"); + Log::logger->log(Log::ERR, "CWindowRuleApplicator::applyDynamicRule: BUG THIS: WINDOW_RULE_EFFECT_NONE??"); break; } case WINDOW_RULE_EFFECT_ROUNDING: { try { m_rounding.first.set(std::stoull(effect), Types::PRIORITY_WINDOW_RULE); m_rounding.second |= rule->getPropertiesMask(); - } catch (...) { Debug::log(ERR, "CWindowRuleApplicator::applyDynamicRule: invalid rounding {}", effect); } + } catch (...) { Log::logger->log(Log::ERR, "CWindowRuleApplicator::applyDynamicRule: invalid rounding {}", effect); } break; } case WINDOW_RULE_EFFECT_ROUNDING_POWER: { try { m_roundingPower.first.set(std::clamp(std::stof(effect), 1.F, 10.F), Types::PRIORITY_WINDOW_RULE); m_roundingPower.second |= rule->getPropertiesMask(); - } catch (...) { Debug::log(ERR, "CWindowRuleApplicator::applyDynamicRule: invalid rounding_power {}", effect); } + } catch (...) { Log::logger->log(Log::ERR, "CWindowRuleApplicator::applyDynamicRule: invalid rounding_power {}", effect); } break; } case WINDOW_RULE_EFFECT_PERSISTENT_SIZE: { @@ -148,17 +141,19 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const case WINDOW_RULE_EFFECT_BORDER_COLOR: { try { // Each vector will only get used if it has at least one color - CGradientValueData activeBorderGradient = {}; - CGradientValueData inactiveBorderGradient = {}; - bool active = true; - CVarList colorsAndAngles = CVarList(trim(effect.substr(effect.find_first_of(' ') + 1)), 0, 's', true); + Config::CGradientValueData activeBorderGradient = {}; + Config::CGradientValueData inactiveBorderGradient = {}; + bool active = true; + CVarList colorsAndAngles = CVarList(trim(effect), 0, 's', true); // Basic form has only two colors, everything else can be parsed as a gradient if (colorsAndAngles.size() == 2 && !colorsAndAngles[1].contains("deg")) { m_activeBorderColor.first = - Types::COverridableVar(CGradientValueData(CHyprColor(configStringToInt(colorsAndAngles[0]).value_or(0))), Types::PRIORITY_WINDOW_RULE); + Types::COverridableVar(Config::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); + Types::COverridableVar(Config::CGradientValueData(CHyprColor(configStringToInt(colorsAndAngles[1]).value_or(0))), Types::PRIORITY_WINDOW_RULE); + m_activeBorderColor.second |= rule->getPropertiesMask(); + m_inactiveBorderColor.second |= rule->getPropertiesMask(); break; } @@ -179,16 +174,16 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const // Includes sanity checks for the number of colors in each gradient if (activeBorderGradient.m_colors.size() > 10 || inactiveBorderGradient.m_colors.size() > 10) - Debug::log(WARN, "Bordercolor rule \"{}\" has more than 10 colors in one gradient, ignoring", effect); + Log::logger->log(Log::WARN, "Bordercolor rule \"{}\" has more than 10 colors in one gradient, ignoring", effect); else if (activeBorderGradient.m_colors.empty()) - Debug::log(WARN, "Bordercolor rule \"{}\" has no colors, ignoring", effect); + Log::logger->log(Log::WARN, "Bordercolor rule \"{}\" has no colors, ignoring", effect); else if (inactiveBorderGradient.m_colors.empty()) m_activeBorderColor.first = Types::COverridableVar(activeBorderGradient, Types::PRIORITY_WINDOW_RULE); else { m_activeBorderColor.first = Types::COverridableVar(activeBorderGradient, Types::PRIORITY_WINDOW_RULE); m_inactiveBorderColor.first = Types::COverridableVar(inactiveBorderGradient, Types::PRIORITY_WINDOW_RULE); } - } catch (std::exception& e) { Debug::log(ERR, "BorderColor rule \"{}\" failed with: {}", effect, e.what()); } + } catch (std::exception& e) { Log::logger->log(Log::ERR, "BorderColor rule \"{}\" failed with: {}", effect, e.what()); } m_activeBorderColor.second = rule->getPropertiesMask(); m_inactiveBorderColor.second = rule->getPropertiesMask(); break; @@ -203,7 +198,7 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const else if (effect == "fullscreen") m_idleInhibitMode.first.set(IDLEINHIBIT_FULLSCREEN, Types::PRIORITY_WINDOW_RULE); else - Debug::log(ERR, "Rule idleinhibit: unknown mode {}", effect); + Log::logger->log(Log::ERR, "Rule idleinhibit: unknown mode {}", effect); m_idleInhibitMode.second = rule->getPropertiesMask(); break; } @@ -246,7 +241,7 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const m_alphaInactive.first = m_alpha.first; m_alphaFullscreen.first = m_alpha.first; } - } catch (std::exception& e) { Debug::log(ERR, "Opacity rule \"{}\" failed with: {}", effect, e.what()); } + } catch (std::exception& e) { Log::logger->log(Log::ERR, "Opacity rule \"{}\" failed with: {}", effect, e.what()); } m_alpha.second = rule->getPropertiesMask(); m_alphaInactive.second = rule->getPropertiesMask(); m_alphaFullscreen.second = rule->getPropertiesMask(); @@ -265,19 +260,21 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const if (!m_window) break; - if (!m_window->m_isFloating && !sc(*PCLAMP_TILED)) + const auto VEC = m_window->calculateExpression(effect); + if (!VEC) { + Log::logger->log(Log::ERR, "failed to parse {} as an expression", effect); break; - - const auto VEC = configStringToVector2D(effect); - if (VEC.x < 1 || VEC.y < 1) { - Debug::log(ERR, "Invalid size for maxsize"); + } + 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_window->clampWindowSize(std::nullopt, m_maxSize.first.value()); + m_maxSize.first = Types::COverridableVar(*VEC, Types::PRIORITY_WINDOW_RULE); - } catch (std::exception& e) { Debug::log(ERR, "maxsize rule \"{}\" failed with: {}", effect, e.what()); } + if (*PCLAMP_TILED || m_window->m_isFloating) + m_window->clampWindowSize(std::nullopt, m_maxSize.first.value()); + } catch (std::exception& e) { Log::logger->log(Log::ERR, "maxsize rule \"{}\" failed with: {}", effect, e.what()); } m_maxSize.second = rule->getPropertiesMask(); break; } @@ -288,18 +285,21 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const if (!m_window) break; - if (!m_window->m_isFloating && !sc(*PCLAMP_TILED)) - break; - - const auto VEC = configStringToVector2D(effect); - if (VEC.x < 1 || VEC.y < 1) { - Debug::log(ERR, "Invalid size for maxsize"); + const auto VEC = m_window->calculateExpression(effect); + if (!VEC) { + Log::logger->log(Log::ERR, "failed to parse {} as an expression", effect); break; } - m_minSize.first = Types::COverridableVar(VEC, Types::PRIORITY_WINDOW_RULE); - m_window->clampWindowSize(std::nullopt, m_minSize.first.value()); - } catch (std::exception& e) { Debug::log(ERR, "minsize rule \"{}\" failed with: {}", effect, e.what()); } + 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); + 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()); } m_minSize.second = rule->getPropertiesMask(); break; } @@ -310,7 +310,7 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const m_borderSize.second |= rule->getPropertiesMask(); if (oldBorderSize != m_borderSize.first.valueOrDefault()) result.needsRelayout = true; - } catch (...) { Debug::log(ERR, "CWindowRuleApplicator::applyDynamicRule: invalid border_size {}", effect); } + } catch (...) { Log::logger->log(Log::ERR, "CWindowRuleApplicator::applyDynamicRule: invalid border_size {}", effect); } break; } case WINDOW_RULE_EFFECT_ALLOWS_INPUT: { @@ -432,14 +432,14 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const try { m_scrollMouse.first.set(std::clamp(std::stof(effect), 0.01F, 10.F), Types::PRIORITY_WINDOW_RULE); m_scrollMouse.second |= rule->getPropertiesMask(); - } catch (...) { Debug::log(ERR, "CWindowRuleApplicator::applyDynamicRule: invalid scroll_mouse {}", effect); } + } catch (...) { Log::logger->log(Log::ERR, "CWindowRuleApplicator::applyDynamicRule: invalid scroll_mouse {}", effect); } break; } case WINDOW_RULE_EFFECT_SCROLL_TOUCHPAD: { try { m_scrollTouchpad.first.set(std::clamp(std::stof(effect), 0.01F, 10.F), Types::PRIORITY_WINDOW_RULE); m_scrollTouchpad.second |= rule->getPropertiesMask(); - } catch (...) { Debug::log(ERR, "CWindowRuleApplicator::applyDynamicRule: invalid scroll_touchpad {}", effect); } + } catch (...) { Log::logger->log(Log::ERR, "CWindowRuleApplicator::applyDynamicRule: invalid scroll_touchpad {}", effect); } break; } } @@ -451,7 +451,7 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyStaticRule(const for (const auto& [key, effect] : rule->effects()) { switch (key) { default: { - Debug::log(TRACE, "CWindowRuleApplicator::applyStaticRule: Skipping effect {}, not static", sc>(key)); + Log::logger->log(Log::TRACE, "CWindowRuleApplicator::applyStaticRule: Skipping effect {}, not static", sc>(key)); break; } @@ -477,10 +477,11 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyStaticRule(const static_.fullscreenStateInternal = std::stoi(std::string{vars[0]}); if (!vars[1].empty()) static_.fullscreenStateClient = std::stoi(std::string{vars[1]}); - } catch (...) { Debug::log(ERR, "CWindowRuleApplicator::applyStaticRule: invalid fullscreen state {}", effect); } + } catch (...) { Log::logger->log(Log::ERR, "CWindowRuleApplicator::applyStaticRule: invalid fullscreen state {}", effect); } break; } case WINDOW_RULE_EFFECT_MOVE: { + static_.center = std::nullopt; static_.position = effect; break; } @@ -489,6 +490,7 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyStaticRule(const break; } case WINDOW_RULE_EFFECT_CENTER: { + static_.position.clear(); static_.center = truthy(effect); break; } @@ -530,7 +532,13 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyStaticRule(const case WINDOW_RULE_EFFECT_NOCLOSEFOR: { try { static_.noCloseFor = std::stoi(effect); - } catch (...) { Debug::log(ERR, "CWindowRuleApplicator::applyStaticRule: invalid no close for {}", effect); } + } catch (...) { Log::logger->log(Log::ERR, "CWindowRuleApplicator::applyStaticRule: invalid no close for {}", effect); } + break; + } + case WINDOW_RULE_EFFECT_SCROLLING_WIDTH: { + try { + static_.scrollingWidth = std::stof(effect); + } catch (...) { Log::logger->log(Log::ERR, "CWindowRuleApplicator::applyStaticRule: invalid scrolling width {}", effect); } break; } } @@ -539,9 +547,10 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyStaticRule(const return SRuleResult{}; } -void CWindowRuleApplicator::readStaticRules() { +// +bool CWindowRuleApplicator::readStaticRules(bool preRead) { if (!m_window) - return; + return false; static_ = {}; @@ -567,34 +576,35 @@ void CWindowRuleApplicator::readStaticRules() { tagsWereChanged = tagsWereChanged || RES.tagsChanged; } - // recheck some props people might wanna use for static rules. - std::underlying_type_t propsToRecheck = RULE_PROP_NONE; + // set a recheck for some props people might wanna use for static rules. if (tagsWereChanged) propsToRecheck |= RULE_PROP_TAG; if (static_.content != NContentType::CONTENT_TYPE_NONE) propsToRecheck |= RULE_PROP_CONTENT; - if (propsToRecheck != RULE_PROP_NONE) { - for (const auto& r : ruleEngine()->rules()) { - if (r->type() != RULE_TYPE_WINDOW) - continue; - - if (!(r->getPropertiesMask() & propsToRecheck)) - continue; - - auto wr = reinterpretPointerCast(r); - - if (!wr->matches(m_window.lock(), true)) - continue; - - applyStaticRule(wr); - } - } - for (const auto& wr : execRules) { applyStaticRule(wr); applyDynamicRule(wr); - ruleEngine()->unregisterRule(wr); + if (!preRead) + ruleEngine()->unregisterRule(wr); + } + return (propsToRecheck != RULE_PROP_NONE); +} + +void CWindowRuleApplicator::recheckStaticRules() { + for (const auto& r : ruleEngine()->rules()) { + if (r->type() != RULE_TYPE_WINDOW) + continue; + + if (!(r->getPropertiesMask() & propsToRecheck)) + continue; + + auto wr = reinterpretPointerCast(r); + + if (!wr->matches(m_window.lock(), true)) + continue; + + applyStaticRule(wr); } } @@ -621,11 +631,13 @@ void CWindowRuleApplicator::propertiesChanged(std::underlying_type_tupdateWindowData(); + m_window->updateWindowDecos(); m_window->updateDecorationValues(); if (needsRelayout) g_pDecorationPositioner->forceRecalcFor(m_window.lock()); // for plugins - EMIT_HOOK_EVENT("windowUpdateRules", m_window.lock()); + Event::bus()->m_events.window.updateRules.emit(m_window.lock()); } diff --git a/src/desktop/rule/windowRule/WindowRuleApplicator.hpp b/src/desktop/rule/windowRule/WindowRuleApplicator.hpp index ba80e17b5..4227368f2 100644 --- a/src/desktop/rule/windowRule/WindowRuleApplicator.hpp +++ b/src/desktop/rule/windowRule/WindowRuleApplicator.hpp @@ -9,7 +9,7 @@ #include "../../types/OverridableVar.hpp" #include "../../../helpers/math/Math.hpp" #include "../../../helpers/TagKeeper.hpp" -#include "../../../config/ConfigDataValues.hpp" +#include "../../../config/shared/complex/ComplexDataTypes.hpp" namespace Desktop::Rule { class CWindowRule; @@ -33,28 +33,28 @@ namespace Desktop::Rule { void propertiesChanged(std::underlying_type_t props); std::unordered_set resetProps(std::underlying_type_t props, Types::eOverridePriority prio = Types::PRIORITY_WINDOW_RULE); - void readStaticRules(); - void applyStaticRules(); + bool readStaticRules(bool preRead = false); + void recheckStaticRules(); // static props struct { std::string monitor, workspace, group; std::optional floating; - - bool fullscreen = false; - bool maximize = false; - bool pseudo = false; - bool pin = false; - bool noInitialFocus = false; + std::optional fullscreen; + std::optional maximize; + std::optional pseudo; + std::optional pin; + std::optional noInitialFocus; + std::optional center; std::optional fullscreenStateClient; std::optional fullscreenStateInternal; - std::optional center; std::optional content; std::optional noCloseFor; std::string size, position; + std::optional scrollingWidth; std::vector suppressEvent; } static_; @@ -130,8 +130,8 @@ namespace Desktop::Rule { DEFINE_PROP(Vector2D, maxSize, Vector2D{}, WINDOW_RULE_EFFECT_MAX_SIZE) DEFINE_PROP(Vector2D, minSize, Vector2D{}, WINDOW_RULE_EFFECT_MIN_SIZE) - DEFINE_PROP(CGradientValueData, activeBorderColor, {}, WINDOW_RULE_EFFECT_BORDER_COLOR) - DEFINE_PROP(CGradientValueData, inactiveBorderColor, {}, WINDOW_RULE_EFFECT_BORDER_COLOR) + DEFINE_PROP(Config::CGradientValueData, activeBorderColor, {}, WINDOW_RULE_EFFECT_BORDER_COLOR) + DEFINE_PROP(Config::CGradientValueData, inactiveBorderColor, {}, WINDOW_RULE_EFFECT_BORDER_COLOR) std::vector>> m_dynamicTags; CTagKeeper m_tagKeeper; @@ -140,7 +140,8 @@ namespace Desktop::Rule { #undef DEFINE_PROP private: - PHLWINDOWREF m_window; + PHLWINDOWREF m_window; + std::underlying_type_t propsToRecheck = RULE_PROP_NONE; struct SRuleResult { bool needsRelayout = false; diff --git a/src/desktop/rule/windowRule/WindowRuleEffectContainer.cpp b/src/desktop/rule/windowRule/WindowRuleEffectContainer.cpp index 660bf8716..668672477 100644 --- a/src/desktop/rule/windowRule/WindowRuleEffectContainer.cpp +++ b/src/desktop/rule/windowRule/WindowRuleEffectContainer.cpp @@ -28,6 +28,7 @@ static const std::vector EFFECT_STRINGS = { "suppress_event", // "content", // "no_close_for", // + "scrolling_width", // "rounding", // "rounding_power", // "persistent_size", // @@ -69,7 +70,7 @@ static const std::vector EFFECT_STRINGS = { // This is here so that if we change the rules, we get reminded to update // the strings. -static_assert(WINDOW_RULE_EFFECT_LAST_STATIC == 54); +static_assert(WINDOW_RULE_EFFECT_LAST_STATIC == 55); CWindowRuleEffectContainer::CWindowRuleEffectContainer() : IEffectContainer(std::vector{EFFECT_STRINGS}) { ; diff --git a/src/desktop/rule/windowRule/WindowRuleEffectContainer.hpp b/src/desktop/rule/windowRule/WindowRuleEffectContainer.hpp index 0827d462d..af8611090 100644 --- a/src/desktop/rule/windowRule/WindowRuleEffectContainer.hpp +++ b/src/desktop/rule/windowRule/WindowRuleEffectContainer.hpp @@ -27,6 +27,7 @@ namespace Desktop::Rule { WINDOW_RULE_EFFECT_SUPPRESSEVENT, WINDOW_RULE_EFFECT_CONTENT, WINDOW_RULE_EFFECT_NOCLOSEFOR, + WINDOW_RULE_EFFECT_SCROLLING_WIDTH, // dynamic WINDOW_RULE_EFFECT_ROUNDING, @@ -76,4 +77,4 @@ namespace Desktop::Rule { }; SP windowEffects(); -}; \ No newline at end of file +}; diff --git a/src/desktop/state/FloatState.cpp b/src/desktop/state/FloatState.cpp new file mode 100644 index 000000000..2ec5be3ae --- /dev/null +++ b/src/desktop/state/FloatState.cpp @@ -0,0 +1,30 @@ +#include "FloatState.hpp" + +using namespace Desktop; + +void CFloatStateCache::remember(PHLWINDOW window, const Vector2D& size) { + Log::logger->log(Log::DEBUG, "[floatStateCache] storing floating size {}x{} for window {}::{}", size.x, size.y, window->m_initialClass, window->m_initialTitle); + // true -> use m_initialClass and m_initialTitle + SFloatCacheKey id{window, true}; + m_storedSizes[id] = size; +} + +std::optional CFloatStateCache::get(PHLWINDOW window) { + // At startup, m_initialClass and m_initialTitle are undefined + // and m_class and m_title are just "initial" ones. + // false -> use m_class and m_title + SFloatCacheKey id{window, false}; + Log::logger->log(Log::DEBUG, "[floatStateCache] Hash for window {}::{} = {}", window->m_class, window->m_title, id.hash); + + if (m_storedSizes.contains(id)) { + Log::logger->log(Log::DEBUG, "[floatStateCache] got stored size {}x{} for window {}::{}", m_storedSizes[id].x, m_storedSizes[id].y, window->m_class, window->m_title); + return m_storedSizes[id]; + } + + return std::nullopt; +} + +UP& Desktop::floatState() { + static UP p = makeUnique(); + return p; +} diff --git a/src/desktop/state/FloatState.hpp b/src/desktop/state/FloatState.hpp new file mode 100644 index 000000000..ff1c4e076 --- /dev/null +++ b/src/desktop/state/FloatState.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include + +#include "../../helpers/math/Math.hpp" +#include "../DesktopTypes.hpp" +#include "../view/Window.hpp" + +namespace Desktop { + struct SFloatCacheKey { + size_t hash; + + SFloatCacheKey(PHLWINDOW window, bool initial) { + // Base hash from class/title + size_t baseHash = initial ? (std::hash{}(window->m_initialClass) ^ (std::hash{}(window->m_initialTitle) << 1)) : + (std::hash{}(window->m_class) ^ (std::hash{}(window->m_title) << 1)); + + // Use empty string as default tag value + std::string tagValue = ""; + if (auto xdgTag = window->xdgTag()) + tagValue = xdgTag.value(); + + // Combine hashes + hash = baseHash ^ (std::hash{}(tagValue) << 2); + } + + bool operator==(const SFloatCacheKey& other) const { + return hash == other.hash; + } + }; +} + +namespace std { + template <> + struct hash { + size_t operator()(const Desktop::SFloatCacheKey& id) const { + return id.hash; + } + }; +} + +namespace Desktop { + class CFloatStateCache { + public: + CFloatStateCache() = default; + ~CFloatStateCache() = default; + + void remember(PHLWINDOW window, const Vector2D& size); + std::optional get(PHLWINDOW window); + + private: + std::unordered_map m_storedSizes; + }; + + UP& floatState(); +} diff --git a/src/desktop/state/FocusState.cpp b/src/desktop/state/FocusState.cpp index 8908d3de2..c12987668 100644 --- a/src/desktop/state/FocusState.cpp +++ b/src/desktop/state/FocusState.cpp @@ -1,34 +1,27 @@ #include "FocusState.hpp" -#include "../Window.hpp" +#include "../view/Window.hpp" #include "../../Compositor.hpp" #include "../../protocols/XDGShell.hpp" #include "../../render/Renderer.hpp" -#include "../../managers/LayoutManager.hpp" #include "../../managers/EventManager.hpp" -#include "../../managers/HookSystemManager.hpp" +#include "../../managers/input/InputManager.hpp" +#include "../../managers/SeatManager.hpp" #include "../../xwayland/XSurface.hpp" #include "../../protocols/PointerConstraints.hpp" +#include "managers/animation/DesktopAnimationManager.hpp" +#include "../../layout/LayoutManager.hpp" +#include "../../event/EventBus.hpp" using namespace Desktop; +#define COMMA , + SP Desktop::focusState() { static SP state = makeShared(); return state; } -Desktop::CFocusState::CFocusState() { - m_windowOpen = g_pHookSystem->hookDynamic("openWindowEarly", [this](void* self, SCallbackInfo& info, std::any data) { - auto window = std::any_cast(data); - - addWindowToHistory(window); - }); - - m_windowClose = g_pHookSystem->hookDynamic("closeWindow", [this](void* self, SCallbackInfo& info, std::any data) { - auto window = std::any_cast(data); - - removeWindowFromHistory(window); - }); -} +Desktop::CFocusState::CFocusState() = default; struct SFullscreenWorkspaceFocusResult { PHLWINDOW overrideFocusWindow = nullptr; @@ -44,6 +37,7 @@ static SFullscreenWorkspaceFocusResult onFullscreenWorkspaceFocusWindow(PHLWINDO if (pWindow->m_isFloating) { // if the window is floating, just bring it to the top pWindow->m_createdOverFullscreen = true; + g_pDesktopAnimationManager->setFullscreenFloatingFade(pWindow, 1.f); g_pHyprRenderer->damageWindow(pWindow); return {}; } @@ -67,13 +61,13 @@ static SFullscreenWorkspaceFocusResult onFullscreenWorkspaceFocusWindow(PHLWINDO g_pCompositor->setWindowFullscreenInternal(pWindow, FSMODE); break; - default: Debug::log(ERR, "Invalid misc:on_focus_under_fullscreen mode: {}", *PONFOCUSUNDERFS); break; + default: Log::logger->log(Log::ERR, "Invalid misc:on_focus_under_fullscreen mode: {}", *PONFOCUSUNDERFS); break; } return {}; } -void CFocusState::fullWindowFocus(PHLWINDOW pWindow, SP surface, bool preserveFocusHistory, bool forceFSCycle) { +void CFocusState::fullWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP surface, bool forceFSCycle) { if (pWindow) { if (!pWindow->m_workspace) return; @@ -89,25 +83,25 @@ void CFocusState::fullWindowFocus(PHLWINDOW pWindow, SP surf static auto PMODALPARENTBLOCKING = CConfigValue("general:modal_parent_blocking"); if (*PMODALPARENTBLOCKING && pWindow && pWindow->m_xdgSurface && pWindow->m_xdgSurface->m_toplevel && pWindow->m_xdgSurface->m_toplevel->anyChildModal()) { - Debug::log(LOG, "Refusing focus to window shadowed by modal dialog"); + Log::logger->log(Log::DEBUG, "Refusing focus to window shadowed by modal dialog"); return; } - rawWindowFocus(pWindow, surface, preserveFocusHistory); + rawWindowFocus(pWindow, reason, surface); } -void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP surface, bool preserveFocusHistory) { +void CFocusState::rawWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP surface) { static auto PFOLLOWMOUSE = CConfigValue("input:follow_mouse"); static auto PSPECIALFALLTHROUGH = CConfigValue("input:special_fallthrough"); if (!pWindow || !pWindow->priorityFocus()) { if (g_pSessionLockManager->isSessionLocked()) { - Debug::log(LOG, "Refusing a keyboard focus to a window because of a sessionlock"); + Log::logger->log(Log::DEBUG, "Refusing a keyboard focus to a window because of a sessionlock"); return; } if (!g_pInputManager->m_exclusiveLSes.empty()) { - Debug::log(LOG, "Refusing a keyboard focus to a window because of an exclusive ls"); + Log::logger->log(Log::DEBUG, "Refusing a keyboard focus to a window because of an exclusive ls"); return; } } @@ -115,7 +109,9 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP surfa if (pWindow && pWindow->m_isX11 && pWindow->isX11OverrideRedirect() && !pWindow->m_xwaylandSurface->wantsFocus()) return; - g_pLayoutManager->getCurrentLayout()->bringWindowToTop(pWindow); + // m_target on purpose, this avoids the group + if (pWindow) + g_layoutManager->bringTargetToTop(pWindow->m_target); if (!pWindow || !validMapped(pWindow)) { @@ -137,9 +133,7 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP surfa g_pEventManager->postEvent(SHyprIPCEvent{"activewindow", ","}); g_pEventManager->postEvent(SHyprIPCEvent{"activewindowv2", ""}); - EMIT_HOOK_EVENT("activeWindow", PHLWINDOW{nullptr}); - - g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(nullptr); + Event::bus()->m_events.window.active.emit(nullptr, reason); m_focusSurface.reset(); @@ -148,7 +142,7 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP surfa } if (pWindow->m_ruleApplicator->noFocus().valueOrDefault()) { - Debug::log(LOG, "Ignoring focus to nofocus window!"); + Log::logger->log(Log::DEBUG, "Ignoring focus to nofocus window!"); return; } @@ -164,8 +158,6 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP surfa const auto PWORKSPACE = pWindow->m_workspace; // This is to fix incorrect feedback on the focus history. PWORKSPACE->m_lastFocusedWindow = pWindow; - if (m_focusMonitor->m_activeWorkspace) - PWORKSPACE->rememberPrevWorkspace(m_focusMonitor->m_activeWorkspace); if (PWORKSPACE->m_isSpecialWorkspace) m_focusMonitor->changeWorkspace(PWORKSPACE, false, true); // if special ws, open on current monitor else if (PMONITOR) @@ -191,7 +183,7 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP surfa g_pXWaylandManager->activateWindow(PLASTWINDOW, false); } - const auto PWINDOWSURFACE = surface ? surface : pWindow->m_wlSurface->resource(); + const auto PWINDOWSURFACE = surface ? surface : pWindow->wlSurface()->resource(); rawSurfaceFocus(PWINDOWSURFACE, pWindow); @@ -208,33 +200,26 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP surfa g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = pWindow->m_class + "," + pWindow->m_title}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = std::format("{:x}", rc(pWindow.get()))}); - EMIT_HOOK_EVENT("activeWindow", pWindow); - - g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(pWindow); + Event::bus()->m_events.window.active.emit(pWindow, reason); g_pInputManager->recheckIdleInhibitorStatus(); - if (!preserveFocusHistory) { - // move to front of the window history - moveWindowToLatestInHistory(pWindow); - } - if (*PFOLLOWMOUSE == 0) g_pInputManager->sendMotionEventsToFocused(); - if (pWindow->m_groupData.pNextWindow) + if (pWindow->m_group) pWindow->deactivateGroupMembers(); } void CFocusState::rawSurfaceFocus(SP pSurface, PHLWINDOW pWindowOwner) { - if (g_pSeatManager->m_state.keyboardFocus == pSurface || (pWindowOwner && g_pSeatManager->m_state.keyboardFocus == pWindowOwner->m_wlSurface->resource())) + if (g_pSeatManager->m_state.keyboardFocus == pSurface || (pWindowOwner && g_pSeatManager->m_state.keyboardFocus == pWindowOwner->wlSurface()->resource())) return; // Don't focus when already focused on this. if (g_pSessionLockManager->isSessionLocked() && pSurface && !g_pSessionLockManager->isSurfaceSessionLock(pSurface)) return; if (g_pSeatManager->m_seatGrab && !g_pSeatManager->m_seatGrab->accepts(pSurface)) { - Debug::log(LOG, "surface {:x} won't receive kb focus because grab rejected it", rc(pSurface.get())); + Log::logger->log(Log::DEBUG, "surface {:x} won't receive kb focus because grab rejected it", rc(pSurface.get())); return; } @@ -248,7 +233,7 @@ void CFocusState::rawSurfaceFocus(SP pSurface, PHLWINDOW pWi g_pSeatManager->setKeyboardFocus(nullptr); g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = ","}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = ""}); - EMIT_HOOK_EVENT("keyboardFocus", SP{nullptr}); + Event::bus()->m_events.input.keyboard.focus.emit(nullptr); m_focusSurface.reset(); return; } @@ -257,17 +242,17 @@ void CFocusState::rawSurfaceFocus(SP pSurface, PHLWINDOW pWi g_pSeatManager->setKeyboardFocus(pSurface); if (pWindowOwner) - Debug::log(LOG, "Set keyboard focus to surface {:x}, with {}", rc(pSurface.get()), pWindowOwner); + Log::logger->log(Log::DEBUG, "Set keyboard focus to surface {:x}, with {}", rc(pSurface.get()), pWindowOwner); else - Debug::log(LOG, "Set keyboard focus to surface {:x}", rc(pSurface.get())); + Log::logger->log(Log::DEBUG, "Set keyboard focus to surface {:x}", rc(pSurface.get())); g_pXWaylandManager->activateSurface(pSurface, true); m_focusSurface = pSurface; - EMIT_HOOK_EVENT("keyboardFocus", pSurface); + Event::bus()->m_events.input.keyboard.focus.emit(pSurface); - const auto SURF = CWLSurface::fromResource(pSurface); - const auto OLDSURF = CWLSurface::fromResource(PLASTSURF); + const auto SURF = Desktop::View::CWLSurface::fromResource(pSurface); + const auto OLDSURF = Desktop::View::CWLSurface::fromResource(PLASTSURF); if (OLDSURF && OLDSURF->constraint()) OLDSURF->constraint()->deactivate(); @@ -293,7 +278,7 @@ void CFocusState::rawMonitorFocus(PHLMONITOR pMonitor) { g_pEventManager->postEvent(SHyprIPCEvent{.event = "focusedmon", .data = pMonitor->m_name + "," + WORKSPACE_NAME}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "focusedmonv2", .data = pMonitor->m_name + "," + WORKSPACE_ID}); - EMIT_HOOK_EVENT("focusedMon", pMonitor); + Event::bus()->m_events.monitor.focused.emit(pMonitor); m_focusMonitor = pMonitor; } @@ -309,22 +294,11 @@ PHLMONITOR CFocusState::monitor() { return m_focusMonitor.lock(); } -const std::vector& CFocusState::windowHistory() { - return m_windowFocusHistory; +void CFocusState::resetWindowFocus() { + m_focusWindow.reset(); + m_focusSurface.reset(); } -void CFocusState::removeWindowFromHistory(PHLWINDOW w) { - std::erase_if(m_windowFocusHistory, [&w](const auto& e) { return !e || e == w; }); -} - -void CFocusState::addWindowToHistory(PHLWINDOW w) { - m_windowFocusHistory.emplace_back(w); -} - -void CFocusState::moveWindowToLatestInHistory(PHLWINDOW w) { - const auto HISTORYPIVOT = std::ranges::find_if(m_windowFocusHistory, [&w](const auto& other) { return other.lock() == w; }); - if (HISTORYPIVOT == m_windowFocusHistory.end()) - Debug::log(TRACE, "CFocusState: {} has no pivot in history, ignoring request to move to latest", w); - else - std::rotate(m_windowFocusHistory.begin(), HISTORYPIVOT, HISTORYPIVOT + 1); +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 2bf0953d0..71330a3eb 100644 --- a/src/desktop/state/FocusState.hpp +++ b/src/desktop/state/FocusState.hpp @@ -1,11 +1,24 @@ #pragma once #include "../DesktopTypes.hpp" -#include "../../SharedDefs.hpp" +#include "../../helpers/signal/Signal.hpp" class CWLSurfaceResource; namespace Desktop { + enum eFocusReason : uint8_t { + FOCUS_REASON_UNKNOWN = 0, + FOCUS_REASON_FFM, + FOCUS_REASON_KEYBIND, + FOCUS_REASON_CLICK, + FOCUS_REASON_OTHER, + FOCUS_REASON_DESKTOP_STATE_CHANGE, + FOCUS_REASON_NEW_WINDOW, + FOCUS_REASON_GHOSTS, + }; + + bool isHardInputFocusReason(eFocusReason r); + class CFocusState { public: CFocusState(); @@ -15,28 +28,23 @@ namespace Desktop { CFocusState(CFocusState&) = delete; CFocusState(const CFocusState&) = delete; - void fullWindowFocus(PHLWINDOW w, SP surface = nullptr, bool preserveFocusHistory = false, bool forceFSCycle = false); - void rawWindowFocus(PHLWINDOW w, SP surface = nullptr, bool preserveFocusHistory = false); - void rawSurfaceFocus(SP s, PHLWINDOW pWindowOwner = nullptr); - void rawMonitorFocus(PHLMONITOR m); + void fullWindowFocus(PHLWINDOW w, eFocusReason reason, SP surface = nullptr, bool forceFSCycle = false); + void rawWindowFocus(PHLWINDOW w, eFocusReason reason, SP surface = nullptr); + void rawSurfaceFocus(SP s, PHLWINDOW pWindowOwner = nullptr); + void rawMonitorFocus(PHLMONITOR m); - SP surface(); - PHLWINDOW window(); - PHLMONITOR monitor(); - const std::vector& windowHistory(); + void resetWindowFocus(); - void addWindowToHistory(PHLWINDOW w); + SP surface(); + PHLWINDOW window(); + PHLMONITOR monitor(); private: - void removeWindowFromHistory(PHLWINDOW w); - void moveWindowToLatestInHistory(PHLWINDOW w); + WP m_focusSurface; + PHLWINDOWREF m_focusWindow; + PHLMONITORREF m_focusMonitor; - WP m_focusSurface; - PHLWINDOWREF m_focusWindow; - PHLMONITORREF m_focusMonitor; - std::vector m_windowFocusHistory; // first element is the most recently focused - - SP m_windowOpen, m_windowClose; + CHyprSignalListener m_windowOpen, m_windowClose; }; SP focusState(); diff --git a/src/desktop/types/OverridableVar.hpp b/src/desktop/types/OverridableVar.hpp index 538346c74..065da167a 100644 --- a/src/desktop/types/OverridableVar.hpp +++ b/src/desktop/types/OverridableVar.hpp @@ -61,8 +61,8 @@ namespace Desktop::Types { for (size_t i = 0; i < PRIORITY_END; ++i) { if constexpr (Extended && !std::is_same_v) - m_values[i] = clampOptional(*other.m_values[i], m_minValue, m_maxValue); - else + m_values[i] = other.m_values[i].has_value() ? clampOptional(*other.m_values[i], m_minValue, m_maxValue) : other.m_values[i]; + else if (other.m_values[i].has_value()) m_values[i] = other.m_values[i]; } diff --git a/src/desktop/view/GlobalViewMethods.cpp b/src/desktop/view/GlobalViewMethods.cpp new file mode 100644 index 000000000..83513e816 --- /dev/null +++ b/src/desktop/view/GlobalViewMethods.cpp @@ -0,0 +1,82 @@ +#include "GlobalViewMethods.hpp" +#include "../../Compositor.hpp" + +#include "LayerSurface.hpp" +#include "Window.hpp" +#include "Popup.hpp" +#include "Subsurface.hpp" +#include "SessionLock.hpp" + +#include "../../protocols/core/Compositor.hpp" +#include "../../protocols/core/Subcompositor.hpp" +#include "../../protocols/SessionLock.hpp" + +using namespace Desktop; +using namespace Desktop::View; + +std::vector> View::getViewsForWorkspace(PHLWORKSPACE ws) { + std::vector> views; + + for (const auto& w : g_pCompositor->m_windows) { + if (!w->aliveAndVisible() || w->m_workspace != ws) + continue; + + views.emplace_back(w); + + w->wlSurface()->resource()->breadthfirst( + [&views](SP s, const Vector2D& pos, void* data) { + auto surf = CWLSurface::fromResource(s); + if (!surf || !s->m_mapped) + return; + + views.emplace_back(surf->view()); + }, + nullptr); + + // xwl windows dont have this + if (w->m_popupHead) { + w->m_popupHead->breadthfirst( + [&views](SP s, void* data) { + auto surf = s->wlSurface(); + if (!surf || !s->aliveAndVisible()) + return; + + views.emplace_back(surf->view()); + }, + nullptr); + } + } + + for (const auto& l : g_pCompositor->m_layers) { + if (!l->aliveAndVisible() || l->m_monitor != ws->m_monitor) + continue; + + views.emplace_back(l); + + l->m_popupHead->breadthfirst( + [&views](SP p, void* data) { + auto surf = p->wlSurface(); + if (!surf || !p->aliveAndVisible()) + return; + + views.emplace_back(surf->view()); + }, + nullptr); + } + + for (const auto& v : g_pCompositor->m_otherViews) { + if (!v->aliveAndVisible() || !v->desktopComponent()) + continue; + + if (v->type() == VIEW_TYPE_LOCK_SCREEN) { + const auto LOCK = Desktop::View::CSessionLock::fromView(v); + if (LOCK->monitor() != ws->m_monitor) + continue; + + views.emplace_back(LOCK); + continue; + } + } + + return views; +} diff --git a/src/desktop/view/GlobalViewMethods.hpp b/src/desktop/view/GlobalViewMethods.hpp new file mode 100644 index 000000000..551a42da7 --- /dev/null +++ b/src/desktop/view/GlobalViewMethods.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "View.hpp" + +#include "../Workspace.hpp" + +#include + +namespace Desktop::View { + std::vector> getViewsForWorkspace(PHLWORKSPACE ws); +}; \ No newline at end of file diff --git a/src/desktop/view/Group.cpp b/src/desktop/view/Group.cpp new file mode 100644 index 000000000..287276b22 --- /dev/null +++ b/src/desktop/view/Group.cpp @@ -0,0 +1,359 @@ +#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(); + m_groupPolicyFlags |= w->m_groupRules; + } + + 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(); + m_groupPolicyFlags |= w->m_groupRules; + 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_groupPolicyFlags & GROUP_LOCK; +} + +void CGroup::setLocked(bool x) { + if (x) + m_groupPolicyFlags |= GROUP_LOCK; + else + m_groupPolicyFlags &= ~GROUP_LOCK; +} + +bool CGroup::denied() const { + return m_groupPolicyFlags & GROUP_DENY; +} + +void CGroup::setDenied(bool x) { + if (x) + m_groupPolicyFlags |= GROUP_DENY; + else + m_groupPolicyFlags &= ~GROUP_DENY; +} + +void CGroup::updateWorkspace(PHLWORKSPACE ws) { + if (!ws) + return; + + for (const auto& w : windows()) { + w->m_monitor = ws->m_monitor; + w->moveToWorkspace(ws); + w->updateToplevel(); + w->updateWindowDecos(); + w->m_target->setSpaceGhost(ws->m_space); + } +} + +void CGroup::swapWithNext() { + const bool HAD_FOCUS = Desktop::focusState()->window() == m_windows.at(m_current); + + size_t idx = m_current + 1 >= m_windows.size() ? 0 : m_current + 1; + std::iter_swap(m_windows.begin() + m_current, m_windows.begin() + idx); + m_current = idx; + + updateWindowVisibility(); + + if (HAD_FOCUS) + Desktop::focusState()->fullWindowFocus(m_windows.at(m_current).lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE); +} + +void CGroup::swapWithLast() { + const bool HAD_FOCUS = Desktop::focusState()->window() == m_windows.at(m_current); + + size_t idx = m_current == 0 ? m_windows.size() - 1 : m_current - 1; + std::iter_swap(m_windows.begin() + m_current, m_windows.begin() + idx); + m_current = idx; + + updateWindowVisibility(); + + if (HAD_FOCUS) + Desktop::focusState()->fullWindowFocus(m_windows.at(m_current).lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE); +} diff --git a/src/desktop/view/Group.hpp b/src/desktop/view/Group.hpp new file mode 100644 index 000000000..048c5023b --- /dev/null +++ b/src/desktop/view/Group.hpp @@ -0,0 +1,68 @@ +#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; + + size_t m_current = 0; + + uint32_t m_groupPolicyFlags = 0; + }; + + std::vector>& groups(); +}; diff --git a/src/desktop/LayerSurface.cpp b/src/desktop/view/LayerSurface.cpp similarity index 67% rename from src/desktop/LayerSurface.cpp rename to src/desktop/view/LayerSurface.cpp index 4f08bff6c..ec7c61f27 100644 --- a/src/desktop/LayerSurface.cpp +++ b/src/desktop/view/LayerSurface.cpp @@ -1,59 +1,67 @@ #include "LayerSurface.hpp" -#include "state/FocusState.hpp" -#include "../Compositor.hpp" -#include "../events/Events.hpp" -#include "../protocols/LayerShell.hpp" -#include "../protocols/core/Compositor.hpp" -#include "../managers/SeatManager.hpp" -#include "../managers/animation/AnimationManager.hpp" -#include "../managers/animation/DesktopAnimationManager.hpp" -#include "../render/Renderer.hpp" -#include "../config/ConfigManager.hpp" -#include "../helpers/Monitor.hpp" -#include "../managers/input/InputManager.hpp" -#include "../managers/HookSystemManager.hpp" -#include "../managers/EventManager.hpp" +#include "../state/FocusState.hpp" +#include "../../Compositor.hpp" +#include "../../protocols/LayerShell.hpp" +#include "../../protocols/core/Compositor.hpp" +#include "../../managers/SeatManager.hpp" +#include "../../managers/animation/AnimationManager.hpp" +#include "../../managers/animation/DesktopAnimationManager.hpp" +#include "../../render/Renderer.hpp" +#include "../../config/shared/animation/AnimationTree.hpp" +#include "../../helpers/Monitor.hpp" +#include "../../managers/input/InputManager.hpp" +#include "../../managers/EventManager.hpp" +#include "../../event/EventBus.hpp" + +using namespace Desktop; +using namespace Desktop::View; PHLLS CLayerSurface::create(SP resource) { PHLLS pLS = SP(new CLayerSurface(resource)); auto pMonitor = resource->m_monitor.empty() ? Desktop::focusState()->monitor() : g_pCompositor->getMonitorFromName(resource->m_monitor); - pLS->m_surface->assign(resource->m_surface.lock(), pLS); + pLS->m_wlSurface->assign(resource->m_surface.lock(), 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, Config::animationTree()->getAnimationPropertyConfig("fadeLayersIn"), pLS, AVARDAMAGE_ENTIRE); + g_pAnimationManager->createAnimation(Vector2D(0, 0), pLS->m_realPosition, Config::animationTree()->getAnimationPropertyConfig("layersIn"), pLS, AVARDAMAGE_ENTIRE); + g_pAnimationManager->createAnimation(Vector2D(0, 0), pLS->m_realSize, Config::animationTree()->getAnimationPropertyConfig("layersIn"), pLS, AVARDAMAGE_ENTIRE); + + pLS->registerCallbacks(); + + pLS->m_alpha->setValueAndWarp(0.f); if (!pMonitor) { - Debug::log(ERR, "New LS has no monitor??"); + 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_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; + pLS->m_monitor = pMonitor; pMonitor->m_layerSurfaceLayers[resource->m_current.layer].emplace_back(pLS); - pLS->m_ruleApplicator = makeUnique(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); - g_pAnimationManager->createAnimation(Vector2D(0, 0), pLS->m_realSize, g_pConfigManager->getAnimationPropertyConfig("layersIn"), pLS, AVARDAMAGE_ENTIRE); - - pLS->registerCallbacks(); - - pLS->m_alpha->setValueAndWarp(0.f); - - Debug::log(LOG, "LayerSurface {:x} (namespace {} layer {}) created on monitor {}", rc(resource.get()), resource->m_layerNamespace, sc(pLS->m_layer), - pMonitor->m_name); + 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); return pLS; } +PHLLS CLayerSurface::fromView(SP v) { + if (!v || v->type() != VIEW_TYPE_LAYER_SURFACE) + return nullptr; + return dynamicPointerCast(v); +} + void CLayerSurface::registerCallbacks() { m_alpha->setUpdateCallback([this](auto) { if (m_ruleApplicator->dimAround().valueOrDefault() && m_monitor) @@ -61,23 +69,16 @@ void CLayerSurface::registerCallbacks() { }); } -CLayerSurface::CLayerSurface(SP resource_) : m_layerSurface(resource_) { +CLayerSurface::CLayerSurface(SP resource_) : IView(CWLSurface::create()), m_layerSurface(resource_) { m_listeners.commit = m_layerSurface->m_events.commit.listen([this] { onCommit(); }); m_listeners.map = m_layerSurface->m_events.map.listen([this] { onMap(); }); m_listeners.unmap = m_layerSurface->m_events.unmap.listen([this] { onUnmap(); }); m_listeners.destroy = m_layerSurface->m_events.destroy.listen([this] { onDestroy(); }); - - m_surface = CWLSurface::create(); } CLayerSurface::~CLayerSurface() { - if (!g_pHyprOpenGL) - return; - - if (m_surface) - m_surface->unassign(); - g_pHyprRenderer->makeEGLCurrent(); - std::erase_if(g_pHyprOpenGL->m_layerFramebuffers, [&](const auto& other) { return other.first.expired() || other.first.lock() == m_self.lock(); }); + if (m_wlSurface) + m_wlSurface->unassign(); for (auto const& mon : g_pCompositor->m_realMonitors) { for (auto& lsl : mon->m_layerSurfaceLayers) { @@ -86,20 +87,43 @@ CLayerSurface::~CLayerSurface() { } } +eViewType CLayerSurface::type() const { + return VIEW_TYPE_LAYER_SURFACE; +} + +bool CLayerSurface::visible() const { + return (m_mapped && m_layerSurface && m_layerSurface->m_mapped && m_wlSurface && m_wlSurface->resource()) || (m_fadingOut && m_alpha->value() > 0.F); +} + +std::optional CLayerSurface::logicalBox() const { + return surfaceLogicalBox(); +} + +std::optional CLayerSurface::surfaceLogicalBox() const { + if (!visible()) + return std::nullopt; + + return CBox{m_realPosition->value(), m_realSize->value()}; +} + +bool CLayerSurface::desktopComponent() const { + return true; +} + void CLayerSurface::onDestroy() { - Debug::log(LOG, "LayerSurface {:x} destroyed", rc(m_layerSurface.get())); + Log::logger->log(Log::DEBUG, "LayerSurface {:x} destroyed", rc(m_layerSurface.get())); const auto PMONITOR = m_monitor.lock(); if (!PMONITOR) - Debug::log(WARN, "Layersurface destroyed on an invalid monitor (removed?)"); + Log::logger->log(Log::WARN, "Layersurface destroyed on an invalid monitor (removed?)"); if (!m_fadingOut) { if (m_mapped) { - Debug::log(LOG, "Forcing an unmap of a LS that did a straight destroy!"); + Log::logger->log(Log::DEBUG, "Forcing an unmap of a LS that did a straight destroy!"); onUnmap(); } else { - Debug::log(LOG, "Removing LayerSurface that wasn't mapped."); + Log::logger->log(Log::DEBUG, "Removing LayerSurface that wasn't mapped."); if (m_alpha) g_pDesktopAnimationManager->startAnimation(m_self.lock(), CDesktopAnimationManager::ANIMATION_TYPE_OUT); m_fadingOut = true; @@ -123,8 +147,8 @@ void CLayerSurface::onDestroy() { m_readyToDelete = true; m_layerSurface.reset(); - if (m_surface) - m_surface->unassign(); + if (m_wlSurface) + m_wlSurface->unassign(); m_listeners.unmap.reset(); m_listeners.destroy.reset(); @@ -133,10 +157,11 @@ void CLayerSurface::onDestroy() { } void CLayerSurface::onMap() { - Debug::log(LOG, "LayerSurface {:x} mapped", rc(m_layerSurface.get())); + Log::logger->log(Log::DEBUG, "LayerSurface {:x} mapped", rc(m_layerSurface.get())); - m_mapped = true; - m_interactivity = m_layerSurface->m_current.interactivity; + m_mapped = true; + m_interactivity = m_layerSurface->m_current.interactivity; + m_aboveFullscreen = true; m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ALL); @@ -156,7 +181,7 @@ void CLayerSurface::onMap() { g_pHyprRenderer->arrangeLayersForMonitor(PMONITOR->m_id); - m_surface->resource()->enter(PMONITOR->m_self.lock()); + m_wlSurface->resource()->enter(PMONITOR->m_self.lock()); const bool ISEXCLUSIVE = m_layerSurface->m_current.interactivity == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE; @@ -170,14 +195,14 @@ void CLayerSurface::onMap() { if (GRABSFOCUS) { // TODO: use the new superb really very cool grab - if (g_pSeatManager->m_seatGrab && !g_pSeatManager->m_seatGrab->accepts(m_surface->resource())) + if (g_pSeatManager->m_seatGrab && !g_pSeatManager->m_seatGrab->accepts(m_wlSurface->resource())) g_pSeatManager->setGrab(nullptr); g_pInputManager->releaseAllMouseButtons(); - Desktop::focusState()->rawSurfaceFocus(m_surface->resource()); + Desktop::focusState()->rawSurfaceFocus(m_wlSurface->resource()); const auto LOCAL = g_pInputManager->getMouseCoordsInternal() - Vector2D(m_geometry.x + PMONITOR->m_position.x, m_geometry.y + PMONITOR->m_position.y); - g_pSeatManager->setPointerFocus(m_surface->resource(), LOCAL); + g_pSeatManager->setPointerFocus(m_wlSurface->resource(), LOCAL); g_pInputManager->m_emptyFocusCursorSet = false; } @@ -192,22 +217,22 @@ void CLayerSurface::onMap() { m_fadingOut = false; g_pEventManager->postEvent(SHyprIPCEvent{.event = "openlayer", .data = m_namespace}); - EMIT_HOOK_EVENT("openLayer", m_self.lock()); + Event::bus()->m_events.layer.opened.emit(m_self.lock()); - g_pCompositor->setPreferredScaleForSurface(m_surface->resource(), PMONITOR->m_scale); - g_pCompositor->setPreferredTransformForSurface(m_surface->resource(), PMONITOR->m_transform); + g_pCompositor->setPreferredScaleForSurface(m_wlSurface->resource(), PMONITOR->m_scale); + g_pCompositor->setPreferredTransformForSurface(m_wlSurface->resource(), PMONITOR->m_transform); } void CLayerSurface::onUnmap() { - Debug::log(LOG, "LayerSurface {:x} unmapped", rc(m_layerSurface.get())); + Log::logger->log(Log::DEBUG, "LayerSurface {:x} unmapped", rc(m_layerSurface.get())); g_pEventManager->postEvent(SHyprIPCEvent{.event = "closelayer", .data = m_layerSurface->m_layerNamespace}); - EMIT_HOOK_EVENT("closeLayer", m_self.lock()); + Event::bus()->m_events.layer.closed.emit(m_self.lock()); std::erase_if(g_pInputManager->m_exclusiveLSes, [this](const auto& other) { return !other || other == m_self; }); if (!m_monitor || g_pCompositor->m_unsafeState) { - Debug::log(WARN, "Layersurface unmapping on invalid monitor (removed?) ignoring."); + Log::logger->log(Log::WARN, "Layersurface unmapping on invalid monitor (removed?) ignoring."); g_pCompositor->addToFadingOutSafe(m_self.lock()); @@ -238,7 +263,7 @@ void CLayerSurface::onUnmap() { const auto PMONITOR = m_monitor.lock(); - const bool WASLASTFOCUS = g_pSeatManager->m_state.keyboardFocus == m_surface->resource() || g_pSeatManager->m_state.pointerFocus == m_surface->resource(); + const bool WASLASTFOCUS = g_pSeatManager->m_state.keyboardFocus == m_wlSurface->resource() || g_pSeatManager->m_state.pointerFocus == m_wlSurface->resource(); if (!PMONITOR) return; @@ -249,7 +274,7 @@ void CLayerSurface::onUnmap() { (Desktop::focusState()->surface() && Desktop::focusState()->surface()->m_hlSurface && !Desktop::focusState()->surface()->m_hlSurface->keyboardFocusable())) { if (!g_pInputManager->refocusLastWindow(PMONITOR)) g_pInputManager->refocus(); - } else if (Desktop::focusState()->surface() && Desktop::focusState()->surface() != m_surface->resource()) + } else if (Desktop::focusState()->surface() && Desktop::focusState()->surface() != m_wlSurface->resource()) g_pSeatManager->setKeyboardFocus(Desktop::focusState()->surface()); CBox geomFixed = {m_geometry.x + PMONITOR->m_position.x, m_geometry.y + PMONITOR->m_position.y, m_geometry.width, m_geometry.height}; @@ -285,7 +310,7 @@ void CLayerSurface::onCommit() { return; if (m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND || m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM) - g_pHyprOpenGL->markBlurDirtyForMonitor(PMONITOR); // so that blur is recalc'd + PMONITOR->m_blurFBDirty = true; CBox geomFixed = {m_geometry.x, m_geometry.y, m_geometry.width, m_geometry.height}; g_pHyprRenderer->damageBox(geomFixed); @@ -293,34 +318,27 @@ void CLayerSurface::onCommit() { if (m_layerSurface->m_current.committed != 0) { if (m_layerSurface->m_current.committed & CLayerShellResource::eCommittedState::STATE_LAYER && m_layerSurface->m_current.layer != m_layer) { + const auto NEW_LAYER = std::clamp(m_layerSurface->m_current.layer, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY); + for (auto it = PMONITOR->m_layerSurfaceLayers[m_layer].begin(); it != PMONITOR->m_layerSurfaceLayers[m_layer].end(); it++) { if (*it == m_self) { - PMONITOR->m_layerSurfaceLayers[m_layerSurface->m_current.layer].emplace_back(*it); + PMONITOR->m_layerSurfaceLayers[NEW_LAYER].emplace_back(*it); PMONITOR->m_layerSurfaceLayers[m_layer].erase(it); break; } } - // update alpha when window is in fullscreen - auto PWORKSPACE = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace; - if (PWORKSPACE && PWORKSPACE->m_fullscreenMode == FSMODE_FULLSCREEN) { - // warp if switching render layer so we don't see glitches and have clean fade - if ((m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND || m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM) && - (m_layerSurface->m_current.layer == ZWLR_LAYER_SHELL_V1_LAYER_TOP || m_layerSurface->m_current.layer == ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY)) - m_alpha->setValueAndWarp(0.f); + m_layer = NEW_LAYER; + m_aboveFullscreen = NEW_LAYER >= ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; - // from overlay to top - if (m_layer == ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY && m_layerSurface->m_current.layer == ZWLR_LAYER_SHELL_V1_LAYER_TOP) - *m_alpha = 0.f; - // to overlay - if (m_layerSurface->m_current.layer == ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY) - *m_alpha = 1.f; - } - - m_layer = m_layerSurface->m_current.layer; + // 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; if (m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND || m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM) - g_pHyprOpenGL->markBlurDirtyForMonitor(PMONITOR); // so that blur is recalc'd + PMONITOR->m_blurFBDirty = true; // so that blur is recalc'd + + if (g_pSeatManager->m_state.pointerFocus == m_wlSurface->resource()) + g_pInputManager->simulateMouseMovement(); } g_pHyprRenderer->arrangeLayersForMonitor(PMONITOR->m_id); @@ -359,8 +377,8 @@ void CLayerSurface::onCommit() { nullptr); if (!WASLASTFOCUS && m_popupHead) { m_popupHead->breadthfirst( - [&WASLASTFOCUS](WP popup, void* data) { - WASLASTFOCUS = WASLASTFOCUS || (popup->m_wlSurface && g_pSeatManager->m_state.keyboardFocus == popup->m_wlSurface->resource()); + [&WASLASTFOCUS](WP popup, void* data) { + WASLASTFOCUS = WASLASTFOCUS || (popup->wlSurface() && g_pSeatManager->m_state.keyboardFocus == popup->wlSurface()->resource()); }, nullptr); } @@ -370,7 +388,7 @@ void CLayerSurface::onCommit() { if (!WASEXCLUSIVE && ISEXCLUSIVE) g_pInputManager->m_exclusiveLSes.push_back(m_self); else if (WASEXCLUSIVE && !ISEXCLUSIVE) - std::erase_if(g_pInputManager->m_exclusiveLSes, [this](const auto& other) { return !other.lock() || other.lock() == m_self.lock(); }); + std::erase_if(g_pInputManager->m_exclusiveLSes, [this](const auto& other) { return !other || other == m_self; }); // if the surface was focused and interactive but now isn't, refocus if (WASLASTFOCUS && m_layerSurface->m_current.interactivity == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE) { @@ -384,20 +402,20 @@ void CLayerSurface::onCommit() { // if now exclusive and not previously g_pSeatManager->setGrab(nullptr); g_pInputManager->releaseAllMouseButtons(); - Desktop::focusState()->rawSurfaceFocus(m_surface->resource()); + Desktop::focusState()->rawSurfaceFocus(m_wlSurface->resource()); const auto LOCAL = g_pInputManager->getMouseCoordsInternal() - Vector2D(m_geometry.x + PMONITOR->m_position.x, m_geometry.y + PMONITOR->m_position.y); - g_pSeatManager->setPointerFocus(m_surface->resource(), LOCAL); + g_pSeatManager->setPointerFocus(m_wlSurface->resource(), LOCAL); g_pInputManager->m_emptyFocusCursorSet = false; } } m_interactivity = m_layerSurface->m_current.interactivity; - g_pHyprRenderer->damageSurface(m_surface->resource(), m_position.x, m_position.y); + g_pHyprRenderer->damageSurface(m_wlSurface->resource(), m_position.x, m_position.y); - g_pCompositor->setPreferredScaleForSurface(m_surface->resource(), PMONITOR->m_scale); - g_pCompositor->setPreferredTransformForSurface(m_surface->resource(), PMONITOR->m_transform); + g_pCompositor->setPreferredScaleForSurface(m_wlSurface->resource(), PMONITOR->m_scale); + g_pCompositor->setPreferredTransformForSurface(m_wlSurface->resource(), PMONITOR->m_transform); } bool CLayerSurface::isFadedOut() { @@ -408,12 +426,10 @@ bool CLayerSurface::isFadedOut() { } int CLayerSurface::popupsCount() { - if (!m_layerSurface || !m_mapped || m_fadingOut) + if (!m_layerSurface || !m_mapped || m_fadingOut || !m_popupHead) return 0; - int no = -1; // we have one dummy - m_popupHead->breadthfirst([](WP p, void* data) { *sc(data) += 1; }, &no); - return no; + return m_popupHead->popupTreeCount(); } MONITORID CLayerSurface::monitorID() { @@ -424,10 +440,10 @@ pid_t CLayerSurface::getPID() { pid_t PID = -1; if (!m_layerSurface || !m_layerSurface->m_surface || !m_layerSurface->m_surface->getResource() || !m_layerSurface->m_surface->getResource()->resource() || - !m_layerSurface->m_surface->getResource()->resource()->client) + !m_layerSurface->m_surface->getResource()->client()) return -1; - wl_client_get_credentials(m_layerSurface->m_surface->getResource()->resource()->client, &PID, nullptr, nullptr); + wl_client_get_credentials(m_layerSurface->m_surface->getResource()->client(), &PID, nullptr, nullptr); return PID; } diff --git a/src/desktop/view/LayerSurface.hpp b/src/desktop/view/LayerSurface.hpp new file mode 100644 index 000000000..a8cc7acd2 --- /dev/null +++ b/src/desktop/view/LayerSurface.hpp @@ -0,0 +1,109 @@ +#pragma once + +#include +#include "../../defines.hpp" +#include "WLSurface.hpp" +#include "View.hpp" +#include "../rule/layerRule/LayerRuleApplicator.hpp" +#include "../../helpers/AnimatedVariable.hpp" +#include "../../render/Framebuffer.hpp" + +class CLayerShellResource; + +namespace Desktop::View { + + class CLayerSurface : public IView { + public: + static PHLLS create(SP); + static PHLLS fromView(SP); + + private: + CLayerSurface(SP); + + public: + virtual ~CLayerSurface(); + + virtual eViewType type() const; + virtual bool visible() const; + virtual std::optional logicalBox() const; + virtual bool desktopComponent() const; + virtual std::optional surfaceLogicalBox() const; + + bool isFadedOut(); + int popupsCount(); + + PHLANIMVAR m_realPosition; + PHLANIMVAR m_realSize; + PHLANIMVAR m_alpha; + + WP m_layerSurface; + + // the header providing the enum type cannot be imported here + int m_interactivity = 0; + + bool m_mapped = false; + uint32_t m_layer = 0; + + PHLMONITORREF m_monitor; + + bool m_fadingOut = false; + bool m_readyToDelete = false; + bool m_noProcess = false; + bool m_aboveFullscreen = true; + + UP m_ruleApplicator; + + PHLLSREF m_self; + + CBox m_geometry = {0, 0, 0, 0}; + Vector2D m_position; + std::string m_namespace = ""; + SP m_popupHead; + + SP m_snapshotFB; + + pid_t getPID(); + + void onDestroy(); + void onMap(); + void onUnmap(); + void onCommit(); + MONITORID monitorID(); + + private: + struct { + CHyprSignalListener destroy; + CHyprSignalListener map; + CHyprSignalListener unmap; + CHyprSignalListener commit; + } m_listeners; + + void registerCallbacks(); + + // For the list lookup + bool operator==(const CLayerSurface& rhs) const { + return m_layerSurface == rhs.m_layerSurface && m_monitor == rhs.m_monitor; + } + }; + + inline bool valid(PHLLS l) { + return l; + } + + inline bool valid(PHLLSREF l) { + return l; + } + + inline bool validMapped(PHLLS l) { + if (!valid(l)) + return false; + return l->aliveAndVisible(); + } + + inline bool validMapped(PHLLSREF l) { + if (!valid(l)) + return false; + return l->aliveAndVisible(); + } + +} diff --git a/src/desktop/Popup.cpp b/src/desktop/view/Popup.cpp similarity index 51% rename from src/desktop/Popup.cpp rename to src/desktop/view/Popup.cpp index c3794c6ca..7791e9211 100644 --- a/src/desktop/Popup.cpp +++ b/src/desktop/view/Popup.cpp @@ -1,43 +1,46 @@ #include "Popup.hpp" -#include "../config/ConfigValue.hpp" -#include "../config/ConfigManager.hpp" -#include "../Compositor.hpp" -#include "../protocols/LayerShell.hpp" -#include "../protocols/XDGShell.hpp" -#include "../protocols/core/Compositor.hpp" -#include "../managers/SeatManager.hpp" -#include "../managers/animation/AnimationManager.hpp" -#include "../desktop/LayerSurface.hpp" -#include "../managers/input/InputManager.hpp" -#include "../render/Renderer.hpp" -#include "../render/OpenGL.hpp" +#include "../../config/ConfigValue.hpp" +#include "../../config/shared/animation/AnimationTree.hpp" +#include "../../Compositor.hpp" +#include "../../protocols/LayerShell.hpp" +#include "../../protocols/XDGShell.hpp" +#include "../../protocols/core/Compositor.hpp" +#include "../../managers/SeatManager.hpp" +#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 -UP CPopup::create(PHLWINDOW pOwner) { - auto popup = UP(new CPopup()); +using namespace Desktop; +using namespace Desktop::View; + +SP CPopup::create(PHLWINDOW pOwner) { + auto popup = SP(new CPopup()); popup->m_windowOwner = pOwner; popup->m_self = popup; popup->initAllSignals(); return popup; } -UP CPopup::create(PHLLS pOwner) { - auto popup = UP(new CPopup()); +SP CPopup::create(PHLLS pOwner) { + auto popup = SP(new CPopup()); popup->m_layerOwner = pOwner; popup->m_self = popup; popup->initAllSignals(); return popup; } -UP CPopup::create(SP resource, WP pOwner) { - auto popup = UP(new CPopup()); +SP CPopup::create(SP resource, WP pOwner) { + auto popup = SP(new CPopup()); popup->m_resource = resource; popup->m_windowOwner = pOwner->m_windowOwner; popup->m_layerOwner = pOwner->m_layerOwner; popup->m_parent = pOwner; popup->m_self = popup; - popup->m_wlSurface = CWLSurface::create(); - popup->m_wlSurface->assign(resource->m_surface->m_surface.lock(), popup.get()); + popup->wlSurface()->assign(resource->m_surface->m_surface.lock(), popup); popup->m_lastSize = resource->m_surface->m_current.geometry.size(); popup->reposition(); @@ -46,14 +49,59 @@ UP CPopup::create(SP resource, WP pOwner) { return popup; } +SP CPopup::fromView(SP v) { + if (!v || v->type() != VIEW_TYPE_POPUP) + return nullptr; + return dynamicPointerCast(v); +} + +CPopup::CPopup() : IView(CWLSurface::create()) { + ; +} + CPopup::~CPopup() { if (m_wlSurface) m_wlSurface->unassign(); } +eViewType CPopup::type() const { + return VIEW_TYPE_POPUP; +} + +bool CPopup::visible() const { + if ((!m_mapped || !m_wlSurface->resource()) && (!m_fadingOut || m_alpha->value() > 0.F)) + return false; + + if (!m_windowOwner.expired()) + return g_pHyprRenderer->shouldRenderWindow(m_windowOwner.lock()); + + if (!m_layerOwner.expired()) + return true; + + if (m_parent) + return m_parent->visible(); + + return false; +} + +std::optional CPopup::logicalBox() const { + return surfaceLogicalBox(); +} + +std::optional CPopup::surfaceLogicalBox() const { + if (!visible()) + return std::nullopt; + + return CBox{coordsGlobal(), size()}; +} + +bool CPopup::desktopComponent() const { + return true; +} + void CPopup::initAllSignals() { - g_pAnimationManager->createAnimation(0.f, m_alpha, g_pConfigManager->getAnimationPropertyConfig("fadePopupsIn"), AVARDAMAGE_NONE); + g_pAnimationManager->createAnimation(0.f, m_alpha, Config::animationTree()->getAnimationPropertyConfig("fadePopupsIn"), AVARDAMAGE_NONE); m_alpha->setUpdateCallback([this](auto) { // g_pHyprRenderer->damageBox(CBox{coordsGlobal(), size()}); @@ -61,8 +109,12 @@ void CPopup::initAllSignals() { m_alpha->setCallbackOnEnd( [this](auto) { if (inert()) { - g_pHyprRenderer->damageBox(CBox{coordsGlobal(), size()}); - fullyDestroy(); + g_pEventLoopManager->doLater([p = m_self] { + if (!p) + return; + g_pHyprRenderer->damageBox(CBox{p->coordsGlobal(), p->size()}); + p->fullyDestroy(); + }); } }, false); @@ -82,7 +134,7 @@ void CPopup::initAllSignals() { m_listeners.map = m_resource->m_surface->m_events.map.listen([this] { this->onMap(); }); m_listeners.unmap = m_resource->m_surface->m_events.unmap.listen([this] { this->onUnmap(); }); m_listeners.dismissed = m_resource->m_events.dismissed.listen([this] { this->onUnmap(); }); - m_listeners.destroy = m_resource->m_surface->m_events.destroy.listen([this] { this->onDestroy(); }); + m_listeners.destroy = m_resource->m_events.destroy.listen([this] { this->onDestroy(); }); m_listeners.commit = m_resource->m_surface->m_events.commit.listen([this] { this->onCommit(); }); m_listeners.newPopup = m_resource->m_surface->m_events.newPopup.listen([this](const auto& resource) { this->onNewPopup(resource); }); } @@ -90,12 +142,17 @@ void CPopup::initAllSignals() { void CPopup::onNewPopup(SP popup) { const auto& POPUP = m_children.emplace_back(CPopup::create(popup, m_self)); POPUP->m_self = POPUP; - Debug::log(LOG, "New popup at {:x}", rc(this)); + + invalidateTreeExtentsCache(); + + Log::logger->log(Log::DEBUG, "New popup at {:x}", rc(this)); } void CPopup::onDestroy() { m_inert = true; + invalidateTreeExtentsCache(); + if (!m_parent) return; // head node @@ -103,8 +160,13 @@ void CPopup::onDestroy() { m_children.clear(); m_wlSurface.reset(); + m_listeners.map.reset(); + m_listeners.unmap.reset(); + m_listeners.commit.reset(); + m_listeners.newPopup.reset(); + if (m_fadingOut && m_alpha->isBeingAnimated()) { - Debug::log(LOG, "popup {:x}: skipping full destroy, animating", rc(this)); + Log::logger->log(Log::DEBUG, "popup {:x}: skipping full destroy, animating", rc(this)); return; } @@ -112,10 +174,9 @@ void CPopup::onDestroy() { } void CPopup::fullyDestroy() { - Debug::log(LOG, "popup {:x} fully destroying", rc(this)); + Log::logger->log(Log::DEBUG, "popup {:x} fully destroying", rc(this)); - g_pHyprRenderer->makeEGLCurrent(); - std::erase_if(g_pHyprOpenGL->m_popupFramebuffers, [&](const auto& other) { return other.first.expired() || other.first == m_self; }); + invalidateTreeExtentsCache(); std::erase_if(m_parent->m_children, [this](const auto& other) { return other.get() == this; }); } @@ -136,22 +197,26 @@ void CPopup::onMap() { m_lastPos = coordsRelativeToParent(); + invalidateTreeExtentsCache(); + g_pInputManager->simulateMouseMovement(); m_subsurfaceHead = CSubsurface::create(m_self); //unconstrain(); sendScale(); - m_resource->m_surface->m_surface->enter(PMONITOR->m_self.lock()); + m_wlSurface->resource()->breadthfirst([PMONITOR](SP s, const Vector2D& offset, void* d) { s->enter(PMONITOR->m_self.lock()); }, nullptr); - if (!m_layerOwner.expired() && m_layerOwner->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP) - g_pHyprOpenGL->markBlurDirtyForMonitor(g_pCompositor->getMonitorFromID(m_layerOwner->m_layer)); + if (!m_layerOwner.expired() && m_layerOwner->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP) { + if (m_layerOwner->m_monitor) + m_layerOwner->m_monitor->m_blurFBDirty = true; + } - m_alpha->setConfig(g_pConfigManager->getAnimationPropertyConfig("fadePopupsIn")); + m_alpha->setConfig(Config::animationTree()->getAnimationPropertyConfig("fadePopupsIn")); m_alpha->setValueAndWarp(0.F); *m_alpha = 1.F; - Debug::log(LOG, "popup {:x}: mapped", rc(this)); + Log::logger->log(Log::DEBUG, "popup {:x}: mapped", rc(this)); } void CPopup::onUnmap() { @@ -159,12 +224,12 @@ void CPopup::onUnmap() { return; if (!m_resource || !m_resource->m_surface) { - Debug::log(ERR, "CPopup: orphaned (no surface/resource) and unmaps??"); + Log::logger->log(Log::ERR, "CPopup: orphaned (no surface/resource) and unmaps??"); onDestroy(); return; } - Debug::log(LOG, "popup {:x}: unmapped", rc(this)); + Log::logger->log(Log::DEBUG, "popup {:x}: unmapped", rc(this)); // if the popup committed a different size right now, we also need to damage the old size. const Vector2D MAX_DAMAGE_SIZE = {std::max(m_lastSize.x, m_resource->m_surface->m_surface->m_current.size.x), @@ -173,6 +238,8 @@ void CPopup::onUnmap() { m_lastSize = m_resource->m_surface->m_surface->m_current.size; m_lastPos = coordsRelativeToParent(); + invalidateTreeExtentsCache(); + const auto COORDS = coordsGlobal(); CBox box = m_wlSurface->resource()->extends(); @@ -188,7 +255,7 @@ void CPopup::onUnmap() { g_pHyprRenderer->makeSnapshot(m_self); m_fadingOut = true; - m_alpha->setConfig(g_pConfigManager->getAnimationPropertyConfig("fadePopupsOut")); + m_alpha->setConfig(Config::animationTree()->getAnimationPropertyConfig("fadePopupsOut")); m_alpha->setValueAndWarp(1.F); *m_alpha = 0.F; @@ -196,8 +263,10 @@ void CPopup::onUnmap() { m_subsurfaceHead.reset(); - if (!m_layerOwner.expired() && m_layerOwner->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP) - g_pHyprOpenGL->markBlurDirtyForMonitor(g_pCompositor->getMonitorFromID(m_layerOwner->m_layer)); + if (!m_layerOwner.expired() && m_layerOwner->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP) { + if (m_layerOwner->m_monitor) + m_layerOwner->m_monitor->m_blurFBDirty = true; + } // damage all children breadthfirst( @@ -219,7 +288,7 @@ void CPopup::onUnmap() { void CPopup::onCommit(bool ignoreSiblings) { if (!m_resource || !m_resource->m_surface) { - Debug::log(ERR, "CPopup: orphaned (no surface/resource) and commits??"); + Log::logger->log(Log::ERR, "CPopup: orphaned (no surface/resource) and commits??"); onDestroy(); return; } @@ -230,11 +299,15 @@ void CPopup::onCommit(bool ignoreSiblings) { } if (!m_windowOwner.expired() && (!m_windowOwner->m_isMapped || !m_windowOwner->m_workspace->m_visible)) { - m_lastSize = m_resource->m_surface->m_surface->m_current.size; + const auto PREV_SIZE = m_lastSize; + m_lastSize = m_resource->m_surface->m_surface->m_current.size; + + if (PREV_SIZE != m_lastSize) + invalidateTreeExtentsCache(); static auto PLOGDAMAGE = CConfigValue("debug:log_damage"); if (*PLOGDAMAGE) - Debug::log(LOG, "Refusing to commit damage from a subsurface of {} because it's invisible.", m_windowOwner.lock()); + Log::logger->log(Log::DEBUG, "Refusing to commit damage from a subsurface of {} because it's invisible.", m_windowOwner.lock()); return; } @@ -252,6 +325,8 @@ void CPopup::onCommit(bool ignoreSiblings) { g_pHyprRenderer->damageBox(box); m_lastPos = COORDSLOCAL; + + invalidateTreeExtentsCache(); } if (!ignoreSiblings && m_subsurfaceHead) @@ -261,17 +336,21 @@ void CPopup::onCommit(bool ignoreSiblings) { m_requestedReposition = false; - if (!m_layerOwner.expired() && m_layerOwner->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP) - g_pHyprOpenGL->markBlurDirtyForMonitor(g_pCompositor->getMonitorFromID(m_layerOwner->m_layer)); + if (!m_layerOwner.expired() && m_layerOwner->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP) { + if (m_layerOwner->m_monitor) + m_layerOwner->m_monitor->m_blurFBDirty = true; + } } void CPopup::onReposition() { - Debug::log(LOG, "Popup {:x} requests reposition", rc(this)); + Log::logger->log(Log::DEBUG, "Popup {:x} requests reposition", rc(this)); m_requestedReposition = true; m_lastPos = coordsRelativeToParent(); + invalidateTreeExtentsCache(); + reposition(); } @@ -282,18 +361,21 @@ void CPopup::reposition() { if (!PMONITOR) return; - CBox box = {PMONITOR->m_position.x, PMONITOR->m_position.y, PMONITOR->m_size.x, PMONITOR->m_size.y}; - m_resource->applyPositioning(box, COORDS); + m_resource->applyPositioning(m_windowOwner ? PMONITOR->logicalBoxMinusReserved() : PMONITOR->logicalBox(), COORDS); } -SP CPopup::getT1Owner() { +SP CPopup::getT1Owner() const { if (m_windowOwner) - return m_windowOwner->m_wlSurface; + return m_windowOwner->wlSurface(); else - return m_layerOwner->m_surface; + return m_layerOwner->wlSurface(); } -Vector2D CPopup::coordsRelativeToParent() { +PHLLS CPopup::layerOwner() const { + return m_layerOwner.lock(); +} + +Vector2D CPopup::coordsRelativeToParent() const { Vector2D offset; if (!m_resource) @@ -304,7 +386,7 @@ Vector2D CPopup::coordsRelativeToParent() { while (current->m_parent && current->m_resource) { - offset += current->m_wlSurface->resource()->m_current.offset; + offset += current->wlSurface()->resource()->m_current.offset; offset += current->m_resource->m_geometry.pos(); current = current->m_parent; @@ -313,15 +395,15 @@ Vector2D CPopup::coordsRelativeToParent() { return offset; } -Vector2D CPopup::coordsGlobal() { +Vector2D CPopup::coordsGlobal() const { return localToGlobal(coordsRelativeToParent()); } -Vector2D CPopup::localToGlobal(const Vector2D& rel) { +Vector2D CPopup::localToGlobal(const Vector2D& rel) const { return t1ParentCoords() + rel; } -Vector2D CPopup::t1ParentCoords() { +Vector2D CPopup::t1ParentCoords() const { if (!m_windowOwner.expired()) return m_windowOwner->m_realPosition->value(); if (!m_layerOwner.expired()) @@ -331,6 +413,15 @@ Vector2D CPopup::t1ParentCoords() { return {}; } +void CPopup::invalidateTreeExtentsCache() { + auto head = popupHead(); + if (!head) + return; + + head->m_treeExtentsCacheDirty = true; + head->m_treePopupCountCacheDirty = true; +} + void CPopup::recheckTree() { WP curr = m_self; while (curr->m_parent) { @@ -347,41 +438,36 @@ 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) { - c->onCommit(true); - c->recheckChildrenRecursive(); + if (!c || !c->visible()) + continue; + + // keep ref, onCommit can call onDestroy + auto x = c.lock(); + + x->onCommit(true); + x->recheckChildrenRecursive(); } } -Vector2D CPopup::size() { +Vector2D CPopup::size() const { return m_lastSize; } void CPopup::sendScale() { if (!m_windowOwner.expired()) - g_pCompositor->setPreferredScaleForSurface(m_wlSurface->resource(), m_windowOwner->m_wlSurface->m_lastScaleFloat); + g_pCompositor->setPreferredScaleForSurface(m_wlSurface->resource(), m_windowOwner->wlSurface()->m_lastScaleFloat); else if (!m_layerOwner.expired()) - g_pCompositor->setPreferredScaleForSurface(m_wlSurface->resource(), m_layerOwner->m_surface->m_lastScaleFloat); + g_pCompositor->setPreferredScaleForSurface(m_wlSurface->resource(), m_layerOwner->wlSurface()->m_lastScaleFloat); else UNREACHABLE(); } -bool CPopup::visible() { - if (!m_windowOwner.expired()) - return g_pHyprRenderer->shouldRenderWindow(m_windowOwner.lock()); - if (!m_layerOwner.expired()) - return true; - if (m_parent) - return m_parent->visible(); - - return false; -} - -void CPopup::bfHelper(std::vector> const& nodes, std::function, void*)> fn, void* data) { +void CPopup::bfHelper(std::vector> const& nodes, std::function, void*)> fn, void* data) { for (auto const& n : nodes) { fn(n, data); } - std::vector> nodes2; + std::vector> nodes2; nodes2.reserve(nodes.size() * 2); for (auto const& n : nodes) { @@ -389,7 +475,7 @@ void CPopup::bfHelper(std::vector> const& nodes, std::functionm_children) { - nodes2.push_back(c->m_self); + nodes2.emplace_back(c->m_self.lock()); } } @@ -397,20 +483,136 @@ void CPopup::bfHelper(std::vector> const& nodes, std::function, void*)> fn, void* data) { +void CPopup::breadthfirst(std::function, void*)> fn, void* data) { if (!m_self) return; - std::vector> popups; - popups.push_back(m_self); + std::vector> popups; + popups.emplace_back(m_self.lock()); bfHelper(popups, fn, data); } -WP CPopup::at(const Vector2D& globalCoords, bool allowsInput) { - std::vector> popups; - breadthfirst([&popups](WP popup, void* data) { popups.push_back(popup); }, &popups); +SP CPopup::popupHead() const { + auto head = m_self.lock(); + while (head && head->m_parent) + head = head->m_parent.lock(); + + return head; +} + +const CBox& CPopup::popupTreeExtents() const { + static const CBox EMPTY = {}; + + auto head = popupHead(); + if (!head) + return EMPTY; + + if (!head->m_treeExtentsCacheDirty) + return head->m_cachedTreeExtents; + + head->m_treeExtentsCacheDirty = false; + head->m_cachedTreeExtents = {}; + + std::vector> popups; + popups.emplace_back(head); + + bool found = false; + double minX = 0.0; + double minY = 0.0; + double maxX = 0.0; + double maxY = 0.0; + + for (size_t i = 0; i < popups.size(); ++i) { + const auto& popup = popups[i]; + if (!popup) + continue; + + if (popup->wlSurface() && popup->wlSurface()->resource()) { + const CBox surf = CBox{popup->coordsRelativeToParent(), popup->size()}; + + if (!found) { + found = true; + minX = surf.x; + minY = surf.y; + maxX = surf.x + surf.w; + maxY = surf.y + surf.h; + } else { + minX = std::min(minX, surf.x); + minY = std::min(minY, surf.y); + maxX = std::max(maxX, surf.x + surf.w); + maxY = std::max(maxY, surf.y + surf.h); + } + } + + for (const auto& c : popup->m_children) { + popups.emplace_back(c->m_self.lock()); + } + } + + if (!found) + return head->m_cachedTreeExtents; + + head->m_cachedTreeExtents = { + minX, + minY, + std::max(0.0, maxX - minX), + std::max(0.0, maxY - minY), + }; + + return head->m_cachedTreeExtents; +} + +int CPopup::popupTreeCount() const { + auto head = popupHead(); + if (!head) + return 0; + + if (!head->m_treePopupCountCacheDirty) + return head->m_cachedTreePopupCount; + + head->m_treePopupCountCacheDirty = false; + head->m_cachedTreePopupCount = 0; + + std::vector> popups; + popups.emplace_back(head); + + for (size_t i = 0; i < popups.size(); ++i) { + const auto& popup = popups[i]; + if (!popup) + continue; + + for (const auto& c : popup->m_children) { + if (!c) + continue; + + popups.emplace_back(c); + head->m_cachedTreePopupCount++; + } + } + + return head->m_cachedTreePopupCount; +} + +SP CPopup::at(const Vector2D& globalCoords, bool allowsInput) { + thread_local std::vector> popups; + popups.clear(); + + popups.emplace_back(m_self.lock()); + + for (size_t i = 0; i < popups.size(); ++i) { + const auto& popup = popups[i]; + if (!popup) + continue; + + for (const auto& c : popup->m_children) { + popups.emplace_back(c->m_self.lock()); + } + } for (auto const& p : popups | std::views::reverse) { + if (!p) + continue; + if (!p->m_resource || !p->m_mapped) continue; @@ -424,15 +626,20 @@ WP CPopup::at(const Vector2D& globalCoords, bool allowsInput) { size = p->size(); const auto BOX = CBox{p->coordsGlobal() + offset, size}; - if (BOX.containsPoint(globalCoords)) + if (BOX.containsPoint(globalCoords)) { + popups.clear(); return p; + } } else { - const auto REGION = CRegion{p->m_wlSurface->resource()->m_current.input}.intersect(CBox{{}, p->m_wlSurface->resource()->m_current.size}).translate(p->coordsGlobal()); - if (REGION.containsPoint(globalCoords)) + const auto REGION = CRegion{p->wlSurface()->resource()->m_current.input}.intersect(CBox{{}, p->wlSurface()->resource()->m_current.size}).translate(p->coordsGlobal()); + if (REGION.containsPoint(globalCoords)) { + popups.clear(); return p; + } } } + popups.clear(); return {}; } @@ -440,7 +647,7 @@ bool CPopup::inert() const { return m_inert; } -PHLMONITOR CPopup::getMonitor() { +PHLMONITOR CPopup::getMonitor() const { if (!m_windowOwner.expired()) return m_windowOwner->m_monitor.lock(); if (!m_layerOwner.expired()) diff --git a/src/desktop/view/Popup.hpp b/src/desktop/view/Popup.hpp new file mode 100644 index 000000000..a2a2e014a --- /dev/null +++ b/src/desktop/view/Popup.hpp @@ -0,0 +1,119 @@ +#pragma once + +#include +#include "Subsurface.hpp" +#include "View.hpp" +#include "../../helpers/signal/Signal.hpp" +#include "../../helpers/memory/Memory.hpp" +#include "../../helpers/AnimatedVariable.hpp" +#include "../../render/Framebuffer.hpp" + +class CXDGPopupResource; + +namespace Desktop::View { + + class CPopup : public IView { + public: + // dummy head nodes + static SP create(PHLWINDOW pOwner); + static SP create(PHLLS pOwner); + + // real nodes + static SP create(SP popup, WP pOwner); + + static SP fromView(SP); + + virtual ~CPopup(); + + virtual eViewType type() const; + virtual bool visible() const; + virtual std::optional logicalBox() const; + virtual bool desktopComponent() const; + virtual std::optional surfaceLogicalBox() const; + + SP getT1Owner() const; + PHLLS layerOwner() const; + Vector2D coordsRelativeToParent() const; + Vector2D coordsGlobal() const; + PHLMONITOR getMonitor() const; + + Vector2D size() const; + + void onNewPopup(SP popup); + void onDestroy(); + void onMap(); + void onUnmap(); + void onCommit(bool ignoreSiblings = false); + void onReposition(); + + void recheckTree(); + + bool inert() const; + + // will also loop over this node + void breadthfirst(std::function, void*)> fn, void* data); + SP at(const Vector2D& globalCoords, bool allowsInput = false); + SP popupHead() const; + const CBox& popupTreeExtents() const; + int popupTreeCount() const; + + // + WP m_self; + bool m_mapped = false; + + // fade in-out + PHLANIMVAR m_alpha; + bool m_fadingOut = false; + + SP m_snapshotFB; + + private: + CPopup(); + + // T1 owners, each popup has to have one of these + PHLWINDOWREF m_windowOwner; + PHLLSREF m_layerOwner; + + // T2 owners + WP m_parent; + + WP m_resource; + + Vector2D m_lastSize = {}; + Vector2D m_lastPos = {}; + + bool m_requestedReposition = false; + + bool m_inert = false; + + mutable CBox m_cachedTreeExtents = {}; + mutable bool m_treeExtentsCacheDirty = true; + mutable int m_cachedTreePopupCount = 0; + mutable bool m_treePopupCountCacheDirty = true; + + // + std::vector> m_children; + SP m_subsurfaceHead; + + struct { + CHyprSignalListener newPopup; + CHyprSignalListener destroy; + CHyprSignalListener map; + CHyprSignalListener unmap; + CHyprSignalListener commit; + CHyprSignalListener dismissed; + CHyprSignalListener reposition; + } m_listeners; + + void initAllSignals(); + void reposition(); + void recheckChildrenRecursive(); + void sendScale(); + void fullyDestroy(); + + Vector2D localToGlobal(const Vector2D& rel) const; + Vector2D t1ParentCoords() const; + void invalidateTreeExtentsCache(); + static void bfHelper(std::vector> const& nodes, std::function, void*)> fn, void* data); + }; +} diff --git a/src/desktop/view/SessionLock.cpp b/src/desktop/view/SessionLock.cpp new file mode 100644 index 000000000..a4a5b78b0 --- /dev/null +++ b/src/desktop/view/SessionLock.cpp @@ -0,0 +1,74 @@ +#include "SessionLock.hpp" + +#include "../../protocols/SessionLock.hpp" +#include "../../protocols/core/Compositor.hpp" +#include "../../helpers/Monitor.hpp" + +#include "../../Compositor.hpp" + +using namespace Desktop; +using namespace Desktop::View; + +SP View::CSessionLock::create(SP resource) { + auto lock = SP(new CSessionLock()); + lock->m_surface = resource; + lock->m_self = lock; + + lock->init(); + + return lock; +} + +View::CSessionLock::CSessionLock() : IView(CWLSurface::create()) { + ; +} + +View::CSessionLock::~CSessionLock() { + m_wlSurface->unassign(); +} + +void View::CSessionLock::init() { + m_listeners.destroy = m_surface->m_events.destroy.listen([this] { std::erase_if(g_pCompositor->m_otherViews, [this](const auto& e) { return e == m_self; }); }); + + m_wlSurface->assign(m_surface->surface(), m_self.lock()); +} + +SP View::CSessionLock::fromView(SP v) { + if (!v || v->type() != VIEW_TYPE_LOCK_SCREEN) + return nullptr; + return dynamicPointerCast(v); +} + +eViewType View::CSessionLock::type() const { + return VIEW_TYPE_LOCK_SCREEN; +} + +bool View::CSessionLock::visible() const { + return m_wlSurface && m_wlSurface->resource() && m_wlSurface->resource()->m_mapped; +} + +std::optional View::CSessionLock::logicalBox() const { + return surfaceLogicalBox(); +} + +std::optional View::CSessionLock::surfaceLogicalBox() const { + if (!visible()) + return std::nullopt; + + const auto MON = m_surface->monitor(); + + if (!MON) + return std::nullopt; + + return MON->logicalBox(); +} + +bool View::CSessionLock::desktopComponent() const { + return true; +} + +PHLMONITOR View::CSessionLock::monitor() const { + if (m_surface) + return m_surface->monitor(); + return nullptr; +} diff --git a/src/desktop/view/SessionLock.hpp b/src/desktop/view/SessionLock.hpp new file mode 100644 index 000000000..c6141fb25 --- /dev/null +++ b/src/desktop/view/SessionLock.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "../../defines.hpp" +#include +#include "WLSurface.hpp" +#include "View.hpp" + +class CSessionLockSurface; + +namespace Desktop::View { + class CSessionLock : public IView { + public: + static SP create(SP resource); + + static SP fromView(SP); + + virtual ~CSessionLock(); + + virtual eViewType type() const; + virtual bool visible() const; + virtual std::optional logicalBox() const; + virtual bool desktopComponent() const; + virtual std::optional surfaceLogicalBox() const; + + PHLMONITOR monitor() const; + + WP m_self; + + private: + CSessionLock(); + + void init(); + + struct { + CHyprSignalListener destroy; + } m_listeners; + + WP m_surface; + }; +} diff --git a/src/desktop/Subsurface.cpp b/src/desktop/view/Subsurface.cpp similarity index 61% rename from src/desktop/Subsurface.cpp rename to src/desktop/view/Subsurface.cpp index cea6977a9..c3c985dfc 100644 --- a/src/desktop/Subsurface.cpp +++ b/src/desktop/view/Subsurface.cpp @@ -1,56 +1,101 @@ #include "Subsurface.hpp" -#include "../events/Events.hpp" -#include "../desktop/state/FocusState.hpp" -#include "../desktop/Window.hpp" -#include "../config/ConfigValue.hpp" -#include "../protocols/core/Compositor.hpp" -#include "../protocols/core/Subcompositor.hpp" -#include "../render/Renderer.hpp" -#include "../managers/input/InputManager.hpp" +#include "../state/FocusState.hpp" +#include "Window.hpp" +#include "../../config/ConfigValue.hpp" +#include "../../protocols/core/Compositor.hpp" +#include "../../protocols/core/Subcompositor.hpp" +#include "../../render/Renderer.hpp" +#include "../../managers/input/InputManager.hpp" -UP CSubsurface::create(PHLWINDOW pOwner) { - auto subsurface = UP(new CSubsurface()); +using namespace Desktop; +using namespace Desktop::View; + +SP CSubsurface::create(PHLWINDOW pOwner) { + auto subsurface = SP(new CSubsurface()); subsurface->m_windowParent = pOwner; subsurface->m_self = subsurface; subsurface->initSignals(); - subsurface->initExistingSubsurfaces(pOwner->m_wlSurface->resource()); + subsurface->initExistingSubsurfaces(pOwner->wlSurface()->resource()); return subsurface; } -UP CSubsurface::create(WP pOwner) { - auto subsurface = UP(new CSubsurface()); +SP CSubsurface::create(WP pOwner) { + auto subsurface = SP(new CSubsurface()); subsurface->m_popupParent = pOwner; subsurface->m_self = subsurface; subsurface->initSignals(); - subsurface->initExistingSubsurfaces(pOwner->m_wlSurface->resource()); + subsurface->initExistingSubsurfaces(pOwner->wlSurface()->resource()); return subsurface; } -UP CSubsurface::create(SP pSubsurface, PHLWINDOW pOwner) { - auto subsurface = UP(new CSubsurface()); +SP CSubsurface::create(SP pSubsurface, PHLWINDOW pOwner) { + auto subsurface = SP(new CSubsurface()); subsurface->m_windowParent = pOwner; subsurface->m_subsurface = pSubsurface; subsurface->m_self = subsurface; - subsurface->m_wlSurface = CWLSurface::create(); - subsurface->m_wlSurface->assign(pSubsurface->m_surface.lock(), subsurface.get()); + subsurface->wlSurface() = CWLSurface::create(); + subsurface->wlSurface()->assign(pSubsurface->m_surface.lock(), subsurface); subsurface->initSignals(); subsurface->initExistingSubsurfaces(pSubsurface->m_surface.lock()); return subsurface; } -UP CSubsurface::create(SP pSubsurface, WP pOwner) { - auto subsurface = UP(new CSubsurface()); +SP CSubsurface::create(SP pSubsurface, WP pOwner) { + auto subsurface = SP(new CSubsurface()); subsurface->m_popupParent = pOwner; subsurface->m_subsurface = pSubsurface; subsurface->m_self = subsurface; - subsurface->m_wlSurface = CWLSurface::create(); - subsurface->m_wlSurface->assign(pSubsurface->m_surface.lock(), subsurface.get()); + subsurface->wlSurface() = CWLSurface::create(); + subsurface->wlSurface()->assign(pSubsurface->m_surface.lock(), subsurface); subsurface->initSignals(); subsurface->initExistingSubsurfaces(pSubsurface->m_surface.lock()); return subsurface; } +SP CSubsurface::fromView(SP v) { + if (!v || v->type() != VIEW_TYPE_SUBSURFACE) + return nullptr; + return dynamicPointerCast(v); +} + +CSubsurface::CSubsurface() : IView(CWLSurface::create()) { + ; +} + +eViewType CSubsurface::type() const { + return VIEW_TYPE_SUBSURFACE; +} + +bool CSubsurface::visible() const { + if (!m_wlSurface || !m_wlSurface->resource() || !m_wlSurface->resource()->m_mapped) + return false; + + if (!m_windowParent.expired()) + return g_pHyprRenderer->shouldRenderWindow(m_windowParent.lock()); + if (m_popupParent) + return m_popupParent->visible(); + if (m_parent) + return m_parent->visible(); + + return false; +} + +bool CSubsurface::desktopComponent() const { + return true; +} + +std::optional CSubsurface::logicalBox() const { + return surfaceLogicalBox(); +} + +std::optional CSubsurface::surfaceLogicalBox() const { + if (!visible()) + return std::nullopt; + + return CBox{coordsGlobal(), m_lastSize}; +} + void CSubsurface::initSignals() { if (m_subsurface) { m_listeners.commitSubsurface = m_subsurface->m_surface->m_events.commit.listen([this] { onCommit(); }); @@ -60,9 +105,9 @@ void CSubsurface::initSignals() { m_listeners.newSubsurface = m_subsurface->m_surface->m_events.newSubsurface.listen([this](const auto& resource) { onNewSubsurface(resource); }); } else { if (m_windowParent) - m_listeners.newSubsurface = m_windowParent->m_wlSurface->resource()->m_events.newSubsurface.listen([this](const auto& resource) { onNewSubsurface(resource); }); + m_listeners.newSubsurface = m_windowParent->wlSurface()->resource()->m_events.newSubsurface.listen([this](const auto& resource) { onNewSubsurface(resource); }); else if (m_popupParent) - m_listeners.newSubsurface = m_popupParent->m_wlSurface->resource()->m_events.newSubsurface.listen([this](const auto& resource) { onNewSubsurface(resource); }); + m_listeners.newSubsurface = m_popupParent->wlSurface()->resource()->m_events.newSubsurface.listen([this](const auto& resource) { onNewSubsurface(resource); }); else ASSERT(false); } @@ -79,14 +124,36 @@ void CSubsurface::checkSiblingDamage() { continue; const auto COORDS = n->coordsGlobal(); - g_pHyprRenderer->damageSurface(n->m_wlSurface->resource(), COORDS.x, COORDS.y, SCALE); + g_pHyprRenderer->damageSurface(n->wlSurface()->resource(), COORDS.x, COORDS.y, SCALE); } } -void CSubsurface::recheckDamageForSubsurfaces() { +void CSubsurface::recheckDamageForSubsurfaces(int depth) { + if (depth > 32) + return; + for (auto const& n : m_children) { const auto COORDS = n->coordsGlobal(); - g_pHyprRenderer->damageSurface(n->m_wlSurface->resource(), COORDS.x, COORDS.y); + const auto SIZE = n->size(); + + const bool GEOMETRYUNCHANGED = n->m_hasLastRecheckGeometry && COORDS.x == n->m_lastRecheckGlobalPos.x && COORDS.y == n->m_lastRecheckGlobalPos.y && + SIZE.x == n->m_lastRecheckGlobalSize.x && SIZE.y == n->m_lastRecheckGlobalSize.y; + + if (!GEOMETRYUNCHANGED) { + // damage the old area to clear stale pixels + if (n->m_hasLastRecheckGeometry) + g_pHyprRenderer->damageBox(CBox{n->m_lastRecheckGlobalPos, n->m_lastRecheckGlobalSize}.expand(4)); + + n->m_lastRecheckGlobalPos = COORDS; + n->m_lastRecheckGlobalSize = SIZE; + n->m_hasLastRecheckGeometry = true; + + // damage the new area + g_pHyprRenderer->damageBox(CBox{COORDS, SIZE}.expand(4)); + } + + // recurse into children to handle nested subsurface trees + n->recheckDamageForSubsurfaces(depth + 1); } } @@ -97,7 +164,7 @@ void CSubsurface::onCommit() { static auto PLOGDAMAGE = CConfigValue("debug:log_damage"); if (*PLOGDAMAGE) - Debug::log(LOG, "Refusing to commit damage from a subsurface of {} because it's invisible.", m_windowParent.lock()); + Log::logger->log(Log::DEBUG, "Refusing to commit damage from a subsurface of {} because it's invisible.", m_windowParent.lock()); return; } @@ -105,7 +172,7 @@ void CSubsurface::onCommit() { g_pHyprRenderer->damageSurface(m_wlSurface->resource(), COORDS.x, COORDS.y); - if (m_popupParent && !m_popupParent->inert() && m_popupParent->m_wlSurface) + if (m_popupParent && !m_popupParent->inert() && m_popupParent->wlSurface()) m_popupParent->recheckTree(); if (!m_windowParent.expired()) // I hate you firefox why are you doing this m_windowParent->m_popupHead->recheckTree(); @@ -149,8 +216,9 @@ void CSubsurface::onNewSubsurface(SP pSubsurface) { } void CSubsurface::onMap() { - m_lastSize = m_wlSurface->resource()->m_current.size; - m_lastPosition = m_subsurface->m_position; + m_lastSize = m_wlSurface->resource()->m_current.size; + m_lastPosition = m_subsurface->m_position; + m_hasLastRecheckGeometry = false; const auto COORDS = coordsGlobal(); CBox box{COORDS, m_lastSize}; @@ -164,6 +232,8 @@ void CSubsurface::onMap() { void CSubsurface::onUnmap() { damageLastArea(); + m_hasLastRecheckGeometry = false; + if (m_wlSurface->resource() == Desktop::focusState()->surface()) g_pInputManager->releaseAllMouseButtons(); @@ -187,13 +257,13 @@ void CSubsurface::damageLastArea() { g_pHyprRenderer->damageBox(box); } -Vector2D CSubsurface::coordsRelativeToParent() { +Vector2D CSubsurface::coordsRelativeToParent() const { if (!m_subsurface) return {}; return m_subsurface->posRelativeToParent(); } -Vector2D CSubsurface::coordsGlobal() { +Vector2D CSubsurface::coordsGlobal() const { Vector2D coords = coordsRelativeToParent(); if (!m_windowParent.expired()) @@ -215,14 +285,3 @@ void CSubsurface::initExistingSubsurfaces(SP pSurface) { Vector2D CSubsurface::size() { return m_wlSurface->resource()->m_current.size; } - -bool CSubsurface::visible() { - if (!m_windowParent.expired()) - return g_pHyprRenderer->shouldRenderWindow(m_windowParent.lock()); - if (m_popupParent) - return m_popupParent->visible(); - if (m_parent) - return m_parent->visible(); - - return false; -} diff --git a/src/desktop/view/Subsurface.hpp b/src/desktop/view/Subsurface.hpp new file mode 100644 index 000000000..bb2f67806 --- /dev/null +++ b/src/desktop/view/Subsurface.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include "../../defines.hpp" +#include +#include "WLSurface.hpp" +#include "View.hpp" + +class CWLSubsurfaceResource; + +namespace Desktop::View { + class CPopup; + class CSubsurface : public IView { + public: + // root dummy nodes + static SP create(PHLWINDOW pOwner); + static SP create(WP pOwner); + + // real nodes + static SP create(SP pSubsurface, PHLWINDOW pOwner); + static SP create(SP pSubsurface, WP pOwner); + + static SP fromView(SP); + + virtual ~CSubsurface() = default; + + virtual eViewType type() const; + virtual bool visible() const; + virtual std::optional logicalBox() const; + virtual bool desktopComponent() const; + virtual std::optional surfaceLogicalBox() const; + + Vector2D coordsRelativeToParent() const; + Vector2D coordsGlobal() const; + + Vector2D size(); + + void onCommit(); + void onDestroy(); + void onNewSubsurface(SP pSubsurface); + void onMap(); + void onUnmap(); + + void recheckDamageForSubsurfaces(int depth = 0); + + WP m_self; + + private: + CSubsurface(); + + struct { + CHyprSignalListener destroySubsurface; + CHyprSignalListener commitSubsurface; + CHyprSignalListener mapSubsurface; + CHyprSignalListener unmapSubsurface; + CHyprSignalListener newSubsurface; + } m_listeners; + + WP m_subsurface; + Vector2D m_lastSize = {}; + Vector2D m_lastPosition = {}; + Vector2D m_lastRecheckGlobalPos; + Vector2D m_lastRecheckGlobalSize; + bool m_hasLastRecheckGeometry = false; + + // if nullptr, means it's a dummy node + WP m_parent; + + PHLWINDOWREF m_windowParent; + WP m_popupParent; + + std::vector> m_children; + + bool m_inert = false; + + void initSignals(); + void initExistingSubsurfaces(SP pSurface); + void checkSiblingDamage(); + void damageLastArea(); + }; +} diff --git a/src/desktop/view/View.cpp b/src/desktop/view/View.cpp new file mode 100644 index 000000000..17a10c64b --- /dev/null +++ b/src/desktop/view/View.cpp @@ -0,0 +1,28 @@ +#include "View.hpp" +#include "../../protocols/core/Compositor.hpp" + +using namespace Desktop; +using namespace Desktop::View; + +SP IView::wlSurface() const { + return m_wlSurface; +} + +IView::IView(SP pWlSurface) : m_wlSurface(pWlSurface) { + ; +} + +SP IView::resource() const { + return m_wlSurface ? m_wlSurface->resource() : nullptr; +} + +bool IView::aliveAndVisible() const { + auto res = resource(); + if (!res) + return false; + + if (!res->m_mapped) + return false; + + return visible(); +} diff --git a/src/desktop/view/View.hpp b/src/desktop/view/View.hpp new file mode 100644 index 000000000..4d777c36d --- /dev/null +++ b/src/desktop/view/View.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "WLSurface.hpp" +#include "../../helpers/math/Math.hpp" + +namespace Desktop::View { + enum eViewType : uint8_t { + VIEW_TYPE_WINDOW = 0, + VIEW_TYPE_SUBSURFACE, + VIEW_TYPE_POPUP, + VIEW_TYPE_LAYER_SURFACE, + VIEW_TYPE_LOCK_SCREEN, + }; + + class IView { + public: + virtual ~IView() = default; + + virtual SP wlSurface() const; + virtual SP resource() const; + virtual bool aliveAndVisible() const; + virtual eViewType type() const = 0; + virtual bool visible() const = 0; + virtual bool desktopComponent() const = 0; + virtual std::optional logicalBox() const = 0; + virtual std::optional surfaceLogicalBox() const = 0; + + protected: + IView(SP pWlSurface); + + SP m_wlSurface; + }; +}; \ No newline at end of file diff --git a/src/desktop/WLSurface.cpp b/src/desktop/view/WLSurface.cpp similarity index 56% rename from src/desktop/WLSurface.cpp rename to src/desktop/view/WLSurface.cpp index a7b654f0b..ae8a22e2c 100644 --- a/src/desktop/WLSurface.cpp +++ b/src/desktop/view/WLSurface.cpp @@ -1,9 +1,12 @@ #include "WLSurface.hpp" #include "LayerSurface.hpp" -#include "../desktop/Window.hpp" -#include "../protocols/core/Compositor.hpp" -#include "../protocols/LayerShell.hpp" -#include "../render/Renderer.hpp" +#include "Window.hpp" +#include "../../protocols/core/Compositor.hpp" +#include "../../protocols/LayerShell.hpp" +#include "../../render/Renderer.hpp" + +using namespace Desktop; +using namespace Desktop::View; void CWLSurface::assign(SP pSurface) { m_resource = pSurface; @@ -11,30 +14,9 @@ void CWLSurface::assign(SP pSurface) { m_inert = false; } -void CWLSurface::assign(SP pSurface, PHLWINDOW pOwner) { - m_windowOwner = pOwner; - m_resource = pSurface; - init(); - m_inert = false; -} - -void CWLSurface::assign(SP pSurface, PHLLS pOwner) { - m_layerOwner = pOwner; - m_resource = pSurface; - init(); - m_inert = false; -} - -void CWLSurface::assign(SP pSurface, CSubsurface* pOwner) { - m_subsurfaceOwner = pOwner; - m_resource = pSurface; - init(); - m_inert = false; -} - -void CWLSurface::assign(SP pSurface, CPopup* pOwner) { - m_popupOwner = pOwner; - m_resource = pSurface; +void CWLSurface::assign(SP pSurface, SP pOwner) { + m_view = pOwner; + m_resource = pSurface; init(); m_inert = false; } @@ -56,24 +38,24 @@ SP CWLSurface::resource() const { } bool CWLSurface::small() const { - if (!validMapped(m_windowOwner) || !exists()) + if (!m_view || !m_view->aliveAndVisible() || m_view->type() != VIEW_TYPE_WINDOW || !exists()) return false; if (!m_resource->m_current.texture) return false; - const auto O = m_windowOwner.lock(); + const auto O = dynamicPointerCast(m_view.lock()); const auto REPORTED_SIZE = O->getReportedSize(); return REPORTED_SIZE.x > m_resource->m_current.size.x + 1 || REPORTED_SIZE.y > m_resource->m_current.size.y + 1; } Vector2D CWLSurface::correctSmallVec() const { - if (!validMapped(m_windowOwner) || !exists() || !small() || m_fillIgnoreSmall) + if (!m_view || !m_view->aliveAndVisible() || m_view->type() != VIEW_TYPE_WINDOW || !exists() || !small() || !m_fillIgnoreSmall) return {}; const auto SIZE = getViewporterCorrectedSize(); - const auto O = m_windowOwner.lock(); + const auto O = dynamicPointerCast(m_view.lock()); const auto REP = O->getReportedSize(); return Vector2D{(REP.x - SIZE.x) / 2, (REP.y - SIZE.y) / 2}.clamp({}, {INFINITY, INFINITY}) * (O->m_realSize->value() / REP); @@ -101,7 +83,7 @@ CRegion CWLSurface::computeDamage() const { return {}; CRegion damage = m_resource->m_current.accumulateBufferDamage(); - damage.transform(wlTransformToHyprutils(m_resource->m_current.transform), m_resource->m_current.bufferSize.x, m_resource->m_current.bufferSize.y); + damage.transform(Math::wlTransformToHyprutils(m_resource->m_current.transform), m_resource->m_current.bufferSize.x, m_resource->m_current.bufferSize.y); const auto BUFSIZE = m_resource->m_current.bufferSize; const auto CORRECTVEC = correctSmallVecBuf(); @@ -123,8 +105,8 @@ CRegion CWLSurface::computeDamage() const { damage.scale(SCALE); if (BOX.has_value()) { - if (m_windowOwner) - damage.intersect(CBox{{}, BOX->size() * m_windowOwner->m_X11SurfaceScaledBy}); + if (m_view->type() == VIEW_TYPE_WINDOW) + damage.intersect(CBox{{}, BOX->size() * dynamicPointerCast(m_view.lock())->m_X11SurfaceScaledBy}); else damage.intersect(CBox{{}, BOX->size()}); } @@ -142,18 +124,15 @@ void CWLSurface::destroy() { m_listeners.destroy.reset(); m_resource->m_hlSurface.reset(); - m_windowOwner.reset(); - m_layerOwner.reset(); - m_popupOwner = nullptr; - m_subsurfaceOwner = nullptr; - m_inert = true; + m_view.reset(); + m_inert = true; if (g_pHyprRenderer && g_pHyprRenderer->m_lastCursorData.surf && g_pHyprRenderer->m_lastCursorData.surf->get() == this) g_pHyprRenderer->m_lastCursorData.surf.reset(); m_resource.reset(); - Debug::log(LOG, "CWLSurface {:x} called destroy()", rc(this)); + Log::logger->log(Log::DEBUG, "CWLSurface {:x} called destroy()", rc(this)); } void CWLSurface::init() { @@ -166,43 +145,22 @@ void CWLSurface::init() { m_listeners.destroy = m_resource->m_events.destroy.listen([this] { destroy(); }); - Debug::log(LOG, "CWLSurface {:x} called init()", rc(this)); + Log::logger->log(Log::DEBUG, "CWLSurface {:x} called init()", rc(this)); } -PHLWINDOW CWLSurface::getWindow() const { - return m_windowOwner.lock(); -} - -PHLLS CWLSurface::getLayer() const { - return m_layerOwner.lock(); -} - -CPopup* CWLSurface::getPopup() const { - return m_popupOwner; -} - -CSubsurface* CWLSurface::getSubsurface() const { - return m_subsurfaceOwner; +SP CWLSurface::view() const { + return m_view.lock(); } bool CWLSurface::desktopComponent() const { - return !m_layerOwner.expired() || !m_windowOwner.expired() || m_subsurfaceOwner || m_popupOwner; + return m_view && m_view->visible(); } std::optional CWLSurface::getSurfaceBoxGlobal() const { if (!desktopComponent()) return {}; - if (!m_windowOwner.expired()) - return m_windowOwner->getWindowMainSurfaceBox(); - if (!m_layerOwner.expired()) - return m_layerOwner->m_geometry; - if (m_popupOwner) - return CBox{m_popupOwner->coordsGlobal(), m_popupOwner->size()}; - if (m_subsurfaceOwner) - return CBox{m_subsurfaceOwner->coordsGlobal(), m_subsurfaceOwner->size()}; - - return {}; + return m_view->surfaceLogicalBox(); } void CWLSurface::appendConstraint(WP constraint) { @@ -213,28 +171,18 @@ SP CWLSurface::constraint() const { return m_constraint.lock(); } -bool CWLSurface::visible() { - if (!m_windowOwner.expired()) - return g_pHyprRenderer->shouldRenderWindow(m_windowOwner.lock()); - if (!m_layerOwner.expired()) - return true; - if (m_popupOwner) - return m_popupOwner->visible(); - if (m_subsurfaceOwner) - return m_subsurfaceOwner->visible(); - return true; // non-desktop, we don't know much. -} - -SP CWLSurface::fromResource(SP pSurface) { +SP CWLSurface::fromResource(SP pSurface) { if (!pSurface) return nullptr; return pSurface->m_hlSurface.lock(); } bool CWLSurface::keyboardFocusable() const { - if (m_windowOwner || m_popupOwner || m_subsurfaceOwner) + if (!m_view) + return false; + if (m_view->type() == VIEW_TYPE_WINDOW || m_view->type() == VIEW_TYPE_SUBSURFACE || m_view->type() == VIEW_TYPE_POPUP) return true; - if (m_layerOwner && m_layerOwner->m_layerSurface) - return m_layerOwner->m_layerSurface->m_current.interactivity != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE; + if (const auto LS = CLayerSurface::fromView(m_view.lock()); LS && LS->m_layerSurface) + return LS->m_layerSurface->m_current.interactivity != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE; return false; } diff --git a/src/desktop/view/WLSurface.hpp b/src/desktop/view/WLSurface.hpp new file mode 100644 index 000000000..3c5e3a382 --- /dev/null +++ b/src/desktop/view/WLSurface.hpp @@ -0,0 +1,116 @@ +#pragma once + +#include "../../defines.hpp" +#include "../../helpers/math/Math.hpp" +#include "../../helpers/signal/Signal.hpp" + +class CPointerConstraint; +class CWLSurfaceResource; + +namespace Desktop::View { + class CSubsurface; + class CPopup; + class IView; + + class CWLSurface { + public: + static SP create() { + auto p = SP(new CWLSurface); + p->m_self = p; + return p; + } + ~CWLSurface(); + + // anonymous surfaces are non-desktop components, e.g. a cursor surface or a DnD + void assign(SP pSurface); + void assign(SP pSurface, SP pOwner); + void unassign(); + + CWLSurface(const CWLSurface&) = delete; + CWLSurface(CWLSurface&&) = delete; + CWLSurface& operator=(const CWLSurface&) = delete; + CWLSurface& operator=(CWLSurface&&) = delete; + + SP resource() const; + bool exists() const; + bool small() const; // means surface is smaller than the requested size + Vector2D correctSmallVec() const; // returns a corrective vector for small() surfaces + Vector2D correctSmallVecBuf() const; // returns a corrective vector for small() surfaces, in BL coords + Vector2D getViewporterCorrectedSize() const; + CRegion computeDamage() const; // logical coordinates. May be wrong if the surface is unassigned + bool keyboardFocusable() const; + + SP view() const; + + // desktop components misc utils + std::optional getSurfaceBoxGlobal() const; + void appendConstraint(WP constraint); + SP constraint() const; + + // allow stretching. Useful for plugins. + bool m_fillIgnoreSmall = false; + + // track surface data and avoid dupes + float m_lastScaleFloat = 0; + int m_lastScaleInt = 0; + wl_output_transform m_lastTransform = sc(-1); + + // + CWLSurface& operator=(SP pSurface) { + destroy(); + m_resource = pSurface; + init(); + + return *this; + } + + bool operator==(const CWLSurface& other) const { + return other.resource() == resource(); + } + + bool operator==(const SP other) const { + return other == resource(); + } + + explicit operator bool() const { + return exists(); + } + + static SP fromResource(SP pSurface); + + // used by the alpha-modifier protocol + float m_alphaModifier = 1.F; + + // used by the hyprland-surface protocol + float m_overallOpacity = 1.F; + CRegion m_visibleRegion; + + struct { + CSignalT<> destroy; + } m_events; + + WP m_self; + + private: + CWLSurface() = default; + + bool m_inert = true; + + WP m_resource; + + WP m_view; + + // + WP m_constraint; + + void destroy(); + void init(); + bool desktopComponent() const; + + struct { + CHyprSignalListener destroy; + } m_listeners; + + friend class ::CPointerConstraint; + }; +} diff --git a/src/desktop/view/Window.cpp b/src/desktop/view/Window.cpp new file mode 100644 index 000000000..6da62b340 --- /dev/null +++ b/src/desktop/view/Window.cpp @@ -0,0 +1,2539 @@ +#include +#include +#include +#include + +#include "Group.hpp" + +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) +#include +#include +#endif + +#include +#include +#include +#include +#include "Window.hpp" +#include "LayerSurface.hpp" +#include "../state/FocusState.hpp" +#include "../state/FloatState.hpp" +#include "../history/WindowHistoryTracker.hpp" +#include "../../Compositor.hpp" +#include "../../render/decorations/CHyprDropShadowDecoration.hpp" +#include "../../render/decorations/CHyprInnerGlowDecoration.hpp" +#include "../../render/decorations/CHyprGroupBarDecoration.hpp" +#include "../../render/decorations/CHyprBorderDecoration.hpp" +#include "../../config/ConfigValue.hpp" +#include "../../config/ConfigManager.hpp" +#include "../../config/shared/animation/AnimationTree.hpp" +#include "../../config/shared/workspace/WorkspaceRuleManager.hpp" +#include "../../managers/TokenManager.hpp" +#include "../../managers/animation/AnimationManager.hpp" +#include "../../managers/ANRManager.hpp" +#include "../../managers/eventLoop/EventLoopManager.hpp" +#include "../../protocols/XDGShell.hpp" +#include "../../protocols/core/Compositor.hpp" +#include "../../protocols/core/Subcompositor.hpp" +#include "../../protocols/ContentType.hpp" +#include "../../protocols/FractionalScale.hpp" +#include "../../protocols/LayerShell.hpp" +#include "../../xwayland/XWayland.hpp" +#include "../../helpers/Color.hpp" +#include "../../helpers/math/Expression.hpp" +#include "../../managers/XWaylandManager.hpp" +#include "../../render/Renderer.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 +#include +#include + +using namespace Hyprutils::String; +using namespace Hyprutils::Animation; +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)); + + pWindow->m_self = pWindow; + pWindow->m_isX11 = true; + pWindow->m_ruleApplicator = makeUnique(pWindow); + + g_pAnimationManager->createAnimation(Vector2D(0, 0), pWindow->m_realPosition, Config::animationTree()->getAnimationPropertyConfig("windowsIn"), pWindow, AVARDAMAGE_ENTIRE); + g_pAnimationManager->createAnimation(Vector2D(0, 0), pWindow->m_realSize, Config::animationTree()->getAnimationPropertyConfig("windowsIn"), pWindow, AVARDAMAGE_ENTIRE); + g_pAnimationManager->createAnimation(0.f, pWindow->m_borderFadeAnimationProgress, Config::animationTree()->getAnimationPropertyConfig("border"), pWindow, AVARDAMAGE_BORDER); + g_pAnimationManager->createAnimation(0.f, pWindow->m_borderAngleAnimationProgress, Config::animationTree()->getAnimationPropertyConfig("borderangle"), pWindow, + AVARDAMAGE_BORDER); + g_pAnimationManager->createAnimation(1.f, pWindow->m_alpha, Config::animationTree()->getAnimationPropertyConfig("fadeIn"), pWindow, AVARDAMAGE_ENTIRE); + g_pAnimationManager->createAnimation(1.f, pWindow->m_activeInactiveAlpha, Config::animationTree()->getAnimationPropertyConfig("fadeSwitch"), pWindow, AVARDAMAGE_ENTIRE); + g_pAnimationManager->createAnimation(CHyprColor(), pWindow->m_realShadowColor, Config::animationTree()->getAnimationPropertyConfig("fadeShadow"), pWindow, AVARDAMAGE_SHADOW); + g_pAnimationManager->createAnimation(CHyprColor(), pWindow->m_realGlowColor, Config::animationTree()->getAnimationPropertyConfig("fadeGlow"), pWindow, AVARDAMAGE_ENTIRE); + g_pAnimationManager->createAnimation(0.f, pWindow->m_dimPercent, Config::animationTree()->getAnimationPropertyConfig("fadeDim"), pWindow, AVARDAMAGE_ENTIRE); + g_pAnimationManager->createAnimation(0.f, pWindow->m_movingToWorkspaceAlpha, Config::animationTree()->getAnimationPropertyConfig("fadeOut"), pWindow, AVARDAMAGE_ENTIRE); + g_pAnimationManager->createAnimation(0.f, pWindow->m_movingFromWorkspaceAlpha, Config::animationTree()->getAnimationPropertyConfig("fadeIn"), pWindow, AVARDAMAGE_ENTIRE); + g_pAnimationManager->createAnimation(0.f, pWindow->m_notRespondingTint, Config::animationTree()->getAnimationPropertyConfig("fade"), pWindow, AVARDAMAGE_ENTIRE); + + pWindow->addWindowDeco(makeUnique(pWindow)); + pWindow->addWindowDeco(makeUnique(pWindow)); + pWindow->addWindowDeco(makeUnique(pWindow)); + + pWindow->m_target = Layout::CWindowTarget::create(pWindow); + + return pWindow; +} + +PHLWINDOW CWindow::create(SP resource) { + PHLWINDOW pWindow = SP(new CWindow(resource)); + + pWindow->m_self = pWindow; + resource->m_toplevel->m_window = pWindow; + pWindow->m_ruleApplicator = makeUnique(pWindow); + + g_pAnimationManager->createAnimation(Vector2D(0, 0), pWindow->m_realPosition, Config::animationTree()->getAnimationPropertyConfig("windowsIn"), pWindow, AVARDAMAGE_ENTIRE); + g_pAnimationManager->createAnimation(Vector2D(0, 0), pWindow->m_realSize, Config::animationTree()->getAnimationPropertyConfig("windowsIn"), pWindow, AVARDAMAGE_ENTIRE); + g_pAnimationManager->createAnimation(0.f, pWindow->m_borderFadeAnimationProgress, Config::animationTree()->getAnimationPropertyConfig("border"), pWindow, AVARDAMAGE_BORDER); + g_pAnimationManager->createAnimation(0.f, pWindow->m_borderAngleAnimationProgress, Config::animationTree()->getAnimationPropertyConfig("borderangle"), pWindow, + AVARDAMAGE_BORDER); + g_pAnimationManager->createAnimation(1.f, pWindow->m_alpha, Config::animationTree()->getAnimationPropertyConfig("fadeIn"), pWindow, AVARDAMAGE_ENTIRE); + g_pAnimationManager->createAnimation(1.f, pWindow->m_activeInactiveAlpha, Config::animationTree()->getAnimationPropertyConfig("fadeSwitch"), pWindow, AVARDAMAGE_ENTIRE); + g_pAnimationManager->createAnimation(CHyprColor(), pWindow->m_realShadowColor, Config::animationTree()->getAnimationPropertyConfig("fadeShadow"), pWindow, AVARDAMAGE_SHADOW); + g_pAnimationManager->createAnimation(CHyprColor(), pWindow->m_realGlowColor, Config::animationTree()->getAnimationPropertyConfig("fadeGlow"), pWindow, AVARDAMAGE_ENTIRE); + g_pAnimationManager->createAnimation(0.f, pWindow->m_dimPercent, Config::animationTree()->getAnimationPropertyConfig("fadeDim"), pWindow, AVARDAMAGE_ENTIRE); + g_pAnimationManager->createAnimation(0.f, pWindow->m_movingToWorkspaceAlpha, Config::animationTree()->getAnimationPropertyConfig("fadeOut"), pWindow, AVARDAMAGE_ENTIRE); + g_pAnimationManager->createAnimation(0.f, pWindow->m_movingFromWorkspaceAlpha, Config::animationTree()->getAnimationPropertyConfig("fadeIn"), pWindow, AVARDAMAGE_ENTIRE); + g_pAnimationManager->createAnimation(0.f, pWindow->m_notRespondingTint, Config::animationTree()->getAnimationPropertyConfig("fade"), pWindow, AVARDAMAGE_ENTIRE); + + pWindow->addWindowDeco(makeUnique(pWindow)); + 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++) { + 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(); }); + m_listeners.destroy = m_xdgSurface->m_events.destroy.listen([this] { destroyWindow(); }); + m_listeners.commit = m_xdgSurface->m_events.commit.listen([this] { commitWindow(); }); + m_listeners.updateState = m_xdgSurface->m_toplevel->m_events.stateChanged.listen([this] { onUpdateState(); }); + 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++) { + 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(); }); + m_listeners.commit = m_xwaylandSurface->m_events.commit.listen([this] { commitWindow(); }); + m_listeners.configureRequest = m_xwaylandSurface->m_events.configureRequest.listen([this](const CBox& box) { onX11ConfigureRequest(box); }); + m_listeners.updateState = m_xwaylandSurface->m_events.stateChanged.listen([this] { onUpdateState(); }); + m_listeners.updateMetadata = m_xwaylandSurface->m_events.metadataChanged.listen([this] { onUpdateMeta(); }); + m_listeners.resourceChange = m_xwaylandSurface->m_events.resourceChange.listen([this] { onResourceChangeX11(); }); + m_listeners.activate = m_xwaylandSurface->m_events.activate.listen([this] { activateX11(); }); + + if (m_xwaylandSurface->m_overrideRedirect) + m_listeners.setGeometry = m_xwaylandSurface->m_events.setGeometry.listen([this] { unmanagedSetGeometry(); }); +} + +SP CWindow::fromView(SP v) { + if (!v || v->type() != VIEW_TYPE_WINDOW) + return nullptr; + return dynamicPointerCast(v); +} + +CWindow::~CWindow() { + if (Desktop::focusState()->window() == m_self) { + Desktop::focusState()->surface().reset(); + Desktop::focusState()->window().reset(); + } + + m_events.destroy.emit(); +} + +eViewType CWindow::type() const { + return VIEW_TYPE_WINDOW; +} + +bool CWindow::visible() const { + return !m_hidden && ((m_isMapped && m_wlSurface && m_wlSurface->resource()) || (m_fadingOut && m_alpha->value() != 0.F)); +} + +std::optional CWindow::logicalBox() const { + return getFullWindowBoundingBox(); +} + +bool CWindow::desktopComponent() const { + return true; +} + +std::optional CWindow::surfaceLogicalBox() const { + return getWindowMainSurfaceBox(); +} + +SBoxExtents CWindow::getFullWindowExtents() const { + if (m_fadingOut) + return m_originalClosedExtents; + + const int BORDERSIZE = getRealBorderSize(); + + if (m_ruleApplicator->dimAround().valueOrDefault()) { + if (const auto PMONITOR = m_monitor.lock(); PMONITOR) + return {.topLeft = {m_realPosition->value().x - PMONITOR->m_position.x, m_realPosition->value().y - PMONITOR->m_position.y}, + .bottomRight = {PMONITOR->m_size.x - (m_realPosition->value().x - PMONITOR->m_position.x), + PMONITOR->m_size.y - (m_realPosition->value().y - PMONITOR->m_position.y)}}; + } + + SBoxExtents maxExtents = {.topLeft = {BORDERSIZE + 2, BORDERSIZE + 2}, .bottomRight = {BORDERSIZE + 2, BORDERSIZE + 2}}; + + const auto EXTENTS = g_pDecorationPositioner->getWindowDecorationExtents(m_self); + + maxExtents.topLeft.x = std::max(EXTENTS.topLeft.x, maxExtents.topLeft.x); + + maxExtents.topLeft.y = std::max(EXTENTS.topLeft.y, maxExtents.topLeft.y); + + maxExtents.bottomRight.x = std::max(EXTENTS.bottomRight.x, maxExtents.bottomRight.x); + + maxExtents.bottomRight.y = std::max(EXTENTS.bottomRight.y, maxExtents.bottomRight.y); + + if (m_wlSurface->exists() && !m_isX11 && m_popupHead) { + const auto& surfaceExtents = m_popupHead->popupTreeExtents(); + + maxExtents.topLeft.x = std::max(-surfaceExtents.x, maxExtents.topLeft.x); + + maxExtents.topLeft.y = std::max(-surfaceExtents.y, maxExtents.topLeft.y); + + if (surfaceExtents.x + surfaceExtents.width > m_wlSurface->resource()->m_current.size.x + maxExtents.bottomRight.x) + maxExtents.bottomRight.x = surfaceExtents.x + surfaceExtents.width - m_wlSurface->resource()->m_current.size.x; + + if (surfaceExtents.y + surfaceExtents.height > m_wlSurface->resource()->m_current.size.y + maxExtents.bottomRight.y) + maxExtents.bottomRight.y = surfaceExtents.y + surfaceExtents.height - m_wlSurface->resource()->m_current.size.y; + } + + return maxExtents; +} + +CBox CWindow::getFullWindowBoundingBox() const { + if (m_ruleApplicator->dimAround().valueOrDefault()) { + if (const auto PMONITOR = m_monitor.lock(); PMONITOR) + return {PMONITOR->m_position.x, PMONITOR->m_position.y, PMONITOR->m_size.x, PMONITOR->m_size.y}; + } + + auto maxExtents = getFullWindowExtents(); + + CBox finalBox = {m_realPosition->value().x - maxExtents.topLeft.x, m_realPosition->value().y - maxExtents.topLeft.y, + m_realSize->value().x + maxExtents.topLeft.x + maxExtents.bottomRight.x, m_realSize->value().y + maxExtents.topLeft.y + maxExtents.bottomRight.y}; + + return finalBox; +} + +CBox CWindow::getWindowIdealBoundingBoxIgnoreReserved() { + const auto PMONITOR = m_monitor.lock(); + + if (!PMONITOR || !m_workspace) + return {m_position, m_size}; + + auto POS = m_position; + auto SIZE = m_size; + + if (isFullscreen()) { + POS = PMONITOR->m_position; + SIZE = PMONITOR->m_size; + + 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 (!RESERVED.ok()) + return CBox{POS, SIZE}; + + if (DELTALESSTHAN(POS.x, WORKAREA.x, 1)) { + POS.x -= RESERVED.left(); + SIZE.x += RESERVED.left(); + } + + if (DELTALESSTHAN(POS.y, WORKAREA.y, 1)) { + POS.y -= RESERVED.top(); + SIZE.y += RESERVED.top(); + } + + if (DELTALESSTHAN(POS.x + SIZE.x, WORKAREA.x + WORKAREA.width, 1)) + SIZE.x += RESERVED.right(); + + if (DELTALESSTHAN(POS.y + SIZE.y, WORKAREA.y + WORKAREA.height, 1)) + SIZE.y += RESERVED.bottom(); + + return CBox{sc(POS.x), sc(POS.y), sc(SIZE.x), sc(SIZE.y)}; +} + +SBoxExtents CWindow::getWindowExtentsUnified(uint64_t properties) { + SBoxExtents extents = {.topLeft = {0, 0}, .bottomRight = {0, 0}}; + if (properties & Desktop::View::RESERVED_EXTENTS) + extents.addExtents(g_pDecorationPositioner->getWindowDecorationReserved(m_self)); + if (properties & Desktop::View::INPUT_EXTENTS) + extents.addExtents(g_pDecorationPositioner->getWindowDecorationExtents(m_self, true)); + if (properties & FULL_EXTENTS) + extents.addExtents(g_pDecorationPositioner->getWindowDecorationExtents(m_self, false)); + + return extents; +} + +CBox CWindow::getWindowBoxUnified(uint64_t properties) { + if (m_ruleApplicator->dimAround().valueOrDefault()) { + const auto PMONITOR = m_monitor.lock(); + if (PMONITOR) + return {PMONITOR->m_position.x, PMONITOR->m_position.y, PMONITOR->m_size.x, PMONITOR->m_size.y}; + } + + const auto POS = m_realPosition->value(); + const auto SIZE = m_realSize->value(); + + CBox box{POS, SIZE}; + box.addExtents(getWindowExtentsUnified(properties)); + + return box; +} + +SBoxExtents CWindow::getFullWindowReservedArea() { + return g_pDecorationPositioner->getWindowDecorationReserved(m_self); +} + +void CWindow::updateWindowDecos() { + + if (!m_isMapped || isHidden()) + return; + + for (auto const& wd : m_decosToRemove) { + for (auto it = m_windowDecorations.begin(); it != m_windowDecorations.end(); it++) { + if (it->get() == wd) { + g_pDecorationPositioner->uncacheDecoration(it->get()); + it = m_windowDecorations.erase(it); + if (it == m_windowDecorations.end()) + break; + } + } + } + + g_pDecorationPositioner->onWindowUpdate(m_self.lock()); + + m_decosToRemove.clear(); + + // make a copy because updateWindow can remove decos. + std::vector decos; + // reserve to avoid reallocations + decos.reserve(m_windowDecorations.size()); + + for (auto const& wd : m_windowDecorations) { + decos.push_back(wd.get()); + } + + for (auto const& wd : decos) { + if (std::ranges::find_if(m_windowDecorations, [wd](const auto& other) { return other.get() == wd; }) == m_windowDecorations.end()) + continue; + wd->updateWindow(m_self.lock()); + } +} + +void CWindow::addWindowDeco(UP deco) { + m_windowDecorations.emplace_back(std::move(deco)); + g_pDecorationPositioner->forceRecalcFor(m_self.lock()); + updateWindowDecos(); + + if (layoutTarget()) + layoutTarget()->recalc(); +} + +void CWindow::removeWindowDeco(IHyprWindowDecoration* deco) { + m_decosToRemove.push_back(deco); + g_pDecorationPositioner->forceRecalcFor(m_self.lock()); + updateWindowDecos(); + + if (layoutTarget()) + layoutTarget()->recalc(); +} + +void CWindow::uncacheWindowDecos() { + for (auto const& wd : m_windowDecorations) { + g_pDecorationPositioner->uncacheDecoration(wd.get()); + } +} + +bool CWindow::checkInputOnDecos(const eInputType type, const Vector2D& mouseCoords, std::any data) { + if (type != INPUT_TYPE_DRAG_END && hasPopupAt(mouseCoords)) + return false; + + for (auto const& wd : m_windowDecorations) { + if (!(wd->getDecorationFlags() & DECORATION_ALLOWS_MOUSE_INPUT)) + continue; + + if (!g_pDecorationPositioner->getWindowDecorationBox(wd.get()).containsPoint(mouseCoords)) + continue; + + if (wd->onInputOnDeco(type, mouseCoords, data)) + return true; + } + + return false; +} + +pid_t CWindow::getPID() { + pid_t PID = -1; + if (!m_isX11) { + if (!m_xdgSurface || !m_xdgSurface->m_owner /* happens at unmap */) + return -1; + + wl_client_get_credentials(m_xdgSurface->m_owner->client(), &PID, nullptr, nullptr); + } else { + if (!m_xwaylandSurface) + return -1; + + PID = m_xwaylandSurface->m_pid; + } + + return PID; +} + +IHyprWindowDecoration* CWindow::getDecorationByType(eDecorationType type) { + for (auto const& wd : m_windowDecorations) { + if (wd->getDecorationType() == type) + return wd.get(); + } + + return nullptr; +} + +void CWindow::updateToplevel() { + updateSurfaceScaleTransformDetails(); +} + +void CWindow::updateSurfaceScaleTransformDetails(bool force) { + if (!m_isMapped || m_hidden || g_pCompositor->m_unsafeState) + return; + + const auto PLASTMONITOR = g_pCompositor->getMonitorFromID(m_lastSurfaceMonitorID); + + m_lastSurfaceMonitorID = monitorID(); + + const auto PNEWMONITOR = m_monitor.lock(); + + if (!PNEWMONITOR) + return; + + if (PNEWMONITOR != PLASTMONITOR || force) { + if (PLASTMONITOR && PLASTMONITOR->m_enabled && PNEWMONITOR != PLASTMONITOR) + m_wlSurface->resource()->breadthfirst([PLASTMONITOR](SP s, const Vector2D& offset, void* d) { s->leave(PLASTMONITOR->m_self.lock()); }, nullptr); + + m_wlSurface->resource()->breadthfirst([PNEWMONITOR](SP s, const Vector2D& offset, void* d) { s->enter(PNEWMONITOR->m_self.lock()); }, nullptr); + } + + const auto PMONITOR = m_monitor.lock(); + + m_wlSurface->resource()->breadthfirst( + [PMONITOR](SP s, const Vector2D& offset, void* d) { + const auto PSURFACE = CWLSurface::fromResource(s); + if (PSURFACE && PSURFACE->m_lastScaleFloat == PMONITOR->m_scale) + return; + + PROTO::fractional->sendScale(s, PMONITOR->m_scale); + g_pCompositor->setPreferredScaleForSurface(s, PMONITOR->m_scale); + g_pCompositor->setPreferredTransformForSurface(s, PMONITOR->m_transform); + }, + nullptr); +} + +void CWindow::moveToWorkspace(PHLWORKSPACE pWorkspace) { + if (m_workspace == pWorkspace) + return; + + static auto PINITIALWSTRACKING = CConfigValue("misc:initial_workspace_tracking"); + + if (!m_initialWorkspaceToken.empty()) { + const auto TOKEN = g_pTokenManager->getToken(m_initialWorkspaceToken); + 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) { ; } + } + } + } + + static auto PCLOSEONLASTSPECIAL = CConfigValue("misc:close_special_on_empty"); + + const auto OLDWORKSPACE = m_workspace; + + if (OLDWORKSPACE->isVisible()) { + m_movingToWorkspaceAlpha->setValueAndWarp(1.F); + *m_movingToWorkspaceAlpha = 0.F; + m_movingToWorkspaceAlpha->setCallbackOnEnd([this](auto) { m_monitorMovedFrom = -1; }); + m_monitorMovedFrom = OLDWORKSPACE ? OLDWORKSPACE->monitorID() : -1; + } + + m_workspace = pWorkspace; + + setAnimationsToMove(); + + 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); + } + + if (const auto SWALLOWED = m_swallowed.lock()) { + if (SWALLOWED->m_currentlySwallowed) { + SWALLOWED->moveToWorkspace(pWorkspace); + SWALLOWED->m_monitor = m_monitor; + } + } + + if (OLDWORKSPACE && g_pCompositor->isWorkspaceSpecial(OLDWORKSPACE->m_id) && OLDWORKSPACE->getWindows() == 0 && *PCLOSEONLASTSPECIAL) { + if (const auto PMONITOR = OLDWORKSPACE->m_monitor.lock(); PMONITOR) + PMONITOR->setSpecialWorkspace(nullptr); + } +} + +PHLWINDOW CWindow::x11TransientFor() { + if (!m_xwaylandSurface || !m_xwaylandSurface->m_parent) + return nullptr; + + auto s = m_xwaylandSurface->m_parent; + std::vector> visited; + while (s) { + // break loops. Some X apps make them, and it seems like it's valid behavior?!?!?! + // TODO: we should reject loops being created in the first place. + if (std::ranges::find(visited.begin(), visited.end(), s) != visited.end()) + break; + + visited.emplace_back(s.lock()); + s = s->m_parent; + } + + if (s == m_xwaylandSurface) + return nullptr; // dead-ass circle + + for (auto const& w : g_pCompositor->m_windows) { + if (w->m_xwaylandSurface != s) + continue; + return w; + } + + return nullptr; +} + +void CWindow::onUnmap() { + static auto PCLOSEONLASTSPECIAL = CConfigValue("misc:close_special_on_empty"); + static auto PINITIALWSTRACKING = CConfigValue("misc:initial_workspace_tracking"); + + if (!m_initialWorkspaceToken.empty()) { + const auto TOKEN = g_pTokenManager->getToken(m_initialWorkspaceToken); + 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); } + } + } + } + + m_lastWorkspace = m_workspace->m_id; + + // if the special workspace now has 0 windows, it will be closed, and this + // window will no longer pass render checks, cuz the workspace will be nuked. + // throw it into the main one for the fadeout. + if (m_workspace->m_isSpecialWorkspace && m_workspace->getWindows() == 0) + m_lastWorkspace = m_monitor->activeWorkspaceID(); + + if (*PCLOSEONLASTSPECIAL && m_workspace && m_workspace->getWindows() == 0 && onSpecialWorkspace()) { + const auto PMONITOR = m_monitor.lock(); + if (PMONITOR && PMONITOR->m_activeSpecialWorkspace && PMONITOR->m_activeSpecialWorkspace == m_workspace) + PMONITOR->setSpecialWorkspace(nullptr); + } + + const auto PMONITOR = m_monitor.lock(); + + if (PMONITOR && PMONITOR->m_solitaryClient == m_self) + PMONITOR->m_solitaryClient.reset(); + + if (m_workspace) { + m_workspace->updateWindows(); + m_workspace->updateWindowData(); + } + + g_pCompositor->updateAllWindowsAnimatedDecorationValues(); + + m_workspace.reset(); + + if (m_isX11) + return; + + m_subsurfaceHead.reset(); + m_popupHead.reset(); +} + +void CWindow::onMap() { + // JIC, reset the callbacks. If any are set, we'll make sure they are cleared so we don't accidentally unset them. (In case a window got remapped) + m_realPosition->resetAllCallbacks(); + m_realSize->resetAllCallbacks(); + m_borderFadeAnimationProgress->resetAllCallbacks(); + m_borderAngleAnimationProgress->resetAllCallbacks(); + m_activeInactiveAlpha->resetAllCallbacks(); + m_alpha->resetAllCallbacks(); + m_realShadowColor->resetAllCallbacks(); + m_realGlowColor->resetAllCallbacks(); + m_dimPercent->resetAllCallbacks(); + m_movingToWorkspaceAlpha->resetAllCallbacks(); + m_movingFromWorkspaceAlpha->resetAllCallbacks(); + + m_movingFromWorkspaceAlpha->setValueAndWarp(1.F); + + if (m_borderAngleAnimationProgress->enabled()) { + m_borderAngleAnimationProgress->setValueAndWarp(0.f); + m_borderAngleAnimationProgress->setCallbackOnEnd([&](WP p) { onBorderAngleAnimEnd(p); }, false); + *m_borderAngleAnimationProgress = 1.f; + } + + m_realSize->setCallbackOnBegin( + [this](auto) { + if (!m_isMapped || isX11OverrideRedirect()) + return; + + g_pEventLoopManager->doLater([this, self = m_self] { + if (!self) + return; + + sendWindowSize(); + }); + }, + false); + + m_realSize->setUpdateCallback([this](auto) { + if (!m_isMapped) + return; + + updateWindowDecos(); + + m_events.resize.emit(); + }); + + m_realPosition->setUpdateCallback([this](auto) { + if (!m_isMapped) + return; + + updateWindowDecos(); + + if (m_monitor != m_prevMonitor) { + m_prevMonitor = m_monitor; + m_events.monitorChanged.emit(); + } + }); + + m_movingFromWorkspaceAlpha->setValueAndWarp(1.F); + + m_reportedSize = m_pendingReportedSize; + m_animatingIn = true; + + updateSurfaceScaleTransformDetails(true); + + if (m_isX11) + return; + + m_subsurfaceHead = CSubsurface::create(m_self.lock()); + m_popupHead = CPopup::create(m_self.lock()); +} + +void CWindow::onBorderAngleAnimEnd(WP pav) { + if (!pav) + return; + + if (pav->getStyle() != "loop" || !pav->enabled()) + return; + + const auto PANIMVAR = dc*>(pav.get()); + + PANIMVAR->setCallbackOnEnd(nullptr); // we remove the callback here because otherwise setvalueandwarp will recurse this + + PANIMVAR->setValueAndWarp(0); + *PANIMVAR = 1.f; + + PANIMVAR->setCallbackOnEnd([&](WP pav) { onBorderAngleAnimEnd(pav); }, false); +} + +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(); + + setSuspended(hidden); +} + +bool CWindow::isHidden() { + return m_hidden; +} + +// check if the point is "hidden" under a rounded corner of the window +// it is assumed that the point is within the real window box (m_vRealPosition, m_vRealSize) +// otherwise behaviour is undefined +bool CWindow::isInCurvedCorner(double x, double y) { + const int ROUNDING = rounding(); + const int ROUNDINGPOWER = roundingPower(); + if (getRealBorderSize() >= ROUNDING) + return false; + + // (x0, y0), (x0, y1), ... are the center point of rounding at each corner + double x0 = m_realPosition->value().x + ROUNDING; + double y0 = m_realPosition->value().y + ROUNDING; + double x1 = m_realPosition->value().x + m_realSize->value().x - ROUNDING; + double y1 = m_realPosition->value().y + m_realSize->value().y - ROUNDING; + + if (x < x0 && y < y0) { + return std::pow(x0 - x, ROUNDINGPOWER) + std::pow(y0 - y, ROUNDINGPOWER) > std::pow(sc(ROUNDING), ROUNDINGPOWER); + } + if (x > x1 && y < y0) { + return std::pow(x - x1, ROUNDINGPOWER) + std::pow(y0 - y, ROUNDINGPOWER) > std::pow(sc(ROUNDING), ROUNDINGPOWER); + } + if (x < x0 && y > y1) { + return std::pow(x0 - x, ROUNDINGPOWER) + std::pow(y - y1, ROUNDINGPOWER) > std::pow(sc(ROUNDING), ROUNDINGPOWER); + } + if (x > x1 && y > y1) { + return std::pow(x - x1, ROUNDINGPOWER) + std::pow(y - y1, ROUNDINGPOWER) > std::pow(sc(ROUNDING), ROUNDINGPOWER); + } + + return false; +} + +// checks if the wayland window has a popup at pos +bool CWindow::hasPopupAt(const Vector2D& pos) { + if (m_isX11 || !m_popupHead) + return false; + + if (m_popupHead->popupTreeCount() == 0) + return false; + + auto popup = m_popupHead->at(pos); + + return popup && popup->wlSurface()->resource(); +} + +Vector2D CWindow::middle() { + return m_realPosition->goal() + m_realSize->goal() / 2.f; +} + +bool CWindow::opaque() { + if (m_alpha->value() != 1.f || m_activeInactiveAlpha->value() != 1.f) + return false; + + const auto PWORKSPACE = m_workspace; + + if (m_wlSurface->small() && !m_wlSurface->m_fillIgnoreSmall) + return false; + + if (PWORKSPACE && PWORKSPACE->m_alpha->value() != 1.f) + return false; + + if (m_isX11 && m_xwaylandSurface && m_xwaylandSurface->m_surface && m_xwaylandSurface->m_surface->m_current.texture) + return m_xwaylandSurface->m_surface->m_current.texture->m_opaque; + + auto solitaryResource = getSolitaryResource(); + if (!solitaryResource || !solitaryResource->m_current.texture) + return false; + + // TODO: this is wrong + const auto EXTENTS = m_xdgSurface->m_surface->m_current.opaque.getExtents(); + if (EXTENTS.w >= m_xdgSurface->m_surface->m_current.bufferSize.x && EXTENTS.h >= m_xdgSurface->m_surface->m_current.bufferSize.y) + return true; + + return solitaryResource->m_current.texture->m_opaque; +} + +float CWindow::rounding() { + static auto PROUNDING = CConfigValue("decoration:rounding"); + static auto PROUNDINGPOWER = CConfigValue("decoration:rounding_power"); + + float roundingPower = m_ruleApplicator->roundingPower().valueOr(*PROUNDINGPOWER); + float rounding = m_ruleApplicator->rounding().valueOr(*PROUNDING) * (roundingPower / 2.0); /* Make perceived roundness consistent. */ + + return rounding; +} + +float CWindow::roundingPower() { + static auto PROUNDINGPOWER = CConfigValue("decoration:rounding_power"); + + return m_ruleApplicator->roundingPower().valueOr(std::clamp(*PROUNDINGPOWER, 1.F, 10.F)); +} + +void CWindow::updateWindowData() { + const auto PWORKSPACE = m_workspace; + const auto WORKSPACERULE = PWORKSPACE ? Config::workspaceRuleMgr()->getWorkspaceRuleFor(PWORKSPACE) : std::nullopt; + updateWindowData(WORKSPACERULE.value_or(Config::CWorkspaceRule{})); +} + +void CWindow::updateWindowData(const Config::CWorkspaceRule& workspaceRule) { + if (workspaceRule.m_noBorder.value_or(false)) + m_ruleApplicator->borderSize().matchOptional(std::optional(0), Desktop::Types::PRIORITY_WORKSPACE_RULE); + else if (workspaceRule.m_borderSize) + m_ruleApplicator->borderSize().matchOptional(workspaceRule.m_borderSize, Desktop::Types::PRIORITY_WORKSPACE_RULE); + else + m_ruleApplicator->borderSize().matchOptional(std::nullopt, Desktop::Types::PRIORITY_WORKSPACE_RULE); + m_ruleApplicator->decorate().matchOptional(workspaceRule.m_decorate, Desktop::Types::PRIORITY_WORKSPACE_RULE); + m_ruleApplicator->rounding().matchOptional(workspaceRule.m_noRounding.value_or(false) ? std::optional(0) : std::nullopt, + Desktop::Types::PRIORITY_WORKSPACE_RULE); + m_ruleApplicator->noShadow().matchOptional(workspaceRule.m_noShadow, Desktop::Types::PRIORITY_WORKSPACE_RULE); + + m_borderSizeCacheDirty = true; +} + +int CWindow::getRealBorderSize() const { + if (!m_borderSizeCacheDirty) + return m_cachedBorderSize; + + if ((m_workspace && isEffectiveInternalFSMode(FSMODE_FULLSCREEN)) || !m_ruleApplicator->decorate().valueOrDefault()) { + m_cachedBorderSize = 0; + m_borderSizeCacheDirty = false; + return 0; + } + + static auto PBORDERSIZE = CConfigValue("general:border_size"); + + m_cachedBorderSize = m_ruleApplicator->borderSize().valueOr(*PBORDERSIZE); + m_borderSizeCacheDirty = false; + return m_cachedBorderSize; +} + +float CWindow::getScrollMouse() { + static auto PINPUTSCROLLFACTOR = CConfigValue("input:scroll_factor"); + return m_ruleApplicator->scrollMouse().valueOr(*PINPUTSCROLLFACTOR); +} + +float CWindow::getScrollTouchpad() { + static auto PTOUCHPADSCROLLFACTOR = CConfigValue("input:touchpad:scroll_factor"); + return m_ruleApplicator->scrollTouchpad().valueOr(*PTOUCHPADSCROLLFACTOR); +} + +bool CWindow::isScrollMouseOverridden() { + return m_ruleApplicator->scrollMouse().hasValue(); +} + +bool CWindow::isScrollTouchpadOverridden() { + return m_ruleApplicator->scrollTouchpad().hasValue(); +} + +bool CWindow::canBeTorn() { + static auto PTEARING = CConfigValue("general:allow_tearing"); + return m_ruleApplicator->tearing().valueOr(m_tearingHint) && *PTEARING; +} + +void CWindow::setSuspended(bool suspend) { + if (suspend == m_suspended) + return; + + if (m_isX11 || !m_xdgSurface || !m_xdgSurface->m_toplevel) + return; + + m_xdgSurface->m_toplevel->setSuspeneded(suspend); + m_suspended = suspend; +} + +bool CWindow::visibleOnMonitor(PHLMONITOR pMonitor) { + CBox wbox = {m_realPosition->value(), m_realSize->value()}; + + if (m_isFloating) + wbox = getFullWindowBoundingBox(); + + return !wbox.intersection({pMonitor->m_position, pMonitor->m_size}).empty(); +} + +void CWindow::setAnimationsToMove() { + m_realPosition->setConfig(Config::animationTree()->getAnimationPropertyConfig("windowsMove")); + m_animatingIn = false; +} + +void CWindow::onWorkspaceAnimUpdate() { + // clip box for animated offsets + if (!m_isFloating || m_pinned || isFullscreen()) { + m_floatingOffset = Vector2D(0, 0); + return; + } + + Vector2D offset; + const auto PWORKSPACE = m_workspace; + if (!PWORKSPACE) + return; + + const auto PWSMON = m_monitor.lock(); + if (!PWSMON) + return; + + const auto WINBB = getFullWindowBoundingBox(); + if (PWORKSPACE->m_renderOffset->value().x != 0) { + const auto PROGRESS = PWORKSPACE->m_renderOffset->value().x / PWSMON->m_size.x; + + if (WINBB.x < PWSMON->m_position.x) + offset.x += (PWSMON->m_position.x - WINBB.x) * PROGRESS; + + if (WINBB.x + WINBB.width > PWSMON->m_position.x + PWSMON->m_size.x) + offset.x += (WINBB.x + WINBB.width - PWSMON->m_position.x - PWSMON->m_size.x) * PROGRESS; + } else if (PWORKSPACE->m_renderOffset->value().y != 0) { + const auto PROGRESS = PWORKSPACE->m_renderOffset->value().y / PWSMON->m_size.y; + + if (WINBB.y < PWSMON->m_position.y) + offset.y += (PWSMON->m_position.y - WINBB.y) * PROGRESS; + + if (WINBB.y + WINBB.height > PWSMON->m_position.y + PWSMON->m_size.y) + offset.y += (WINBB.y + WINBB.height - PWSMON->m_position.y - PWSMON->m_size.y) * PROGRESS; + } + + m_floatingOffset = offset; +} + +void CWindow::onFocusAnimUpdate() { + // borderangle once + if (m_borderAngleAnimationProgress->enabled() && !m_borderAngleAnimationProgress->isBeingAnimated()) { + m_borderAngleAnimationProgress->setValueAndWarp(0.f); + *m_borderAngleAnimationProgress = 1.f; + } +} + +int CWindow::popupsCount() { + if (m_isX11 || !m_popupHead) + return 0; + + return m_popupHead->popupTreeCount(); +} + +int CWindow::surfacesCount() { + if (m_isX11) + return 1; + + int no = 0; + m_wlSurface->resource()->breadthfirst([](SP r, const Vector2D& offset, void* d) { *sc(d) += 1; }, &no); + return no; +} + +bool CWindow::clampWindowSize(const std::optional minSize, const std::optional maxSize) { + const Vector2D REALSIZE = m_realSize->goal(); + const Vector2D MAX = isFullscreen() ? Vector2D{INFINITY, INFINITY} : maxSize.value_or(Vector2D{INFINITY, INFINITY}); + const Vector2D NEWSIZE = REALSIZE.clamp(minSize.value_or(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}), MAX); + const bool changed = !(NEWSIZE == REALSIZE); + + if (changed) { + const Vector2D DELTA = REALSIZE - NEWSIZE; + *m_realPosition = m_realPosition->goal() + DELTA / 2.0; + *m_realSize = NEWSIZE; + } + + return changed; +} + +bool CWindow::isFullscreen() { + return m_fullscreenState.internal != FSMODE_NONE; +} + +bool CWindow::isEffectiveInternalFSMode(const eFullscreenMode MODE) const { + return sc(std::bit_floor(sc(m_fullscreenState.internal))) == MODE; +} + +WORKSPACEID CWindow::workspaceID() { + return m_workspace ? m_workspace->m_id : m_lastWorkspace; +} + +MONITORID CWindow::monitorID() { + return m_monitor ? m_monitor->m_id : MONITOR_INVALID; +} + +bool CWindow::onSpecialWorkspace() { + return m_workspace ? m_workspace->m_isSpecialWorkspace : g_pCompositor->isWorkspaceSpecial(m_lastWorkspace); +} + +std::unordered_map CWindow::getEnv() { + + const auto PID = getPID(); + + if (PID <= 1) + return {}; + + std::unordered_map results; + + std::vector buffer; + size_t needle = 0; + +#if defined(__linux__) + // + std::string environFile = "/proc/" + std::to_string(PID) + "/environ"; + std::ifstream ifs(environFile, std::ios::binary); + + if (!ifs.good()) + return {}; + + buffer.resize(512, '\0'); + while (ifs.read(buffer.data() + needle, 512)) { + buffer.resize(buffer.size() + 512, '\0'); + needle += 512; + } +#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ENV, static_cast(PID)}; + size_t len = 0; + + if (sysctl(mib, 4, nullptr, &len, nullptr, 0) < 0 || len == 0) + return {}; + + buffer.resize(len, '\0'); + + if (sysctl(mib, 4, buffer.data(), &len, nullptr, 0) < 0) + return {}; + + needle = len; +#endif + + if (needle <= 1) + return {}; + + std::replace(buffer.begin(), buffer.end() - 1, '\0', '\n'); + + CVarList envs(std::string{buffer.data(), buffer.size() - 1}, 0, '\n', true); + + for (auto const& e : envs) { + if (!e.contains('=')) + continue; + + const auto EQ = e.find_first_of('='); + results[e.substr(0, EQ)] = e.substr(EQ + 1); + } + + return results; +} + +void CWindow::activate(bool force) { + if (Desktop::focusState()->window() == m_self) + return; + + static auto PFOCUSONACTIVATE = CConfigValue("misc:focus_on_activate"); + + 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()); + + if (!force && + (!m_ruleApplicator->focusOnActivate().valueOr(*PFOCUSONACTIVATE) || (m_suppressedEvents & SUPPRESS_ACTIVATE_FOCUSONLY) || (m_suppressedEvents & SUPPRESS_ACTIVATE))) + return; + + if (!m_isMapped) { + Log::logger->log(Log::DEBUG, "Ignoring CWindow::activate focus/warp, window is not mapped yet."); + return; + } + + if (m_isFloating) + g_pCompositor->changeWindowZOrder(m_self.lock(), true); + + Desktop::focusState()->fullWindowFocus(m_self.lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE); + warpCursor(); +} + +void CWindow::onUpdateState() { + std::optional requestsFS = m_xdgSurface ? m_xdgSurface->m_toplevel->m_state.requestsFullscreen : m_xwaylandSurface->m_state.requestsFullscreen; + std::optional requestsID = m_xdgSurface ? m_xdgSurface->m_toplevel->m_state.requestsFullscreenMonitor : MONITOR_INVALID; + std::optional requestsMX = m_xdgSurface ? m_xdgSurface->m_toplevel->m_state.requestsMaximize : m_xwaylandSurface->m_state.requestsMaximize; + + if (requestsFS.has_value() && !(m_suppressedEvents & SUPPRESS_FULLSCREEN)) { + if (requestsID.has_value() && (requestsID.value() != MONITOR_INVALID) && !(m_suppressedEvents & SUPPRESS_FULLSCREEN_OUTPUT)) { + if (m_isMapped) { + const auto monitor = g_pCompositor->getMonitorFromID(requestsID.value()); + g_pCompositor->moveWindowToWorkspaceSafe(m_self.lock(), monitor->m_activeWorkspace); + Desktop::focusState()->rawMonitorFocus(monitor); + } + + if (!m_isMapped) + m_wantsInitialFullscreenMonitor = requestsID.value(); + } + + bool fs = requestsFS.value(); + if (m_isMapped) + g_pCompositor->changeWindowFullscreenModeClient(m_self.lock(), FSMODE_FULLSCREEN, requestsFS.value()); + + if (!m_isMapped) + m_wantsInitialFullscreen = fs; + } + + if (requestsMX.has_value() && !(m_suppressedEvents & SUPPRESS_MAXIMIZE)) { + if (m_isMapped) { + auto window = m_self.lock(); + auto state = sc(window->m_fullscreenState.client); + bool maximized = (state & sc(FSMODE_MAXIMIZED)) != 0; + g_pCompositor->changeWindowFullscreenModeClient(window, FSMODE_MAXIMIZED, !maximized); + } + } +} + +void CWindow::onUpdateMeta() { + const auto NEWTITLE = fetchTitle(); + bool doUpdate = false; + + if (m_title != NEWTITLE) { + 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()); + + 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 + } + + Log::logger->log(Log::DEBUG, "Window {:x} set title to {}", rc(this), m_title); + doUpdate = true; + } + + const auto NEWCLASS = fetchClass(); + 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 + } + + Log::logger->log(Log::DEBUG, "Window {:x} set class to {}", rc(this), m_class); + doUpdate = true; + } + + if (doUpdate) { + m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_TITLE | Desktop::Rule::RULE_PROP_CLASS); + updateToplevel(); + } +} + +std::string CWindow::fetchTitle() { + if (!m_isX11) { + if (m_xdgSurface && m_xdgSurface->m_toplevel) + return m_xdgSurface->m_toplevel->m_state.title; + } else { + if (m_xwaylandSurface) + return m_xwaylandSurface->m_state.title; + } + + return ""; +} + +std::string CWindow::fetchClass() { + if (!m_isX11) { + if (m_xdgSurface && m_xdgSurface->m_toplevel) + return m_xdgSurface->m_toplevel->m_state.appid; + } else { + if (m_xwaylandSurface) + return m_xwaylandSurface->m_state.appid; + } + + return ""; +} + +void CWindow::onAck(uint32_t serial) { + const auto SERIAL = std::ranges::find_if(m_pendingSizeAcks | std::views::reverse, [serial](const auto& e) { return e.first <= serial; }); + + if (SERIAL == m_pendingSizeAcks.rend()) + return; + + m_pendingSizeAck = *SERIAL; + std::erase_if(m_pendingSizeAcks, [&](const auto& el) { return el.first <= SERIAL->first; }); + + if (m_isX11) + return; + + m_wlSurface->resource()->m_pending.ackedSize = m_pendingSizeAck->second; // apply pending size. We pinged, the window ponged. + m_wlSurface->resource()->m_pending.updated.bits.acked = true; + m_pendingSizeAck.reset(); +} + +void CWindow::onResourceChangeX11() { + if (m_xwaylandSurface->m_surface && !m_wlSurface->resource()) + m_wlSurface->assign(m_xwaylandSurface->m_surface.lock(), m_self.lock()); + else if (!m_xwaylandSurface->m_surface && m_wlSurface->resource()) + m_wlSurface->unassign(); + + // update metadata as well, + // could be first assoc and we need to catch the class + onUpdateMeta(); + + Log::logger->log(Log::DEBUG, "xwayland window {:x} -> association to {:x}", rc(m_xwaylandSurface.get()), rc(m_wlSurface->resource().get())); +} + +void CWindow::onX11ConfigureRequest(CBox box) { + + if (!m_xwaylandSurface->m_surface || !m_xwaylandSurface->m_mapped || !m_isMapped) { + m_xwaylandSurface->configure(box); + m_pendingReportedSize = box.size(); + m_reportedSize = box.size(); + m_reportedPosition = box.pos(); + updateX11SurfaceScale(); + return; + } + + g_pHyprRenderer->damageWindow(m_self.lock()); + + if (!m_isFloating || isFullscreen() || g_layoutManager->dragController()->target() == m_self) { + sendWindowSize(true); + g_pInputManager->refocus(); + g_pHyprRenderer->damageWindow(m_self.lock()); + return; + } + + if (box.size() > Vector2D{1, 1}) + setHidden(false); + else + setHidden(true); + + m_realPosition->setValueAndWarp(xwaylandPositionToReal(box.pos())); + m_realSize->setValueAndWarp(xwaylandSizeToReal(box.size())); + + m_position = m_realPosition->goal(); + m_size = m_realSize->goal(); + + if (m_pendingReportedSize != box.size() || m_reportedPosition != box.pos()) { + m_xwaylandSurface->configure(box); + m_reportedSize = box.size(); + m_pendingReportedSize = box.size(); + m_reportedPosition = box.pos(); + } + + updateX11SurfaceScale(); + updateWindowDecos(); + + 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; + } + + g_pCompositor->changeWindowZOrder(m_self.lock(), true); + + m_createdOverFullscreen = true; + + g_pHyprRenderer->damageWindow(m_self.lock()); +} + +void CWindow::warpCursor(bool force) { + static auto PERSISTENTWARPS = CConfigValue("cursor:persistent_warps"); + const auto coords = m_relativeCursorCoordsOnLastWarp; + m_relativeCursorCoordsOnLastWarp.x = -1; // reset m_vRelativeCursorCoordsOnLastWarp + + if (*PERSISTENTWARPS && coords.x > 0 && coords.y > 0 && coords < m_size) // don't warp cursor outside the window + g_pCompositor->warpCursorTo(m_position + coords, force); + else + g_pCompositor->warpCursorTo(middle(), force); +} + +PHLWINDOW CWindow::getSwallower() { + static auto PSWALLOWREGEX = CConfigValue("misc:swallow_regex"); + static auto PSWALLOWEXREGEX = CConfigValue("misc:swallow_exception_regex"); + static auto PSWALLOW = CConfigValue("misc:enable_swallow"); + + if (!*PSWALLOW || std::string{*PSWALLOWREGEX} == STRVAL_EMPTY || (*PSWALLOWREGEX).empty()) + return nullptr; + + // check parent + std::vector candidates; + pid_t currentPid = getPID(); + // walk up the tree until we find someone, 25 iterations max. + for (size_t i = 0; i < 25; ++i) { + currentPid = getPPIDof(currentPid); + + if (!currentPid) + break; + + for (auto const& w : g_pCompositor->m_windows) { + if (!w->m_isMapped || w->isHidden()) + continue; + + if (w->getPID() == currentPid) + candidates.push_back(w); + } + } + + if (!(*PSWALLOWREGEX).empty()) + std::erase_if(candidates, [&](const auto& other) { return !RE2::FullMatch(other->m_class, *PSWALLOWREGEX); }); + + if (candidates.empty()) + return nullptr; + + if (!(*PSWALLOWEXREGEX).empty()) + std::erase_if(candidates, [&](const auto& other) { return RE2::FullMatch(other->m_title, *PSWALLOWEXREGEX); }); + + if (candidates.empty()) + return nullptr; + + if (candidates.size() == 1) + return candidates[0]; + + // walk up the focus history and find the last focused + for (auto const& w : Desktop::History::windowTracker()->fullHistory() | std::views::reverse) { + if (!w) + continue; + + if (std::ranges::find(candidates.begin(), candidates.end(), w.lock()) != candidates.end()) + return w.lock(); + } + + // if none are found (??) then just return the first one + return candidates[0]; +} + +bool CWindow::isX11OverrideRedirect() { + return m_xwaylandSurface && m_xwaylandSurface->m_overrideRedirect; +} + +bool CWindow::isModal() { + return (m_xwaylandSurface && m_xwaylandSurface->m_modal); +} + +Vector2D CWindow::realToReportSize() { + if (!m_isX11) + return m_realSize->goal().clamp(Vector2D{0, 0}, Math::VECTOR2D_MAX); + + static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); + + const auto REPORTSIZE = m_realSize->goal().clamp(Vector2D{1, 1}, Math::VECTOR2D_MAX); + 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; +} + +Vector2D CWindow::realToReportPosition() { + if (!m_isX11) + return m_realPosition->goal(); + + return g_pXWaylandManager->waylandToXWaylandCoords(m_realPosition->goal(), m_monitor.lock()); +} + +Vector2D CWindow::xwaylandSizeToReal(Vector2D size) { + static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); + + const auto PMONITOR = m_monitor.lock(); + const auto SIZE = size.clamp(Vector2D{1, 1}, Math::VECTOR2D_MAX); + const auto SCALE = *PXWLFORCESCALEZERO && PMONITOR ? PMONITOR->m_scale : 1.0f; + + return SIZE / SCALE; +} + +Vector2D CWindow::xwaylandPositionToReal(Vector2D pos) { + return g_pXWaylandManager->xwaylandToWaylandCoords(pos, m_monitor.lock()); +} + +void CWindow::updateX11SurfaceScale() { + static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); + + m_X11SurfaceScaledBy = 1.0f; + if (m_isX11 && *PXWLFORCESCALEZERO) { + if (const auto PMONITOR = m_monitor.lock(); PMONITOR) + m_X11SurfaceScaledBy = PMONITOR->m_scale; + } +} + +void CWindow::sendWindowSize(bool force) { + const auto PMONITOR = m_monitor.lock(); + + Log::logger->log(Log::TRACE, "sendWindowSize: window:{:x},title:{} with real pos {}, real size {} (force: {})", rc(this), this->m_title, m_realPosition->goal(), + m_realSize->goal(), force); + + // TODO: this should be decoupled from setWindowSize IMO + const auto REPORTPOS = realToReportPosition(); + + const auto REPORTSIZE = realToReportSize(); + + if (!force && m_pendingReportedSize == REPORTSIZE && (m_reportedPosition == REPORTPOS || !m_isX11)) + return; + + m_reportedPosition = REPORTPOS; + m_pendingReportedSize = REPORTSIZE; + updateX11SurfaceScale(); + + if (m_isX11 && m_xwaylandSurface) + m_xwaylandSurface->configure({REPORTPOS, REPORTSIZE}); + else if (m_xdgSurface && m_xdgSurface->m_toplevel) + m_pendingSizeAcks.emplace_back(m_xdgSurface->m_toplevel->setSize(REPORTSIZE), REPORTSIZE.floor()); +} + +NContentType::eContentType CWindow::getContentType() { + if (!m_wlSurface || !m_wlSurface->resource() || !m_wlSurface->resource()->m_contentType.valid()) + return CONTENT_TYPE_NONE; + + return m_wlSurface->resource()->m_contentType->m_value; +} + +void CWindow::setContentType(NContentType::eContentType contentType) { + if (!m_wlSurface->resource()->m_contentType.valid()) + m_wlSurface->resource()->m_contentType = PROTO::contentType->getContentType(m_wlSurface->resource()); + // else disallow content type change if proto is used? + + Log::logger->log(Log::INFO, "ContentType for window {}", sc(contentType)); + m_wlSurface->resource()->m_contentType->m_value = contentType; +} + +void CWindow::deactivateGroupMembers() { + if (!m_group) + return; + for (const auto& w : m_group->windows()) { + if (w != m_self.lock()) { + // we don't want to deactivate unfocused xwayland windows + // because X is weird, keep the behavior for wayland windows + // also its not really needed for xwayland windows + // ref: #9760 #9294 + if (!w->m_isX11 && w->m_xdgSurface && w->m_xdgSurface->m_toplevel) + w->m_xdgSurface->m_toplevel->setActive(false); + } + } +} + +bool CWindow::isNotResponding() { + return g_pANRManager->isNotResponding(m_self.lock()); +} + +std::optional CWindow::xdgTag() { + if (!m_xdgSurface || !m_xdgSurface->m_toplevel) + return std::nullopt; + + return m_xdgSurface->m_toplevel->m_toplevelTag; +} + +std::optional CWindow::xdgDescription() { + if (!m_xdgSurface || !m_xdgSurface->m_toplevel) + return std::nullopt; + + return m_xdgSurface->m_toplevel->m_toplevelDescription; +} + +PHLWINDOW CWindow::parent() { + if (m_isX11) { + auto t = x11TransientFor(); + + // don't return a parent that's not mapped + if (!validMapped(t)) + return nullptr; + + return t; + } + + if (!m_xdgSurface || !m_xdgSurface->m_toplevel || !m_xdgSurface->m_toplevel->m_parent) + return nullptr; + + // don't return a parent that's not mapped + if (!m_xdgSurface->m_toplevel->m_parent->m_window || !validMapped(m_xdgSurface->m_toplevel->m_parent->m_window)) + return nullptr; + + return m_xdgSurface->m_toplevel->m_parent->m_window.lock(); +} + +bool CWindow::priorityFocus() { + return !m_isX11 && CAsyncDialogBox::isPriorityDialogBox(getPID()); +} + +SP CWindow::getSolitaryResource() { + if (!m_wlSurface || !m_wlSurface->resource()) + return nullptr; + + auto res = m_wlSurface->resource(); + if (m_isX11) + return res; + + if (popupsCount()) + return nullptr; + + if (res->m_subsurfaces.size() == 0) + return res; + + if (res->m_subsurfaces.size() >= 1) { + if (!res->hasVisibleSubsurface()) + return res; + + if (res->m_subsurfaces.size() == 1) { + if (res->m_subsurfaces[0].expired() || res->m_subsurfaces[0]->m_surface.expired()) + return nullptr; + auto surf = res->m_subsurfaces[0]->m_surface.lock(); + if (!surf || surf->m_subsurfaces.size() != 0 || surf->extends() != res->extends() || !surf->m_current.texture || !surf->m_current.texture->m_opaque) + return nullptr; + return surf; + } + } + + return nullptr; +} + +Vector2D CWindow::getReportedSize() { + if (m_isX11) + return m_reportedSize; + if (m_wlSurface && m_wlSurface->resource()) + return m_wlSurface->resource()->m_current.ackedSize; + return m_reportedSize; +} + +void CWindow::updateDecorationValues() { + m_borderSizeCacheDirty = true; + + static auto PACTIVECOL = CConfigValue("general:col.active_border"); + static auto PINACTIVECOL = CConfigValue("general:col.inactive_border"); + static auto PNOGROUPACTIVECOL = CConfigValue("general:col.nogroup_border_active"); + static auto PNOGROUPINACTIVECOL = CConfigValue("general:col.nogroup_border"); + static auto PGROUPACTIVECOL = CConfigValue("group:col.border_active"); + static auto PGROUPINACTIVECOL = CConfigValue("group:col.border_inactive"); + static auto PGROUPACTIVELOCKEDCOL = CConfigValue("group:col.border_locked_active"); + static auto PGROUPINACTIVELOCKEDCOL = CConfigValue("group:col.border_locked_inactive"); + static auto PINACTIVEALPHA = CConfigValue("decoration:inactive_opacity"); + static auto PACTIVEALPHA = CConfigValue("decoration:active_opacity"); + static auto PFULLSCREENALPHA = CConfigValue("decoration:fullscreen_opacity"); + static auto PSHADOWCOL = CConfigValue("decoration:shadow:color"); + static auto PSHADOWCOLINACTIVE = CConfigValue("decoration:shadow:color_inactive"); + static auto PGLOW = CConfigValue("decoration:glow:enabled"); + static auto PGLOWCOL = CConfigValue("decoration:glow:color"); + static auto PGLOWCOLINACTIVE = CConfigValue("decoration:glow:color_inactive"); + static auto PDIMSTRENGTH = CConfigValue("decoration:dim_strength"); + static auto PDIMENABLED = CConfigValue("decoration:dim_inactive"); + static auto PDIMMODAL = CConfigValue("decoration:dim_modal"); + + auto* const ACTIVECOL = sc((PACTIVECOL.ptr())->getData()); + auto* const INACTIVECOL = sc((PINACTIVECOL.ptr())->getData()); + auto* const NOGROUPACTIVECOL = sc((PNOGROUPACTIVECOL.ptr())->getData()); + auto* const NOGROUPINACTIVECOL = sc((PNOGROUPINACTIVECOL.ptr())->getData()); + auto* const GROUPACTIVECOL = sc((PGROUPACTIVECOL.ptr())->getData()); + auto* const GROUPINACTIVECOL = sc((PGROUPINACTIVECOL.ptr())->getData()); + auto* const GROUPACTIVELOCKEDCOL = sc((PGROUPACTIVELOCKEDCOL.ptr())->getData()); + auto* const GROUPINACTIVELOCKEDCOL = sc((PGROUPINACTIVELOCKEDCOL.ptr())->getData()); + + auto setBorderColor = [&](Config::CGradientValueData grad) -> void { + if (grad == m_realBorderColor) + return; + + m_realBorderColorPrevious = m_realBorderColor; + m_realBorderColor = grad; + m_borderFadeAnimationProgress->setValueAndWarp(0.f); + *m_borderFadeAnimationProgress = 1.f; + }; + + 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)); + + if (*PGLOW) + *m_realGlowColor = *PGLOWCOL; + } else { + const auto* const INACTIVECOLOR = !m_group ? (!(m_groupRules & GROUP_DENY) ? INACTIVECOL : NOGROUPINACTIVECOL) : (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); + setBorderColor(m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR)); + + if (*PGLOW) + *m_realGlowColor = *PGLOWCOLINACTIVE; + } + + // opacity + const auto PWORKSPACE = m_workspace; + if (isEffectiveInternalFSMode(FSMODE_FULLSCREEN)) { + *m_activeInactiveAlpha = m_ruleApplicator->alphaFullscreen().valueOrDefault().applyAlpha(*PFULLSCREENALPHA); + } else { + if (m_self == Desktop::focusState()->window()) + *m_activeInactiveAlpha = m_ruleApplicator->alpha().valueOrDefault().applyAlpha(*PACTIVEALPHA); + else + *m_activeInactiveAlpha = m_ruleApplicator->alphaInactive().valueOrDefault().applyAlpha(*PINACTIVEALPHA); + } + + // dim + float goalDim = 1.F; + if (m_self == Desktop::focusState()->window() || m_ruleApplicator->noDim().valueOrDefault() || !*PDIMENABLED) + goalDim = 0; + else + goalDim = *PDIMSTRENGTH; + + if (IS_SHADOWED_BY_MODAL && *PDIMMODAL) + goalDim += (1.F - goalDim) / 2.F; + + *m_dimPercent = goalDim; + + // shadow + if (!isX11OverrideRedirect() && !m_X11DoesntWantBorders) { + if (m_self == Desktop::focusState()->window()) + *m_realShadowColor = CHyprColor(*PSHADOWCOL); + else + *m_realShadowColor = CHyprColor(*PSHADOWCOLINACTIVE != -1 ? *PSHADOWCOLINACTIVE : *PSHADOWCOL); + } else + m_realShadowColor->setValueAndWarp(CHyprColor(0, 0, 0, 0)); // no shadow + + updateWindowDecos(); +} + +std::optional CWindow::calculateSingleExpr(const std::string& s) { + const auto PMONITOR = m_monitor ? m_monitor : Desktop::focusState()->monitor(); + const auto CURSOR_LOCAL = g_pInputManager->getMouseCoordsInternal() - (PMONITOR ? PMONITOR->m_position : Vector2D{}); + + Math::CExpression expr; + expr.addVariable("window_w", m_realSize->goal().x); + expr.addVariable("window_h", m_realSize->goal().y); + expr.addVariable("window_x", m_realPosition->goal().x - (PMONITOR ? PMONITOR->m_position.x : 0)); + expr.addVariable("window_y", m_realPosition->goal().y - (PMONITOR ? PMONITOR->m_position.y : 0)); + + expr.addVariable("monitor_w", PMONITOR ? PMONITOR->m_size.x : 1920); + expr.addVariable("monitor_h", PMONITOR ? PMONITOR->m_size.y : 1080); + + expr.addVariable("cursor_x", CURSOR_LOCAL.x); + expr.addVariable("cursor_y", CURSOR_LOCAL.y); + + return expr.compute(s); +} + +std::optional CWindow::calculateExpression(const std::string& s) { + auto spacePos = s.find(' '); + if (spacePos == std::string::npos) + return std::nullopt; + + const auto LHS = calculateSingleExpr(s.substr(0, spacePos)); + const auto RHS = calculateSingleExpr(s.substr(spacePos + 1)); + + if (!LHS || !RHS) + return std::nullopt; + + return Vector2D{*LHS, *RHS}; +} + +static void setVector2DAnimToMove(WP pav) { + if (!pav) + return; + + CAnimatedVariable* animvar = dc*>(pav.get()); + animvar->setConfig(Config::animationTree()->getAnimationPropertyConfig("windowsMove")); + + if (animvar->m_Context.pWindow) + animvar->m_Context.pWindow->m_animatingIn = false; +} + +void CWindow::mapWindow() { + static auto PINACTIVEALPHA = CConfigValue("decoration:inactive_opacity"); + static auto PACTIVEALPHA = CConfigValue("decoration:active_opacity"); + 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; + const auto LAST_FS_MODE = LAST_FOCUS_WINDOW ? LAST_FOCUS_WINDOW->m_fullscreenState.internal : FSMODE_NONE; + + auto PMONITOR = Desktop::focusState()->monitor(); + if (!Desktop::focusState()->monitor()) { + Desktop::focusState()->rawMonitorFocus(g_pCompositor->getMonitorFromVector({})); + PMONITOR = Desktop::focusState()->monitor(); + } + auto PWORKSPACE = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace; + m_monitor = PMONITOR; + m_workspace = PWORKSPACE; + m_isMapped = true; + m_readyToDelete = false; + m_fadingOut = false; + m_title = fetchTitle(); + m_firstMap = true; + m_initialTitle = m_title; + m_initialClass = fetchClass(); + + // check for token + std::string requestedWorkspace = ""; + bool workspaceSilent = false; + + if (*PINITIALWSTRACKING) { + const auto WINDOWENV = getEnv(); + if (WINDOWENV.contains("HL_INITIAL_WORKSPACE_TOKEN")) { + const auto SZTOKEN = WINDOWENV.at("HL_INITIAL_WORKSPACE_TOKEN"); + Log::logger->log(Log::DEBUG, "New window contains HL_INITIAL_WORKSPACE_TOKEN: {}", SZTOKEN); + const auto TOKEN = g_pTokenManager->getToken(SZTOKEN); + if (TOKEN) { + // find workspace and use it + Desktop::View::SInitialWorkspaceToken WS = std::any_cast(TOKEN->m_data); + + Log::logger->log(Log::DEBUG, "HL_INITIAL_WORKSPACE_TOKEN {} -> {}", SZTOKEN, WS.workspace); + + if (g_pCompositor->getWorkspaceByString(WS.workspace) != m_workspace) { + requestedWorkspace = WS.workspace; + workspaceSilent = true; + } + + if (*PINITIALWSTRACKING == 1) // one-shot token + g_pTokenManager->removeToken(TOKEN); + else if (*PINITIALWSTRACKING == 2) { // persistent + if (WS.primaryOwner.expired()) { + WS.primaryOwner = m_self.lock(); + TOKEN->m_data = WS; + } + + m_initialWorkspaceToken = SZTOKEN; + } + } + } + } + + if (g_pInputManager->m_lastFocusOnLS) // waybar fix + g_pInputManager->releaseAllMouseButtons(); + + // checks if the window wants borders and sets the appropriate flag + g_pXWaylandManager->checkBorders(m_self.lock()); + + // registers the animated vars and stuff + onMap(); + + if (g_pXWaylandManager->shouldBeFloated(m_self.lock())) { + m_isFloating = true; + m_requestsFloat = true; + } + + m_X11ShouldntFocus = m_X11ShouldntFocus || (m_isX11 && isX11OverrideRedirect() && !m_xwaylandSurface->wantsFocus()); + + // window rules + std::optional requestedInternalFSMode, requestedClientFSMode; + std::optional requestedFSState; + if (m_wantsInitialFullscreen || (m_isX11 && m_xwaylandSurface->m_fullscreen)) + requestedClientFSMode = FSMODE_FULLSCREEN; + MONITORID requestedFSMonitor = m_wantsInitialFullscreenMonitor; + + auto setStaticProps = [&]() { + if (!m_ruleApplicator->static_.monitor.empty()) { + const auto& MONITORSTR = m_ruleApplicator->static_.monitor; + if (MONITORSTR == "unset") + m_monitor = PMONITOR; + else { + const auto MONITOR = g_pCompositor->getMonitorFromString(MONITORSTR); + + if (MONITOR) { + m_monitor = MONITOR; + + const auto PMONITORFROMID = m_monitor.lock(); + + if (m_monitor != PMONITOR) { + g_pKeybindManager->m_dispatchers["focusmonitor"](std::to_string(monitorID())); + PMONITOR = PMONITORFROMID; + } + m_workspace = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace; + PWORKSPACE = m_workspace; + + Log::logger->log(Log::DEBUG, "Rule monitor, applying to {:mw}", m_self.lock()); + requestedFSMonitor = MONITOR_INVALID; + } else + Log::logger->log(Log::ERR, "No monitor in monitor {} rule", MONITORSTR); + } + } + + if (!m_ruleApplicator->static_.workspace.empty()) { + const auto WORKSPACERQ = m_ruleApplicator->static_.workspace; + + if (WORKSPACERQ == "unset") + requestedWorkspace = ""; + else + requestedWorkspace = WORKSPACERQ; + + const auto JUSTWORKSPACE = WORKSPACERQ.contains(' ') ? WORKSPACERQ.substr(0, WORKSPACERQ.find_first_of(' ')) : WORKSPACERQ; + + if (JUSTWORKSPACE == PWORKSPACE->m_name || JUSTWORKSPACE == "name:" + PWORKSPACE->m_name) + requestedWorkspace = ""; + + Log::logger->log(Log::DEBUG, "Rule workspace matched by {}, {} applied.", m_self.lock(), m_ruleApplicator->static_.workspace); + 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_noInitialFocus = m_ruleApplicator->static_.noInitialFocus.value_or(m_noInitialFocus); + m_pinned = m_ruleApplicator->static_.pin.value_or(m_pinned); + + if (m_ruleApplicator->static_.fullscreenStateClient || m_ruleApplicator->static_.fullscreenStateInternal) { + requestedFSState = Desktop::View::SFullscreenState{ + .internal = sc(m_ruleApplicator->static_.fullscreenStateInternal.value_or(0)), + .client = sc(m_ruleApplicator->static_.fullscreenStateClient.value_or(0)), + }; + } + + if (!m_ruleApplicator->static_.suppressEvent.empty()) { + for (const auto& var : m_ruleApplicator->static_.suppressEvent) { + if (var == "fullscreen") + m_suppressedEvents |= Desktop::View::SUPPRESS_FULLSCREEN; + else if (var == "maximize") + m_suppressedEvents |= Desktop::View::SUPPRESS_MAXIMIZE; + else if (var == "activate") + m_suppressedEvents |= Desktop::View::SUPPRESS_ACTIVATE; + else if (var == "activatefocus") + m_suppressedEvents |= Desktop::View::SUPPRESS_ACTIVATE_FOCUSONLY; + else if (var == "fullscreenoutput") + m_suppressedEvents |= Desktop::View::SUPPRESS_FULLSCREEN_OUTPUT; + else + Log::logger->log(Log::ERR, "Error while parsing suppressevent windowrule: unknown event type {}", var); + } + } + + if (m_ruleApplicator->static_.fullscreen.value_or(false)) + requestedInternalFSMode = FSMODE_FULLSCREEN; + + if (m_ruleApplicator->static_.maximize.value_or(false)) + requestedInternalFSMode = FSMODE_MAXIMIZED; + + if (!m_ruleApplicator->static_.group.empty()) { + if (!(m_groupRules & Desktop::View::GROUP_OVERRIDE) && trim(m_ruleApplicator->static_.group) != "group") { + CVarList2 vars(std::string{m_ruleApplicator->static_.group}, 0, 's'); + std::string vPrev = ""; + + for (auto const& v : vars) { + if (v == "group") + continue; + + if (v == "set") { + m_groupRules |= Desktop::View::GROUP_SET; + } else if (v == "new") { + // shorthand for `group barred set` + m_groupRules |= (Desktop::View::GROUP_SET | Desktop::View::GROUP_BARRED); + } else if (v == "lock") { + m_groupRules |= Desktop::View::GROUP_LOCK; + } else if (v == "invade") { + m_groupRules |= Desktop::View::GROUP_INVADE; + } else if (v == "barred") { + m_groupRules |= Desktop::View::GROUP_BARRED; + } else if (v == "deny") { + m_groupRules |= Desktop::View::GROUP_DENY; + } else if (v == "override") { + // Clear existing rules + m_groupRules = Desktop::View::GROUP_OVERRIDE; + } else if (v == "unset") { + // Clear existing rules and stop processing + m_groupRules = Desktop::View::GROUP_OVERRIDE; + break; + } else if (v == "always") { + if (vPrev == "set" || vPrev == "group") + m_groupRules |= Desktop::View::GROUP_SET_ALWAYS; + else if (vPrev == "lock") + m_groupRules |= Desktop::View::GROUP_LOCK_ALWAYS; + else + Log::logger->log(Log::ERR, "windowrule `group` does not support `{} always`", vPrev); + } + vPrev = v; + } + } + } + + if (m_ruleApplicator->static_.content) + setContentType(sc(m_ruleApplicator->static_.content.value())); + + if (m_ruleApplicator->static_.noCloseFor) + m_closeableSince = Time::steadyNow() + std::chrono::milliseconds(m_ruleApplicator->static_.noCloseFor.value()); + }; + + const bool recheck = m_ruleApplicator->readStaticRules(); + setStaticProps(); + if (recheck) { + m_ruleApplicator->recheckStaticRules(); + setStaticProps(); + } + + // make it uncloseable if it's a Hyprland dialog + // TODO: make some closeable? + if (CAsyncDialogBox::isAsyncDialogBox(getPID())) + m_closeableSince = Time::steadyNow() + std::chrono::years(10 /* Should be enough, no? */); + + // disallow tiled pinned + if (m_pinned && !m_isFloating) + m_pinned = false; + + CVarList2 WORKSPACEARGS = CVarList2(std::move(requestedWorkspace), 0, ' ', false, false); + + if (!WORKSPACEARGS[0].empty()) { + WORKSPACEID requestedWorkspaceID; + std::string requestedWorkspaceName; + if (WORKSPACEARGS.contains("silent")) + workspaceSilent = true; + + auto joined = WORKSPACEARGS.join(" ", 0, workspaceSilent ? WORKSPACEARGS.size() - 1 : 0); + if (joined.starts_with("empty") && PWORKSPACE->getWindows() == 0) { + requestedWorkspaceID = PWORKSPACE->m_id; + requestedWorkspaceName = PWORKSPACE->m_name; + } else { + auto result = getWorkspaceIDNameFromString(joined); + requestedWorkspaceID = result.id; + requestedWorkspaceName = result.name; + } + + if (requestedWorkspaceID != WORKSPACE_INVALID) { + auto pWorkspace = g_pCompositor->getWorkspaceByID(requestedWorkspaceID); + + if (!pWorkspace) + pWorkspace = g_pCompositor->createNewWorkspace(requestedWorkspaceID, monitorID(), requestedWorkspaceName, false); + + PWORKSPACE = pWorkspace; + + m_workspace = pWorkspace; + m_monitor = pWorkspace->m_monitor; + + if (m_monitor.lock()->m_activeSpecialWorkspace && !pWorkspace->m_isSpecialWorkspace) + workspaceSilent = true; + + if (!workspaceSilent) { + if (pWorkspace->m_isSpecialWorkspace) + pWorkspace->m_monitor->setSpecialWorkspace(pWorkspace); + else if (PMONITOR->activeWorkspaceID() != requestedWorkspaceID && !m_noInitialFocus) + g_pKeybindManager->m_dispatchers["workspace"](requestedWorkspaceName); + + PMONITOR = Desktop::focusState()->monitor(); + } + + requestedFSMonitor = MONITOR_INVALID; + } else + workspaceSilent = false; + } + + if (m_suppressedEvents & Desktop::View::SUPPRESS_FULLSCREEN_OUTPUT) + requestedFSMonitor = MONITOR_INVALID; + else if (requestedFSMonitor != MONITOR_INVALID) { + if (const auto PM = g_pCompositor->getMonitorFromID(requestedFSMonitor); PM) + m_monitor = PM; + + const auto PMONITORFROMID = m_monitor.lock(); + + if (m_monitor != PMONITOR) { + g_pKeybindManager->m_dispatchers["focusmonitor"](std::to_string(monitorID())); + PMONITOR = PMONITORFROMID; + } + m_workspace = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace; + PWORKSPACE = m_workspace; + + Log::logger->log(Log::DEBUG, "Requested monitor, applying to {:mw}", m_self.lock()); + } + + if (PWORKSPACE->m_defaultFloating) + m_isFloating = true; + + if (PWORKSPACE->m_defaultPseudo) { + CBox desiredGeometry = g_pXWaylandManager->getGeometryForWindow(m_self.lock()); + m_target->setPseudoSize(Vector2D{desiredGeometry.width, desiredGeometry.height}); + m_target->setPseudo(true); + } + + // Verify window swallowing. Get the swallower before calling onWindowCreated(m_self.lock()) because getSwallower() wouldn't get it after if m_self.lock() gets auto grouped. + const auto SWALLOWER = getSwallower(); + m_swallowed = SWALLOWER; + if (m_swallowed) + m_swallowed->m_currentlySwallowed = true; + + // 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}); + + updateWindowData(); + + if (m_isFloating) { + m_createdOverFullscreen = true; + + // set the pseudo size to the GOAL of our current size + // because the windows are animated on RealSize + m_target->setPseudoSize(m_realSize->goal()); + + g_pCompositor->changeWindowZOrder(m_self.lock(), true); + } else { + bool setPseudo = false; + + 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 { + setPseudo = true; + m_target->setPseudoSize(*COMPUTED); + setHidden(false); + } + } + + if (!setPseudo) + m_target->setPseudoSize(m_realSize->goal() - Vector2D(10, 10)); + } + + const auto PFOCUSEDWINDOWPREV = Desktop::focusState()->window(); + + if (m_ruleApplicator->allowsInput().valueOrDefault()) { // if default value wasn't set to false getPriority() would throw an exception + m_ruleApplicator->noFocusOverride(Desktop::Types::COverridableVar(false, m_ruleApplicator->allowsInput().getPriority())); + m_noInitialFocus = false; + m_X11ShouldntFocus = false; + } + + // check LS focus grab + const auto PFORCEFOCUS = g_pCompositor->getForceFocus(); + const auto PLSFROMFOCUS = g_pCompositor->getLayerSurfaceFromSurface(Desktop::focusState()->surface()); + if (PLSFROMFOCUS && PLSFROMFOCUS->m_layerSurface->m_current.interactivity != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE) + m_noInitialFocus = true; + + if (m_workspace->m_hasFullscreenWindow && !requestedInternalFSMode.has_value() && !requestedClientFSMode.has_value() && !m_isFloating) { + if (*PNEWTAKESOVERFS == 0) + m_noInitialFocus = true; + else if (*PNEWTAKESOVERFS == 1) + requestedInternalFSMode = m_workspace->m_fullscreenMode; + else if (*PNEWTAKESOVERFS == 2) + g_pCompositor->setWindowFullscreenInternal(m_workspace->getFullscreenWindow(), FSMODE_NONE); + } + + if (!m_ruleApplicator->noFocus().valueOrDefault() && !m_noInitialFocus && (!isX11OverrideRedirect() || (m_isX11 && m_xwaylandSurface->wantsFocus())) && !workspaceSilent && + (!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); + + if (IS_LAST_IN_FS && SAME_GROUP) { + Desktop::focusState()->rawWindowFocus(m_self.lock(), FOCUS_REASON_NEW_WINDOW); + g_pCompositor->setWindowFullscreenInternal(m_self.lock(), LAST_FS_MODE); + } else + Desktop::focusState()->fullWindowFocus(m_self.lock(), FOCUS_REASON_NEW_WINDOW); + + m_activeInactiveAlpha->setValueAndWarp(*PACTIVEALPHA); + m_dimPercent->setValueAndWarp(m_ruleApplicator->noDim().valueOrDefault() ? 0.f : *PDIMSTRENGTH); + } else { + m_activeInactiveAlpha->setValueAndWarp(*PINACTIVEALPHA); + m_dimPercent->setValueAndWarp(0); + } + + if (requestedClientFSMode.has_value() && (m_suppressedEvents & Desktop::View::SUPPRESS_FULLSCREEN)) + requestedClientFSMode = sc(sc(requestedClientFSMode.value_or(FSMODE_NONE)) & ~sc(FSMODE_FULLSCREEN)); + if (requestedClientFSMode.has_value() && (m_suppressedEvents & Desktop::View::SUPPRESS_MAXIMIZE)) + requestedClientFSMode = sc(sc(requestedClientFSMode.value_or(FSMODE_NONE)) & ~sc(FSMODE_MAXIMIZED)); + + if (!m_noInitialFocus && (requestedInternalFSMode.has_value() || requestedClientFSMode.has_value() || requestedFSState.has_value())) { + // fix fullscreen on requested (basically do a switcheroo) + if (m_workspace->m_hasFullscreenWindow) + g_pCompositor->setWindowFullscreenInternal(m_workspace->getFullscreenWindow(), FSMODE_NONE); + + m_realPosition->warp(); + m_realSize->warp(); + if (requestedFSState.has_value()) { + m_ruleApplicator->syncFullscreenOverride(Desktop::Types::COverridableVar(false, Desktop::Types::PRIORITY_WINDOW_RULE)); + g_pCompositor->setWindowFullscreenState(m_self.lock(), requestedFSState.value()); + } else if (requestedInternalFSMode.has_value() && requestedClientFSMode.has_value() && !m_ruleApplicator->syncFullscreen().valueOrDefault()) + g_pCompositor->setWindowFullscreenState(m_self.lock(), + Desktop::View::SFullscreenState{.internal = requestedInternalFSMode.value(), .client = requestedClientFSMode.value()}); + else if (requestedInternalFSMode.has_value()) + g_pCompositor->setWindowFullscreenInternal(m_self.lock(), requestedInternalFSMode.value()); + else if (requestedClientFSMode.has_value()) + g_pCompositor->setWindowFullscreenClient(m_self.lock(), requestedClientFSMode.value()); + } + + // recheck idle inhibitors + g_pInputManager->recheckIdleInhibitorStatus(); + + updateToplevel(); + m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ALL); + + if (workspaceSilent) { + if (validMapped(PFOCUSEDWINDOWPREV)) { + Desktop::focusState()->rawWindowFocus(PFOCUSEDWINDOWPREV, FOCUS_REASON_NEW_WINDOW); + PFOCUSEDWINDOWPREV->updateWindowDecos(); // need to for some reason i cba to find out why + } else if (!PFOCUSEDWINDOWPREV) + Desktop::focusState()->rawWindowFocus(nullptr, FOCUS_REASON_NEW_WINDOW); + } + + // swallow + if (SWALLOWER) { + g_layoutManager->removeTarget(SWALLOWER->layoutTarget()); + SWALLOWER->setHidden(true); + } + + m_firstMap = false; + + 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()); + + // apply data from default decos. Borders, shadows. + g_pDecorationPositioner->forceRecalcFor(m_self.lock()); + updateWindowDecos(); + layoutTarget()->recalc(); + + // do animations + g_pDesktopAnimationManager->startAnimation(m_self.lock(), CDesktopAnimationManager::ANIMATION_TYPE_IN); + + m_realPosition->setCallbackOnEnd(setVector2DAnimToMove); + m_realSize->setCallbackOnEnd(setVector2DAnimToMove); + + // recalc the values for this window + updateDecorationValues(); + // avoid this window being visible + if (PWORKSPACE->m_hasFullscreenWindow && !isFullscreen() && !m_isFloating) + m_alpha->setValueAndWarp(0.f); + + g_pCompositor->setPreferredScaleForSurface(wlSurface()->resource(), PMONITOR->m_scale); + g_pCompositor->setPreferredTransformForSurface(wlSurface()->resource(), PMONITOR->m_transform); + + if (g_pSeatManager->m_mouse.expired() || !g_pInputManager->isConstrained()) + g_pInputManager->sendMotionEventsToFocused(); + + // fix some xwayland apps that don't behave nicely + m_reportedSize = m_pendingReportedSize; + + if (m_workspace) + m_workspace->updateWindows(); + + if (PMONITOR && isX11OverrideRedirect()) { + static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); + if (*PXWLFORCESCALEZERO) + m_X11SurfaceScaledBy = PMONITOR->m_scale; + } +} + +void CWindow::unmapWindow() { + Log::logger->log(Log::DEBUG, "{:c} unmapped", m_self.lock()); + + static auto PEXITRETAINSFS = CConfigValue("misc:exit_window_retains_fullscreen"); + + const auto CURRENTWINDOWFSSTATE = isFullscreen(); + const auto CURRENTFSMODE = m_fullscreenState.internal; + + if (!wlSurface()->exists() || !m_isMapped) { + Log::logger->log(Log::WARN, "{} unmapped without being mapped??", m_self.lock()); + m_fadingOut = false; + return; + } + + const auto PMONITOR = m_monitor.lock(); + if (PMONITOR) { + m_originalClosedPos = m_realPosition->value() - PMONITOR->m_position; + m_originalClosedSize = m_realSize->value(); + 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()); + + 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); + Desktop::floatState()->remember(m_self.lock(), m_realSize->value()); + } + + if (isFullscreen()) + g_pCompositor->setWindowFullscreenInternal(m_self.lock(), FSMODE_NONE); + + // Allow the renderer to catch the last frame. + if (g_pHyprRenderer->shouldRenderWindow(m_self.lock())) + g_pHyprRenderer->makeSnapshot(m_self.lock()); + + // swallowing + if (valid(m_swallowed)) { + if (m_swallowed->m_currentlySwallowed) { + m_swallowed->m_currentlySwallowed = false; + m_swallowed->setHidden(false); + + if (m_group) + m_swallowed->m_groupSwallowed = true; // flag for the swallowed window to be created into the group where it belongs when auto_group = false. + + g_layoutManager->newTarget(m_swallowed->layoutTarget(), m_workspace->m_space); + } + + m_swallowed->m_groupSwallowed = false; + m_swallowed.reset(); + } + + 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; + }(); + + if (m_self.lock() == Desktop::focusState()->window()) { + wasLastWindow = true; + Desktop::focusState()->resetWindowFocus(); + + g_pInputManager->releaseAllMouseButtons(); + } + + if (m_self.lock() == g_layoutManager->dragController()->target()) + CKeybindManager::changeMouseBindMode(MBIND_INVALID); + + // remove the fullscreen window status from workspace if we closed it + const auto PWORKSPACE = m_workspace; + + if (PWORKSPACE->m_hasFullscreenWindow && isFullscreen()) + PWORKSPACE->m_hasFullscreenWindow = false; + + if (m_group) + m_group->remove(m_self.lock()); + + g_layoutManager->removeTarget(m_target); + + g_pHyprRenderer->damageWindow(m_self.lock()); + + // do this after onWindowRemoved because otherwise it'll think the window is invalid + m_isMapped = false; + + // refocus on a new window if needed + if (wasLastWindow) { + static auto FOCUSONCLOSE = CConfigValue("input:focus_on_close"); + PHLWINDOW candidate = nextInGroup; + + if (!candidate) { + if (*FOCUSONCLOSE == 1) + 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(); + } + } + + 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); + else + Desktop::focusState()->fullWindowFocus(candidate, FOCUS_REASON_DESKTOP_STATE_CHANGE); + + if ((*PEXITRETAINSFS || candidate == nextInGroup) && CURRENTWINDOWFSSTATE) + g_pCompositor->setWindowFullscreenInternal(candidate, CURRENTFSMODE); + } + + if (!candidate && m_workspace && m_workspace->getWindows() == 0) + g_pInputManager->refocus(); + + g_pInputManager->sendMotionEventsToFocused(); + + // CWindow::onUnmap will remove this window's active status, but we can't really do it above. + 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); + } + } else { + Log::logger->log(Log::DEBUG, "Unmapped was not focused, ignoring a refocus."); + } + + m_fadingOut = true; + + g_pCompositor->addToFadingOutSafe(m_self.lock()); + + if (!m_X11DoesntWantBorders) // don't animate out if they weren't animated in. + *m_realPosition = m_realPosition->value() + Vector2D(0.01f, 0.01f); // it has to be animated, otherwise CesktopAnimationManager will ignore it + + // anims + g_pDesktopAnimationManager->startAnimation(m_self.lock(), CDesktopAnimationManager::ANIMATION_TYPE_OUT); + + // recheck idle inhibitors + g_pInputManager->recheckIdleInhibitorStatus(); + + // force report all sizes (QT sometimes has an issue with this) + if (m_workspace) + m_workspace->forceReportSizesToWindows(); + + // update lastwindow after focus + onUnmap(); +} + +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{}; + + Log::logger->log(Log::DEBUG, "Layout predicts size {} for {}", predSize, m_self.lock()); + + m_xdgSurface->m_toplevel->setSize(predSize); + return; + } + + if (!m_isMapped || isHidden()) + return; + + if (m_isX11) + m_reportedSize = m_pendingReportedSize; + + if (!m_isX11 && !isFullscreen() && m_isFloating) { + const auto MINSIZE = m_xdgSurface->m_toplevel->layoutMinSize(); + const auto MAXSIZE = m_xdgSurface->m_toplevel->layoutMaxSize(); + + if (clampWindowSize(MINSIZE, MAXSIZE > Vector2D{1, 1} ? std::optional{MAXSIZE} : std::nullopt)) + g_pHyprRenderer->damageWindow(m_self.lock()); + } + + if (!m_workspace->m_visible) + return; + + 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 (!m_isX11) { + m_subsurfaceHead->recheckDamageForSubsurfaces(); + m_popupHead->recheckTree(); + } + + // tearing: if solitary, redraw it. This still might be a single surface window + if (PMONITOR && PMONITOR->m_solitaryClient.lock() == m_self.lock() && canBeTorn() && PMONITOR->m_tearingState.canTear && wlSurface()->resource()->m_current.texture) { + CRegion damageBox{wlSurface()->resource()->m_current.accumulateBufferDamage()}; + + if (!damageBox.empty()) { + if (PMONITOR->m_tearingState.busy) { + PMONITOR->m_tearingState.frameScheduledWhileBusy = true; + } else { + PMONITOR->m_tearingState.nextRenderTorn = true; + g_pHyprRenderer->renderMonitor(PMONITOR); + } + } + } +} + +void CWindow::destroyWindow() { + Log::logger->log(Log::DEBUG, "{:c} destroyed, queueing.", m_self.lock()); + + if (m_self.lock() == Desktop::focusState()->window()) { + Desktop::focusState()->window().reset(); + Desktop::focusState()->surface().reset(); + } + + wlSurface()->unassign(); + + m_listeners = {}; + + g_layoutManager->removeTarget(m_target); + + 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 + } +} + +void CWindow::activateX11() { + Log::logger->log(Log::DEBUG, "X11 Activate request for window {}", m_self.lock()); + + if (isX11OverrideRedirect()) { + + Log::logger->log(Log::DEBUG, "Unmanaged X11 {} requests activate", m_self.lock()); + + if (Desktop::focusState()->window() && Desktop::focusState()->window()->getPID() != getPID()) + return; + + if (!m_xwaylandSurface->wantsFocus()) + return; + + Desktop::focusState()->fullWindowFocus(m_self.lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE); + return; + } + + if (m_self.lock() == Desktop::focusState()->window() || (m_suppressedEvents & Desktop::View::SUPPRESS_ACTIVATE)) + return; + + activate(); +} + +void CWindow::unmanagedSetGeometry() { + if (!m_isMapped || !m_xwaylandSurface || !m_xwaylandSurface->m_overrideRedirect) + return; + + const auto POS = m_realPosition->goal(); + const auto SIZ = m_realSize->goal(); + + if (m_xwaylandSurface->m_geometry.size() > Vector2D{1, 1}) + setHidden(false); + else + setHidden(true); + + if (isFullscreen() || !m_isFloating) { + sendWindowSize(true); + g_pHyprRenderer->damageWindow(m_self.lock()); + return; + } + + static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); + + const auto PMONITOR = m_monitor.lock(); + const auto LOGICALPOS = g_pXWaylandManager->xwaylandToWaylandCoords(m_xwaylandSurface->m_geometry.pos(), PMONITOR); + const auto XWLSCALE = (*PXWLFORCESCALEZERO && PMONITOR) ? PMONITOR->m_scale : 1.0; + const auto LOGICALGEOSIZE = m_xwaylandSurface->m_geometry.size() / XWLSCALE; + + if (abs(std::floor(POS.x) - LOGICALPOS.x) > 2 || abs(std::floor(POS.y) - LOGICALPOS.y) > 2 || abs(std::floor(SIZ.x) - LOGICALGEOSIZE.x) > 2 || + abs(std::floor(SIZ.y) - LOGICALGEOSIZE.y) > 2) { + Log::logger->log(Log::DEBUG, "Unmanaged window {} requests geometry update to {:j} {:j}", m_self.lock(), LOGICALPOS, m_xwaylandSurface->m_geometry.size()); + + g_pHyprRenderer->damageWindow(m_self.lock()); + m_realPosition->setValueAndWarp(Vector2D(LOGICALPOS.x, LOGICALPOS.y)); + + if (abs(std::floor(SIZ.x) - LOGICALGEOSIZE.x) > 2 || abs(std::floor(SIZ.y) - LOGICALGEOSIZE.y) > 2) + m_realSize->setValueAndWarp(LOGICALGEOSIZE); + + m_position = m_realPosition->goal(); + m_size = m_realSize->goal(); + + m_workspace = g_pCompositor->getMonitorFromVector(m_realPosition->value() + m_realSize->value() / 2.f)->m_activeWorkspace; + + g_pCompositor->changeWindowZOrder(m_self.lock(), true); + updateWindowDecos(); + g_pHyprRenderer->damageWindow(m_self.lock()); + + m_reportedPosition = m_realPosition->goal(); + m_pendingReportedSize = m_realSize->goal(); + } +} + +std::optional CWindow::minSize() { + // first check for overrides + if (m_ruleApplicator->minSize().hasValue()) + return m_ruleApplicator->minSize().value(); + + // then check if we have any proto overrides + bool hasSizeHints = m_xwaylandSurface ? m_xwaylandSurface->m_sizeHints : false; + bool hasTopLevel = m_xdgSurface ? m_xdgSurface->m_toplevel : false; + if ((m_isX11 && !hasSizeHints) || (!m_isX11 && !hasTopLevel)) + return std::nullopt; + + Vector2D minSize = m_isX11 ? Vector2D(m_xwaylandSurface->m_sizeHints->min_width, m_xwaylandSurface->m_sizeHints->min_height) : m_xdgSurface->m_toplevel->layoutMinSize(); + + minSize = minSize.clamp({1, 1}); + + return minSize; +} + +std::optional CWindow::maxSize() { + // first check for overrides + if (m_ruleApplicator->maxSize().hasValue()) + return m_ruleApplicator->maxSize().value(); + + // then check if we have any proto overrides + if (((m_isX11 && !m_xwaylandSurface->m_sizeHints) || (!m_isX11 && (!m_xdgSurface || !m_xdgSurface->m_toplevel)) || m_ruleApplicator->noMaxSize().valueOrDefault())) + return std::nullopt; + + constexpr const double NO_MAX_SIZE_LIMIT = std::numeric_limits::max(); + + Vector2D maxSize = m_isX11 ? Vector2D(m_xwaylandSurface->m_sizeHints->max_width, m_xwaylandSurface->m_sizeHints->max_height) : m_xdgSurface->m_toplevel->layoutMaxSize(); + + if (maxSize.x < 5) + maxSize.x = NO_MAX_SIZE_LIMIT; + if (maxSize.y < 5) + maxSize.y = NO_MAX_SIZE_LIMIT; + + 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 new file mode 100644 index 000000000..a72c626ba --- /dev/null +++ b/src/desktop/view/Window.hpp @@ -0,0 +1,467 @@ +#pragma once + +#include +#include +#include + +#include "View.hpp" +#include "../../config/shared/complex/ComplexDataTypes.hpp" +#include "../../helpers/AnimatedVariable.hpp" +#include "../../helpers/TagKeeper.hpp" +#include "../../macros.hpp" +#include "../../managers/XWaylandManager.hpp" +#include "../../render/decorations/IHyprWindowDecoration.hpp" +#include "../../render/Transformer.hpp" +#include "../DesktopTypes.hpp" +#include "Popup.hpp" +#include "Subsurface.hpp" +#include "WLSurface.hpp" +#include "../Workspace.hpp" +#include "../rule/windowRule/WindowRuleApplicator.hpp" +#include "../../protocols/types/ContentType.hpp" +#include "../../render/Framebuffer.hpp" + +class CXDGSurfaceResource; +class CXWaylandSurface; +class IWindowTransformer; + +namespace Config { + class CWorkspaceRule; +} + +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, + GROUP_SET = 1 << 0, // Open as new group or add to focused group + GROUP_SET_ALWAYS = 1 << 1, + GROUP_BARRED = 1 << 2, // Don't insert to focused group. + GROUP_LOCK = 1 << 3, // Lock m_sGroupData.lock + 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 : uint16_t { + WINDOW_ONLY = 0, + RESERVED_EXTENTS = 1 << 0, + INPUT_EXTENTS = 1 << 1, + FULL_EXTENTS = 1 << 2, + FLOATING_ONLY = 1 << 3, + ALLOW_FLOATING = 1 << 4, + USE_PROP_TILED = 1 << 5, + SKIP_FULLSCREEN_PRIORITY = 1 << 6, + FOCUS_PRIORITY = 1 << 7, + FOLLOW_MOUSE_CHECK = 1 << 8, + }; + + enum eSuppressEvents : uint8_t { + SUPPRESS_NONE = 0, + SUPPRESS_FULLSCREEN = 1 << 0, + SUPPRESS_MAXIMIZE = 1 << 1, + SUPPRESS_ACTIVATE = 1 << 2, + SUPPRESS_ACTIVATE_FOCUSONLY = 1 << 3, + SUPPRESS_FULLSCREEN_OUTPUT = 1 << 4, + }; + + struct SWindowActiveEvent { + PHLWINDOW window = nullptr; + eFocusReason reason = sc(0) /* unknown */; + }; + + struct SInitialWorkspaceToken { + PHLWINDOWREF primaryOwner; + std::string workspace; + }; + + struct SFullscreenState { + eFullscreenMode internal = FSMODE_NONE; + eFullscreenMode client = FSMODE_NONE; + }; + + class CWindow : public IView { + public: + static PHLWINDOW create(SP); + static PHLWINDOW create(SP); + static PHLWINDOW fromView(SP); + + private: + CWindow(SP resource); + CWindow(SP surface); + + public: + virtual ~CWindow(); + + virtual eViewType type() const; + virtual bool visible() const; + virtual std::optional logicalBox() const; + virtual bool desktopComponent() const; + virtual std::optional surfaceLogicalBox() const; + + 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); + + // this is the real position and size used to draw the thing + PHLANIMVAR m_realPosition; + PHLANIMVAR m_realSize; + + // for not spamming the protocols + Vector2D m_reportedPosition; + Vector2D m_reportedSize; + Vector2D m_pendingReportedSize; + std::optional> m_pendingSizeAck; + std::vector> m_pendingSizeAcks; + + // for floating window offset in workspace animations + Vector2D m_floatingOffset = Vector2D(0, 0); + + // for recovering relative cursor position + Vector2D m_relativeCursorCoordsOnLastWarp = Vector2D(-1, -1); + + bool m_firstMap = false; // for layouts + bool m_isFloating = false; + 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; + + bool m_isMapped = false; + + bool m_requestsFloat = false; + + // This is for fullscreen apps + bool m_createdOverFullscreen = false; + + // XWayland stuff + bool m_isX11 = false; + bool m_X11DoesntWantBorders = false; + bool m_X11ShouldntFocus = false; + float m_X11SurfaceScaledBy = 1.f; + // + + // For nofocus + bool m_noInitialFocus = false; + + // Fullscreen and Maximize + bool m_wantsInitialFullscreen = false; + MONITORID m_wantsInitialFullscreenMonitor = MONITOR_INVALID; + + // bitfield suppressEvents + uint64_t m_suppressedEvents = SUPPRESS_NONE; + + // desktop components + SP m_subsurfaceHead; + SP m_popupHead; + + // Animated border + Config::CGradientValueData m_realBorderColor = {0}; + Config::CGradientValueData m_realBorderColorPrevious = {0}; + PHLANIMVAR m_borderFadeAnimationProgress; + PHLANIMVAR m_borderAngleAnimationProgress; + + // Cached border size (invalidated by updateWindowData) + mutable int m_cachedBorderSize = -1; + mutable bool m_borderSizeCacheDirty = true; + + // Fade in-out + PHLANIMVAR m_alpha; + bool m_fadingOut = false; + bool m_readyToDelete = false; + Vector2D m_originalClosedPos; // these will be used for calculations later on in + Vector2D m_originalClosedSize; // drawing the closing animations + SBoxExtents m_originalClosedExtents; + bool m_animatingIn = false; + + // For pinned (sticky) windows + bool m_pinned = false; + + // For preserving pinned state when fullscreening a pinned window + bool m_pinFullscreened = false; + + // urgency hint + bool m_isUrgent = false; + + // for proper cycling. While cycling we can't just move the pointers, so we need to keep track of the last cycled window. + PHLWINDOWREF m_lastCycledWindow; + + // Window decorations + // TODO: make this a SP. + std::vector> m_windowDecorations; + std::vector m_decosToRemove; + + // Special render data, rules, etc + UP m_ruleApplicator; + + // Transformers + std::vector> m_transformers; + + // for alpha + PHLANIMVAR m_activeInactiveAlpha; + PHLANIMVAR m_movingFromWorkspaceAlpha; + + // animated shadow color + PHLANIMVAR m_realShadowColor; + + // animated glow color + PHLANIMVAR m_realGlowColor; + + // animated tint + PHLANIMVAR m_dimPercent; + + // animate moving to an invisible workspace + int m_monitorMovedFrom = -1; // -1 means not moving + PHLANIMVAR m_movingToWorkspaceAlpha; + + // swallowing + PHLWINDOWREF m_swallowed; + bool m_currentlySwallowed = false; + bool m_groupSwallowed = false; + + // for toplevel monitor events + MONITORID m_lastSurfaceMonitorID = -1; + + // initial token. Will be unregistered on workspace change or timeout of 2 minutes + std::string m_initialWorkspaceToken = ""; + + // for groups + SP m_group; + 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; + + // snapshots + SP m_snapshotFB; + + // ANR + PHLANIMVAR m_notRespondingTint; + + // For the noclosefor windowrule + Time::steady_tp m_closeableSince = Time::steadyNow(); + + // For the list lookup + bool operator==(const CWindow& rhs) const { + return m_xdgSurface == rhs.m_xdgSurface && m_xwaylandSurface == rhs.m_xwaylandSurface && m_position == rhs.m_position && m_size == rhs.m_size && + m_fadingOut == rhs.m_fadingOut; + } + + // methods + CBox getFullWindowBoundingBox() const; + SBoxExtents getFullWindowExtents() const; + CBox getWindowBoxUnified(uint64_t props); + SBoxExtents getWindowExtentsUnified(uint64_t props); + CBox getWindowIdealBoundingBoxIgnoreReserved(); + void addWindowDeco(UP deco); + void updateWindowDecos(); + void removeWindowDeco(IHyprWindowDecoration* deco); + void uncacheWindowDecos(); + bool checkInputOnDecos(const eInputType, const Vector2D&, std::any = {}); + pid_t getPID(); + IHyprWindowDecoration* getDecorationByType(eDecorationType); + void updateToplevel(); + void updateSurfaceScaleTransformDetails(bool force = false); + void moveToWorkspace(PHLWORKSPACE); + PHLWINDOW x11TransientFor(); + void onUnmap(); + void onMap(); + void setHidden(bool hidden); + bool isHidden(); + void updateDecorationValues(); + SBoxExtents getFullWindowReservedArea(); + Vector2D middle(); + bool opaque(); + float rounding(); + float roundingPower(); + bool canBeTorn(); + void setSuspended(bool suspend); + bool visibleOnMonitor(PHLMONITOR pMonitor); + WORKSPACEID workspaceID(); + MONITORID monitorID(); + bool onSpecialWorkspace(); + void activate(bool force = false); + int surfacesCount(); + bool clampWindowSize(const std::optional minSize, const std::optional maxSize); + bool isFullscreen(); + bool isEffectiveInternalFSMode(const eFullscreenMode) const; + int getRealBorderSize() const; + float getScrollMouse(); + float getScrollTouchpad(); + bool isScrollMouseOverridden(); + bool isScrollTouchpadOverridden(); + void updateWindowData(); + void updateWindowData(const Config::CWorkspaceRule&); + void onBorderAngleAnimEnd(WP pav); + bool isInCurvedCorner(double x, double y); + bool hasPopupAt(const Vector2D& pos); + int popupsCount(); + void setAnimationsToMove(); + void onWorkspaceAnimUpdate(); + void onFocusAnimUpdate(); + void onUpdateState(); + void onUpdateMeta(); + void onX11ConfigureRequest(CBox box); + void onResourceChangeX11(); + std::string fetchTitle(); + std::string fetchClass(); + void warpCursor(bool force = false); + PHLWINDOW getSwallower(); + bool isX11OverrideRedirect(); + bool isModal(); + Vector2D realToReportSize(); + Vector2D realToReportPosition(); + Vector2D xwaylandSizeToReal(Vector2D size); + Vector2D xwaylandPositionToReal(Vector2D size); + void updateX11SurfaceScale(); + void sendWindowSize(bool force = false); + NContentType::eContentType getContentType(); + void setContentType(NContentType::eContentType contentType); + void deactivateGroupMembers(); + bool isNotResponding(); + std::optional xdgTag(); + std::optional xdgDescription(); + PHLWINDOW parent(); + bool priorityFocus(); + SP getSolitaryResource(); + Vector2D getReportedSize(); + 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}; + } + + // listeners + void onAck(uint32_t serial); + + // + std::unordered_map getEnv(); + + // + PHLWINDOWREF m_self; + + // make private once we move listeners to inside CWindow + struct { + CHyprSignalListener map; + CHyprSignalListener ack; + CHyprSignalListener unmap; + CHyprSignalListener commit; + CHyprSignalListener destroy; + CHyprSignalListener activate; + CHyprSignalListener configureRequest; + CHyprSignalListener setGeometry; + CHyprSignalListener updateState; + CHyprSignalListener updateMetadata; + CHyprSignalListener resourceChange; + } m_listeners; + + private: + std::optional calculateSingleExpr(const std::string& s); + void mapWindow(); + void unmapWindow(); + void commitWindow(); + void destroyWindow(); + void activateX11(); + void unmanagedSetGeometry(); + + // For hidden windows and stuff + bool m_hidden = false; + bool m_suspended = false; + WORKSPACEID m_lastWorkspace = WORKSPACE_INVALID; + }; + + inline bool valid(PHLWINDOW w) { + return w.get(); + } + + inline bool valid(PHLWINDOWREF w) { + return !w.expired(); + } + + inline bool validMapped(PHLWINDOW w) { + if (!valid(w)) + return false; + return w->m_isMapped; + } + + inline bool validMapped(PHLWINDOWREF w) { + if (!valid(w)) + return false; + return w->m_isMapped; + } +} + +/** + format specification + - 'x', only address, equivalent of (uintpr_t)CWindow* + - 'm', with monitor id + - 'w', with workspace id + - 'c', with application class +*/ + +template +struct std::formatter : std::formatter { + bool formatAddressOnly = false; + bool formatWorkspace = false; + bool formatMonitor = false; + bool formatClass = false; + FORMAT_PARSE( // + FORMAT_FLAG('x', formatAddressOnly) // + FORMAT_FLAG('m', formatMonitor) // + FORMAT_FLAG('w', formatWorkspace) // + FORMAT_FLAG('c', formatClass), + PHLWINDOW) + + template + auto format(PHLWINDOW const& w, FormatContext& ctx) const { + auto&& out = ctx.out(); + if (formatAddressOnly) + return std::format_to(out, "{:x}", rc(w.get())); + if (!w) + return std::format_to(out, "[Window nullptr]"); + + std::format_to(out, "["); + std::format_to(out, "Window {:x}: title: \"{}\"", rc(w.get()), w->m_title); + if (formatWorkspace) + std::format_to(out, ", workspace: {}", w->m_workspace ? w->workspaceID() : WORKSPACE_INVALID); + if (formatMonitor) + std::format_to(out, ", monitor: {}", w->monitorID()); + if (formatClass) + std::format_to(out, ", class: {}", w->m_class); + return std::format_to(out, "]"); + } +}; diff --git a/src/devices/IKeyboard.cpp b/src/devices/IKeyboard.cpp index b732ba088..678141f32 100644 --- a/src/devices/IKeyboard.cpp +++ b/src/devices/IKeyboard.cpp @@ -1,14 +1,17 @@ #include "IKeyboard.hpp" #include "../defines.hpp" -#include "../helpers/varlist/VarList.hpp" +#include "../config/legacy/ConfigManager.hpp" #include "../managers/input/InputManager.hpp" #include "../managers/SeatManager.hpp" -#include "../config/ConfigManager.hpp" +#include "../helpers/MiscFunctions.hpp" +#include "../errorOverlay/Overlay.hpp" #include #include +#include #include using namespace Hyprutils::OS; +using namespace Hyprutils::String; #define LED_COUNT 3 @@ -56,7 +59,7 @@ void IKeyboard::clearManuallyAllocd() { void IKeyboard::setKeymap(const SStringRuleNames& rules) { if (m_keymapOverridden) { - Debug::log(LOG, "Ignoring setKeymap: keymap is overridden"); + Log::logger->log(Log::DEBUG, "Ignoring setKeymap: keymap is overridden"); return; } @@ -72,20 +75,20 @@ void IKeyboard::setKeymap(const SStringRuleNames& rules) { const auto CONTEXT = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (!CONTEXT) { - Debug::log(ERR, "setKeymap: CONTEXT null??"); + Log::logger->log(Log::ERR, "setKeymap: CONTEXT null??"); return; } clearManuallyAllocd(); - Debug::log(LOG, "Attempting to create a keymap for layout {} with variant {} (rules: {}, model: {}, options: {})", rules.layout, rules.variant, rules.rules, rules.model, - rules.options); + Log::logger->log(Log::DEBUG, "Attempting to create a keymap for layout {} with variant {} (rules: {}, model: {}, options: {})", rules.layout, rules.variant, rules.rules, + rules.model, rules.options); if (!m_xkbFilePath.empty()) { - auto path = absolutePath(m_xkbFilePath, g_pConfigManager->m_configCurrentPath); + auto path = absolutePath(m_xkbFilePath, Config::mgr()->currentConfigPath()); if (FILE* const KEYMAPFILE = fopen(path.c_str(), "r"); !KEYMAPFILE) - Debug::log(ERR, "Cannot open input:kb_file= file for reading"); + Log::logger->log(Log::ERR, "Cannot open input:kb_file= file for reading"); else { m_xkbKeymap = xkb_keymap_new_from_file(CONTEXT, KEYMAPFILE, XKB_KEYMAP_FORMAT_TEXT_V2, XKB_KEYMAP_COMPILE_NO_FLAGS); fclose(KEYMAPFILE); @@ -96,11 +99,11 @@ void IKeyboard::setKeymap(const SStringRuleNames& rules) { m_xkbKeymap = xkb_keymap_new_from_names2(CONTEXT, &XKBRULES, XKB_KEYMAP_FORMAT_TEXT_V2, XKB_KEYMAP_COMPILE_NO_FLAGS); if (!m_xkbKeymap) { - g_pConfigManager->addParseError("Invalid keyboard layout passed. ( rules: " + rules.rules + ", model: " + rules.model + ", variant: " + rules.variant + - ", options: " + rules.options + ", layout: " + rules.layout + " )"); + ErrorOverlay::overlay()->queueError("Invalid keyboard layout passed. ( rules: " + rules.rules + ", model: " + rules.model + ", variant: " + rules.variant + + ", options: " + rules.options + ", layout: " + rules.layout + " )"); - Debug::log(ERR, "Keyboard layout {} with variant {} (rules: {}, model: {}, options: {}) couldn't have been loaded.", rules.layout, rules.variant, rules.rules, rules.model, - rules.options); + Log::logger->log(Log::ERR, "Keyboard layout {} with variant {} (rules: {}, model: {}, options: {}) couldn't have been loaded.", rules.layout, rules.variant, rules.rules, + rules.model, rules.options); memset(&XKBRULES, 0, sizeof(XKBRULES)); m_currentRules.rules = ""; @@ -114,7 +117,7 @@ void IKeyboard::setKeymap(const SStringRuleNames& rules) { updateXKBTranslationState(m_xkbKeymap); - const auto NUMLOCKON = g_pConfigManager->getDeviceInt(m_hlName, "numlock_by_default", "input:numlock_by_default"); + const auto NUMLOCKON = Config::mgr()->getDeviceInt(m_hlName, "numlock_by_default", "input:numlock_by_default"); if (NUMLOCKON == 1) { // lock numlock @@ -129,12 +132,12 @@ void IKeyboard::setKeymap(const SStringRuleNames& rules) { for (size_t i = 0; i < std::min(LEDNAMES.size(), m_ledIndexes.size()); ++i) { m_ledIndexes[i] = xkb_map_led_get_index(m_xkbKeymap, LEDNAMES[i]); - Debug::log(LOG, "xkb: LED index {} (name {}) got index {}", i, LEDNAMES[i], m_ledIndexes[i]); + Log::logger->log(Log::DEBUG, "xkb: LED index {} (name {}) got index {}", i, LEDNAMES[i], m_ledIndexes[i]); } for (size_t i = 0; i < std::min(MODNAMES.size(), m_modIndexes.size()); ++i) { m_modIndexes[i] = xkb_map_mod_get_index(m_xkbKeymap, MODNAMES[i]); - Debug::log(LOG, "xkb: Mod index {} (name {}) got index {}", i, MODNAMES[i], m_modIndexes[i]); + Log::logger->log(Log::DEBUG, "xkb: Mod index {} (name {}) got index {}", i, MODNAMES[i], m_modIndexes[i]); } updateKeymapFD(); @@ -145,7 +148,7 @@ void IKeyboard::setKeymap(const SStringRuleNames& rules) { } void IKeyboard::updateKeymapFD() { - Debug::log(LOG, "Updating keymap fd for keyboard {}", m_deviceName); + Log::logger->log(Log::DEBUG, "Updating keymap fd for keyboard {}", m_deviceName); if (m_xkbKeymapFD.isValid()) m_xkbKeymapFD.reset(); @@ -162,11 +165,11 @@ void IKeyboard::updateKeymapFD() { CFileDescriptor rw, ro, rwV1, roV1; if (!allocateSHMFilePair(m_xkbKeymapString.length() + 1, rw, ro)) - Debug::log(ERR, "IKeyboard: failed to allocate shm pair for the keymap"); + Log::logger->log(Log::ERR, "IKeyboard: failed to allocate shm pair for the keymap"); else if (!allocateSHMFilePair(m_xkbKeymapV1String.length() + 1, rwV1, roV1)) { ro.reset(); rw.reset(); - Debug::log(ERR, "IKeyboard: failed to allocate shm pair for keymap V1"); + Log::logger->log(Log::ERR, "IKeyboard: failed to allocate shm pair for keymap V1"); } else { auto keymapFDDest = mmap(nullptr, m_xkbKeymapString.length() + 1, PROT_READ | PROT_WRITE, MAP_SHARED, rw.get(), 0); auto keymapV1FDDest = mmap(nullptr, m_xkbKeymapV1String.length() + 1, PROT_READ | PROT_WRITE, MAP_SHARED, rwV1.get(), 0); @@ -174,7 +177,7 @@ void IKeyboard::updateKeymapFD() { rwV1.reset(); if (keymapFDDest == MAP_FAILED || keymapV1FDDest == MAP_FAILED) { - Debug::log(ERR, "IKeyboard: failed to mmap a shm pair for the keymap"); + Log::logger->log(Log::ERR, "IKeyboard: failed to mmap a shm pair for the keymap"); ro.reset(); roV1.reset(); } else { @@ -187,7 +190,7 @@ void IKeyboard::updateKeymapFD() { } } - Debug::log(LOG, "Updated keymap fd to {}, keymap V1 to: {}", m_xkbKeymapFD.get(), m_xkbKeymapV1FD.get()); + Log::logger->log(Log::DEBUG, "Updated keymap fd to {}, keymap V1 to: {}", m_xkbKeymapFD.get(), m_xkbKeymapV1FD.get()); } void IKeyboard::updateXKBTranslationState(xkb_keymap* const keymap) { @@ -206,7 +209,7 @@ void IKeyboard::updateXKBTranslationState(xkb_keymap* const keymap) { m_xkbSymState = nullptr; if (keymap) { - Debug::log(LOG, "Updating keyboard {:x}'s translation state from a provided keymap", rc(this)); + Log::logger->log(Log::DEBUG, "Updating keyboard {:x}'s translation state from a provided keymap", rc(this)); m_xkbStaticState = xkb_state_new(keymap); m_xkbState = xkb_state_new(keymap); m_xkbSymState = xkb_state_new(keymap); @@ -221,7 +224,7 @@ void IKeyboard::updateXKBTranslationState(xkb_keymap* const keymap) { for (uint32_t i = 0; i < LAYOUTSNUM; ++i) { if (xkb_state_layout_index_is_active(STATE, i, XKB_STATE_LAYOUT_EFFECTIVE) == 1) { - Debug::log(LOG, "Updating keyboard {:x}'s translation state from an active index {}", rc(this), i); + Log::logger->log(Log::DEBUG, "Updating keyboard {:x}'s translation state from an active index {}", rc(this), i); CVarList keyboardLayouts(m_currentRules.layout, 0, ','); CVarList keyboardModels(m_currentRules.model, 0, ','); @@ -241,14 +244,14 @@ void IKeyboard::updateXKBTranslationState(xkb_keymap* const keymap) { auto KEYMAP = xkb_keymap_new_from_names2(PCONTEXT, &rules, XKB_KEYMAP_FORMAT_TEXT_V2, XKB_KEYMAP_COMPILE_NO_FLAGS); if (!KEYMAP) { - Debug::log(ERR, "updateXKBTranslationState: keymap failed 1, fallback without model/variant"); + Log::logger->log(Log::ERR, "updateXKBTranslationState: keymap failed 1, fallback without model/variant"); rules.model = ""; rules.variant = ""; KEYMAP = xkb_keymap_new_from_names2(PCONTEXT, &rules, XKB_KEYMAP_FORMAT_TEXT_V2, XKB_KEYMAP_COMPILE_NO_FLAGS); } if (!KEYMAP) { - Debug::log(ERR, "updateXKBTranslationState: keymap failed 2, fallback to us"); + Log::logger->log(Log::ERR, "updateXKBTranslationState: keymap failed 2, fallback to us"); rules.layout = "us"; KEYMAP = xkb_keymap_new_from_names2(PCONTEXT, &rules, XKB_KEYMAP_FORMAT_TEXT_V2, XKB_KEYMAP_COMPILE_NO_FLAGS); } @@ -264,7 +267,7 @@ void IKeyboard::updateXKBTranslationState(xkb_keymap* const keymap) { } } - Debug::log(LOG, "Updating keyboard {:x}'s translation state from an unknown index", rc(this)); + Log::logger->log(Log::DEBUG, "Updating keyboard {:x}'s translation state from an unknown index", rc(this)); xkb_rule_names rules = { .rules = m_currentRules.rules.c_str(), diff --git a/src/devices/VirtualKeyboard.cpp b/src/devices/VirtualKeyboard.cpp index 2951f36ad..97a2c76c7 100644 --- a/src/devices/VirtualKeyboard.cpp +++ b/src/devices/VirtualKeyboard.cpp @@ -1,7 +1,7 @@ #include "VirtualKeyboard.hpp" #include "../defines.hpp" #include "../protocols/VirtualKeyboard.hpp" -#include "../config/ConfigManager.hpp" +#include "../config/legacy/ConfigManager.hpp" #include SP CVirtualKeyboard::create(SP keeb) { @@ -46,7 +46,7 @@ CVirtualKeyboard::CVirtualKeyboard(SP keeb_) : m_key m_deviceName = keeb_->m_name; - const auto SHARESTATES = g_pConfigManager->getDeviceInt(m_deviceName, "share_states", "input:virtualkeyboard:share_states"); + const auto SHARESTATES = Config::mgr()->getDeviceInt(m_deviceName, "share_states", "input:virtualkeyboard:share_states"); m_shareStates = SHARESTATES != 0; m_shareStatesAuto = SHARESTATES == 2; } diff --git a/src/errorOverlay/Overlay.cpp b/src/errorOverlay/Overlay.cpp new file mode 100644 index 000000000..928d78003 --- /dev/null +++ b/src/errorOverlay/Overlay.cpp @@ -0,0 +1,260 @@ +#include "Overlay.hpp" +#include "../Compositor.hpp" +#include "../config/ConfigValue.hpp" +#include "../config/shared/animation/AnimationTree.hpp" +#include "../desktop/state/FocusState.hpp" +#include "../event/EventBus.hpp" +#include "../managers/animation/AnimationManager.hpp" +#include "../render/Renderer.hpp" +#include "../render/pass/BorderPassElement.hpp" +#include "../render/pass/RectPassElement.hpp" +#include "../render/pass/TexPassElement.hpp" + +#include +#include + +using namespace Hyprutils::Animation; +using namespace ErrorOverlay; + +static std::string takeFirstNLines(const std::string& text, size_t lines) { + if (lines <= 0) + return ""; + + size_t begin = 0; + size_t count = 1; + while (count < lines) { + const auto nlPos = text.find('\n', begin); + if (nlPos == std::string::npos) + return text; + + begin = nlPos + 1; + ++count; + } + + const auto cutPos = text.find('\n', begin); + return cutPos == std::string::npos ? text : text.substr(0, cutPos); +} + +static std::string buildVisibleText(const std::string& text, size_t lineLimit) { + const size_t lineCount = 1 + std::ranges::count(text, '\n'); + const size_t visibleLines = std::max(0, std::min(lineCount, lineLimit)); + + std::string visibleText = takeFirstNLines(text, visibleLines); + if (visibleLines < lineCount) { + if (!visibleText.empty()) + visibleText += '\n'; + + visibleText += std::format("({} more...)", lineCount - visibleLines); + } + + return visibleText; +} + +UP& ErrorOverlay::overlay() { + static UP p = makeUnique(); + return p; +} + +COverlay::COverlay() { + g_pAnimationManager->createAnimation(0.f, m_fadeOpacity, Config::animationTree()->getAnimationPropertyConfig("fadeIn"), AVARDAMAGE_NONE); + + static auto P = Event::bus()->m_events.monitor.focused.listen([&](PHLMONITOR mon) { + if (!m_isCreated) + return; + + g_pHyprRenderer->damageMonitor(Desktop::focusState()->monitor()); + m_monitorChanged = true; + }); + + static auto P2 = Event::bus()->m_events.render.pre.listen([&](PHLMONITOR mon) { + if (!m_isCreated) + return; + + if (m_fadeOpacity->isBeingAnimated() || m_monitorChanged) + g_pHyprRenderer->damageBox(m_damageBox); + }); +} + +void COverlay::queueCreate(std::string message, const CHyprColor& color) { + queueCreate(std::move(message), Config::CGradientValueData{color}); +} + +void COverlay::queueCreate(std::string message, const Config::CGradientValueData& gradient) { + m_queued = std::move(message); + m_queuedBorderGradient = gradient; + + if (m_queuedBorderGradient.m_colors.empty()) + m_queuedBorderGradient.reset(CHyprColor(1.0, 50.0 / 255.0, 50.0 / 255.0, 1.0)); + else + m_queuedBorderGradient.updateColorsOk(); +} + +void COverlay::queueError(std::string err) { + queueCreate(err + "\nHyprland may not work correctly.", CHyprColor(1.0, 50.0 / 255.0, 50.0 / 255.0, 1.0)); +} + +void COverlay::createQueued() { + if (m_isCreated && m_textTexture) + m_textTexture.reset(); + + m_fadeOpacity->setConfig(Config::animationTree()->getAnimationPropertyConfig("fadeIn")); + m_fadeOpacity->setValueAndWarp(0.f); + *m_fadeOpacity = 1.f; + + const auto PMONITOR = g_pCompositor->m_monitors.front(); + if (!PMONITOR) + return; + + const float SCALE = PMONITOR->m_scale; + const int FONTSIZE = std::clamp(sc(10.f * ((PMONITOR->m_pixelSize.x * SCALE) / 1920.f)), 8, 40); + + static auto LINELIMIT = CConfigValue("debug:error_limit"); + static auto BAR_POSITION = CConfigValue("debug:error_position"); + static auto FONT_FAMILY = CConfigValue("misc:font_family"); + + const bool TOPBAR = *BAR_POSITION == 0; + const std::string visibleText = buildVisibleText(m_queued, *LINELIMIT); + + m_outerPad = 10.F * SCALE; + + const float barWidth = std::max(1.F, sc(PMONITOR->m_pixelSize.x) - m_outerPad * 2.F); + const float textMaxWidth = std::max(1.F, barWidth - 2.F * (1.F + m_outerPad)); + + m_textTexture = g_pHyprRenderer->renderText(Hyprgraphics::CTextResource::STextResourceData{ + .text = visibleText, + .font = *FONT_FAMILY, + .fontSize = sc(FONTSIZE), + .color = CHyprColor(0.9, 0.9, 0.9, 1.0).asRGB(), + .maxSize = Vector2D{textMaxWidth, -1.F}, + .ellipsize = false, + .wrap = true, + }); + + m_textSize = m_textTexture ? m_textTexture->m_size : Vector2D{}; + + m_lastHeight = std::max(3.F, sc(m_textSize.y) + 3.F); + m_radius = std::min(m_outerPad, std::max(0.F, m_lastHeight / 2.F - 1.F)); + m_textOffsetX = 1.F + m_radius; + m_textOffsetY = 1.F; + + m_damageBox = { + sc(PMONITOR->m_position.x), + sc(PMONITOR->m_position.y + (TOPBAR ? 0 : PMONITOR->m_pixelSize.y - (m_lastHeight + m_outerPad * 2.F))), + sc(PMONITOR->m_pixelSize.x), + sc(m_lastHeight + m_outerPad * 2.F), + }; + + m_borderGradient = m_queuedBorderGradient; + + m_isCreated = true; + m_queued = ""; + m_queuedDestroy = false; + + g_pHyprRenderer->damageMonitor(PMONITOR); + + for (const auto& m : g_pCompositor->m_monitors) { + m->m_reservedArea.resetType(Desktop::RESERVED_DYNAMIC_TYPE_ERROR_BAR); + } + + const auto RESERVED = (m_lastHeight + m_outerPad) / SCALE; + PMONITOR->m_reservedArea.addType(Desktop::RESERVED_DYNAMIC_TYPE_ERROR_BAR, Vector2D{0.0, TOPBAR ? RESERVED : 0.0}, Vector2D{0.0, !TOPBAR ? RESERVED : 0.0}); + + for (const auto& m : g_pCompositor->m_monitors) { + g_pHyprRenderer->arrangeLayersForMonitor(m->m_id); + } +} + +void COverlay::draw() { + if (!m_isCreated || !m_queued.empty()) { + if (!m_queued.empty()) + createQueued(); + return; + } + + if (m_queuedDestroy) { + if (!m_fadeOpacity->isBeingAnimated()) { + if (m_fadeOpacity->value() == 0.f) { + m_queuedDestroy = false; + m_textTexture.reset(); + m_textSize = {}; + m_outerPad = 0.F; + m_radius = 0.F; + 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; + } else { + m_fadeOpacity->setConfig(Config::animationTree()->getAnimationPropertyConfig("fadeOut")); + *m_fadeOpacity = 0.f; + } + } + } + + const auto PMONITOR = g_pHyprRenderer->m_renderData.pMonitor; + if (!PMONITOR) + return; + + static auto BAR_POSITION = CConfigValue("debug:error_position"); + const bool TOPBAR = *BAR_POSITION == 0; + + const float barWidth = std::max(1.F, sc(PMONITOR->m_pixelSize.x) - m_outerPad * 2.F); + const float barY = TOPBAR ? m_outerPad : PMONITOR->m_pixelSize.y - m_lastHeight - m_outerPad; + const CBox barBox = {m_outerPad, barY, barWidth, m_lastHeight}; + + m_damageBox.x = sc(PMONITOR->m_position.x); + m_damageBox.width = sc(PMONITOR->m_pixelSize.x); + m_damageBox.height = sc(m_lastHeight + m_outerPad * 2.F); + m_damageBox.y = sc(PMONITOR->m_position.y + (TOPBAR ? 0 : PMONITOR->m_pixelSize.y - m_damageBox.height)); + + if (m_fadeOpacity->isBeingAnimated() || m_monitorChanged) + g_pHyprRenderer->damageBox(m_damageBox); + + m_monitorChanged = false; + + const float opacity = m_fadeOpacity->value(); + + CRectPassElement::SRectData bgData; + bgData.box = barBox; + bgData.color = CHyprColor(0.06, 0.06, 0.06, opacity); + bgData.round = sc(std::round(m_radius)); + g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(bgData))); + + CBorderPassElement::SBorderData borderData; + borderData.box = barBox; + borderData.grad1 = m_borderGradient; + borderData.round = sc(std::round(m_radius)); + borderData.outerRound = sc(std::round(m_radius)); + borderData.borderSize = 2; + borderData.a = opacity; + g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(borderData))); + + if (m_textTexture) { + CTexPassElement::SRenderData textData; + textData.tex = m_textTexture; + textData.box = {Vector2D{barBox.x + m_textOffsetX, barBox.y + m_textOffsetY}.round(), m_textSize}; + textData.a = opacity; + textData.clipRegion = CRegion(barBox.copy().expand(-1)); + + g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(textData))); + } +} + +void COverlay::destroy() { + if (m_isCreated) + m_queuedDestroy = true; + else + m_queued = ""; +} + +bool COverlay::active() { + return m_isCreated; +} + +float COverlay::height() { + return m_lastHeight; +} diff --git a/src/errorOverlay/Overlay.hpp b/src/errorOverlay/Overlay.hpp new file mode 100644 index 000000000..68f0e5dcc --- /dev/null +++ b/src/errorOverlay/Overlay.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include + +#include "../defines.hpp" +#include "../render/Texture.hpp" +#include "../helpers/AnimatedVariable.hpp" +#include "../helpers/MiscFunctions.hpp" +#include "../config/shared/complex/ComplexDataTypes.hpp" + +namespace ErrorOverlay { + + namespace Colors { + constexpr const float ANGLE_30 = 0.52359877; + + static const Config::CGradientValueData ERROR = + Config::CGradientValueData{std::vector{*configStringToInt("0xffff6666"), *configStringToInt("0xff800000")}, ANGLE_30}; + static const Config::CGradientValueData WARNING = + Config::CGradientValueData{std::vector{*configStringToInt("0xffffdb4d"), *configStringToInt("0xff665200")}, ANGLE_30}; + }; + + class COverlay { + public: + COverlay(); + ~COverlay() = default; + + void queueCreate(std::string message, const CHyprColor& color); + void queueCreate(std::string message, const Config::CGradientValueData& gradient); + void queueError(std::string err); + void draw(); + void destroy(); + + bool active(); + float height(); // logical + + private: + void createQueued(); + std::string m_queued = ""; + Config::CGradientValueData m_queuedBorderGradient; + Config::CGradientValueData m_borderGradient; + bool m_queuedDestroy = false; + bool m_isCreated = false; + SP m_textTexture; + Vector2D m_textSize = {}; + float m_outerPad = 0.F; + float m_radius = 0.F; + float m_textOffsetX = 0.F; + float m_textOffsetY = 0.F; + PHLANIMVAR m_fadeOpacity; + CBox m_damageBox = {0, 0, 0, 0}; + float m_lastHeight = 0.F; + + bool m_monitorChanged = false; + }; + + UP& overlay(); +} diff --git a/src/event/EventBus.cpp b/src/event/EventBus.cpp new file mode 100644 index 000000000..f06c3984a --- /dev/null +++ b/src/event/EventBus.cpp @@ -0,0 +1,8 @@ +#include "EventBus.hpp" + +using namespace Event; + +UP& Event::bus() { + static UP p = makeUnique(); + return p; +} diff --git a/src/event/EventBus.hpp b/src/event/EventBus.hpp new file mode 100644 index 000000000..40a6fc926 --- /dev/null +++ b/src/event/EventBus.hpp @@ -0,0 +1,147 @@ +#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; + Event<> start; + Event<> exit; + + 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 preChecks; + 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/events/Events.hpp b/src/events/Events.hpp deleted file mode 100644 index 2e564944c..000000000 --- a/src/events/Events.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include "../defines.hpp" - -// NOLINTNEXTLINE(readability-identifier-naming) -namespace Events { - // Window events - DYNLISTENFUNC(commitWindow); - DYNLISTENFUNC(mapWindow); - DYNLISTENFUNC(unmapWindow); - DYNLISTENFUNC(destroyWindow); - DYNLISTENFUNC(setTitleWindow); - DYNLISTENFUNC(fullscreenWindow); - DYNLISTENFUNC(activateX11); - DYNLISTENFUNC(configureX11); - DYNLISTENFUNC(unmanagedSetGeometry); - DYNLISTENFUNC(requestMove); - DYNLISTENFUNC(requestResize); - DYNLISTENFUNC(requestMinimize); - DYNLISTENFUNC(requestMaximize); - DYNLISTENFUNC(setOverrideRedirect); - DYNLISTENFUNC(ackConfigure); -}; diff --git a/src/events/Windows.cpp b/src/events/Windows.cpp deleted file mode 100644 index 9a1c07d95..000000000 --- a/src/events/Windows.cpp +++ /dev/null @@ -1,859 +0,0 @@ -#include "Events.hpp" - -#include "../Compositor.hpp" -#include "../helpers/WLClasses.hpp" -#include "../helpers/AsyncDialogBox.hpp" -#include "../managers/input/InputManager.hpp" -#include "../managers/TokenManager.hpp" -#include "../managers/SeatManager.hpp" -#include "../render/Renderer.hpp" -#include "../config/ConfigValue.hpp" -#include "../config/ConfigManager.hpp" -#include "../protocols/LayerShell.hpp" -#include "../protocols/XDGShell.hpp" -#include "../protocols/core/Compositor.hpp" -#include "../protocols/ToplevelExport.hpp" -#include "../protocols/types/ContentType.hpp" -#include "../xwayland/XSurface.hpp" -#include "desktop/DesktopTypes.hpp" -#include "managers/animation/AnimationManager.hpp" -#include "managers/animation/DesktopAnimationManager.hpp" -#include "managers/PointerManager.hpp" -#include "../desktop/LayerSurface.hpp" -#include "../desktop/state/FocusState.hpp" -#include "../managers/LayoutManager.hpp" -#include "../managers/EventManager.hpp" -#include "../managers/animation/AnimationManager.hpp" - -#include -using namespace Hyprutils::String; -using namespace Hyprutils::Animation; - -// ------------------------------------------------------------ // -// __ _______ _ _ _____ ______ _______ // -// \ \ / /_ _| \ | | __ \ / __ \ \ / / ____| // -// \ \ /\ / / | | | \| | | | | | | \ \ /\ / / (___ // -// \ \/ \/ / | | | . ` | | | | | | |\ \/ \/ / \___ \ // -// \ /\ / _| |_| |\ | |__| | |__| | \ /\ / ____) | // -// \/ \/ |_____|_| \_|_____/ \____/ \/ \/ |_____/ // -// // -// ------------------------------------------------------------ // - -static void setVector2DAnimToMove(WP pav) { - const auto PAV = pav.lock(); - if (!PAV) - return; - - CAnimatedVariable* animvar = dc*>(PAV.get()); - animvar->setConfig(g_pConfigManager->getAnimationPropertyConfig("windowsMove")); - - const auto PHLWINDOW = animvar->m_Context.pWindow.lock(); - if (PHLWINDOW) - PHLWINDOW->m_animatingIn = false; -} - -void Events::listener_mapWindow(void* owner, void* data) { - PHLWINDOW PWINDOW = sc(owner)->m_self.lock(); - - static auto PINACTIVEALPHA = CConfigValue("decoration:inactive_opacity"); - static auto PACTIVEALPHA = CConfigValue("decoration:active_opacity"); - static auto PDIMSTRENGTH = CConfigValue("decoration:dim_strength"); - static auto PNEWTAKESOVERFS = CConfigValue("misc:on_focus_under_fullscreen"); - static auto PINITIALWSTRACKING = CConfigValue("misc:initial_workspace_tracking"); - - auto PMONITOR = Desktop::focusState()->monitor(); - if (!Desktop::focusState()->monitor()) { - Desktop::focusState()->rawMonitorFocus(g_pCompositor->getMonitorFromVector({})); - PMONITOR = Desktop::focusState()->monitor(); - } - auto PWORKSPACE = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace; - PWINDOW->m_monitor = PMONITOR; - PWINDOW->m_workspace = PWORKSPACE; - PWINDOW->m_isMapped = true; - PWINDOW->m_readyToDelete = false; - PWINDOW->m_fadingOut = false; - PWINDOW->m_title = PWINDOW->fetchTitle(); - PWINDOW->m_firstMap = true; - PWINDOW->m_initialTitle = PWINDOW->m_title; - PWINDOW->m_initialClass = PWINDOW->fetchClass(); - - // check for token - std::string requestedWorkspace = ""; - bool workspaceSilent = false; - - if (*PINITIALWSTRACKING) { - const auto WINDOWENV = PWINDOW->getEnv(); - if (WINDOWENV.contains("HL_INITIAL_WORKSPACE_TOKEN")) { - const auto SZTOKEN = WINDOWENV.at("HL_INITIAL_WORKSPACE_TOKEN"); - Debug::log(LOG, "New window contains HL_INITIAL_WORKSPACE_TOKEN: {}", SZTOKEN); - const auto TOKEN = g_pTokenManager->getToken(SZTOKEN); - if (TOKEN) { - // find workspace and use it - SInitialWorkspaceToken WS = std::any_cast(TOKEN->m_data); - - Debug::log(LOG, "HL_INITIAL_WORKSPACE_TOKEN {} -> {}", SZTOKEN, WS.workspace); - - if (g_pCompositor->getWorkspaceByString(WS.workspace) != PWINDOW->m_workspace) { - requestedWorkspace = WS.workspace; - workspaceSilent = true; - } - - if (*PINITIALWSTRACKING == 1) // one-shot token - g_pTokenManager->removeToken(TOKEN); - else if (*PINITIALWSTRACKING == 2) { // persistent - if (WS.primaryOwner.expired()) { - WS.primaryOwner = PWINDOW; - TOKEN->m_data = WS; - } - - PWINDOW->m_initialWorkspaceToken = SZTOKEN; - } - } - } - } - - if (g_pInputManager->m_lastFocusOnLS) // waybar fix - g_pInputManager->releaseAllMouseButtons(); - - // checks if the window wants borders and sets the appropriate flag - g_pXWaylandManager->checkBorders(PWINDOW); - - // registers the animated vars and stuff - PWINDOW->onMap(); - - const auto PWINDOWSURFACE = PWINDOW->m_wlSurface->resource(); - - if (!PWINDOWSURFACE) { - g_pCompositor->removeWindowFromVectorSafe(PWINDOW); - return; - } - - if (g_pXWaylandManager->shouldBeFloated(PWINDOW)) { - PWINDOW->m_isFloating = true; - PWINDOW->m_requestsFloat = true; - } - - PWINDOW->m_X11ShouldntFocus = PWINDOW->m_X11ShouldntFocus || (PWINDOW->m_isX11 && PWINDOW->isX11OverrideRedirect() && !PWINDOW->m_xwaylandSurface->wantsFocus()); - - // window rules - std::optional requestedInternalFSMode, requestedClientFSMode; - std::optional requestedFSState; - if (PWINDOW->m_wantsInitialFullscreen || (PWINDOW->m_isX11 && PWINDOW->m_xwaylandSurface->m_fullscreen)) - requestedClientFSMode = FSMODE_FULLSCREEN; - MONITORID requestedFSMonitor = PWINDOW->m_wantsInitialFullscreenMonitor; - - PWINDOW->m_ruleApplicator->readStaticRules(); - { - if (!PWINDOW->m_ruleApplicator->static_.monitor.empty()) { - const auto& MONITORSTR = PWINDOW->m_ruleApplicator->static_.monitor; - if (MONITORSTR == "unset") - PWINDOW->m_monitor = PMONITOR; - else { - const auto MONITOR = g_pCompositor->getMonitorFromString(MONITORSTR); - - if (MONITOR) { - PWINDOW->m_monitor = MONITOR; - - const auto PMONITORFROMID = PWINDOW->m_monitor.lock(); - - if (PWINDOW->m_monitor != PMONITOR) { - g_pKeybindManager->m_dispatchers["focusmonitor"](std::to_string(PWINDOW->monitorID())); - PMONITOR = PMONITORFROMID; - } - PWINDOW->m_workspace = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace; - PWORKSPACE = PWINDOW->m_workspace; - - Debug::log(LOG, "Rule monitor, applying to {:mw}", PWINDOW); - requestedFSMonitor = MONITOR_INVALID; - } else - Debug::log(ERR, "No monitor in monitor {} rule", MONITORSTR); - } - } - - if (!PWINDOW->m_ruleApplicator->static_.workspace.empty()) { - const auto WORKSPACERQ = PWINDOW->m_ruleApplicator->static_.workspace; - - if (WORKSPACERQ == "unset") - requestedWorkspace = ""; - else - requestedWorkspace = WORKSPACERQ; - - const auto JUSTWORKSPACE = WORKSPACERQ.contains(' ') ? WORKSPACERQ.substr(0, WORKSPACERQ.find_first_of(' ')) : WORKSPACERQ; - - if (JUSTWORKSPACE == PWORKSPACE->m_name || JUSTWORKSPACE == "name:" + PWORKSPACE->m_name) - requestedWorkspace = ""; - - Debug::log(LOG, "Rule workspace matched by {}, {} applied.", PWINDOW, PWINDOW->m_ruleApplicator->static_.workspace); - requestedFSMonitor = MONITOR_INVALID; - } - - if (PWINDOW->m_ruleApplicator->static_.floating.has_value()) - PWINDOW->m_isFloating = PWINDOW->m_ruleApplicator->static_.floating.value(); - - if (PWINDOW->m_ruleApplicator->static_.pseudo) - PWINDOW->m_isPseudotiled = true; - - if (PWINDOW->m_ruleApplicator->static_.noInitialFocus) - PWINDOW->m_noInitialFocus = true; - - if (PWINDOW->m_ruleApplicator->static_.fullscreenStateClient || PWINDOW->m_ruleApplicator->static_.fullscreenStateInternal) { - requestedFSState = SFullscreenState{ - .internal = sc(PWINDOW->m_ruleApplicator->static_.fullscreenStateInternal.value_or(0)), - .client = sc(PWINDOW->m_ruleApplicator->static_.fullscreenStateClient.value_or(0)), - }; - } - - if (!PWINDOW->m_ruleApplicator->static_.suppressEvent.empty()) { - for (const auto& var : PWINDOW->m_ruleApplicator->static_.suppressEvent) { - if (var == "fullscreen") - PWINDOW->m_suppressedEvents |= SUPPRESS_FULLSCREEN; - else if (var == "maximize") - PWINDOW->m_suppressedEvents |= SUPPRESS_MAXIMIZE; - else if (var == "activate") - PWINDOW->m_suppressedEvents |= SUPPRESS_ACTIVATE; - else if (var == "activatefocus") - PWINDOW->m_suppressedEvents |= SUPPRESS_ACTIVATE_FOCUSONLY; - else if (var == "fullscreenoutput") - PWINDOW->m_suppressedEvents |= SUPPRESS_FULLSCREEN_OUTPUT; - else - Debug::log(ERR, "Error while parsing suppressevent windowrule: unknown event type {}", var); - } - } - - if (PWINDOW->m_ruleApplicator->static_.pin) - PWINDOW->m_pinned = true; - - if (PWINDOW->m_ruleApplicator->static_.fullscreen) - requestedInternalFSMode = FSMODE_FULLSCREEN; - - if (PWINDOW->m_ruleApplicator->static_.maximize) - requestedInternalFSMode = FSMODE_MAXIMIZED; - - if (!PWINDOW->m_ruleApplicator->static_.group.empty()) { - if (!(PWINDOW->m_groupRules & GROUP_OVERRIDE) && trim(PWINDOW->m_ruleApplicator->static_.group) != "group") { - CVarList2 vars(std::string{PWINDOW->m_ruleApplicator->static_.group}, 0, 's'); - std::string vPrev = ""; - - for (auto const& v : vars) { - if (v == "group") - continue; - - if (v == "set") { - PWINDOW->m_groupRules |= GROUP_SET; - } else if (v == "new") { - // shorthand for `group barred set` - PWINDOW->m_groupRules |= (GROUP_SET | GROUP_BARRED); - } else if (v == "lock") { - PWINDOW->m_groupRules |= GROUP_LOCK; - } else if (v == "invade") { - PWINDOW->m_groupRules |= GROUP_INVADE; - } else if (v == "barred") { - PWINDOW->m_groupRules |= GROUP_BARRED; - } else if (v == "deny") { - PWINDOW->m_groupData.deny = true; - } else if (v == "override") { - // Clear existing rules - PWINDOW->m_groupRules = GROUP_OVERRIDE; - } else if (v == "unset") { - // Clear existing rules and stop processing - PWINDOW->m_groupRules = GROUP_OVERRIDE; - break; - } else if (v == "always") { - if (vPrev == "set" || vPrev == "group") - PWINDOW->m_groupRules |= GROUP_SET_ALWAYS; - else if (vPrev == "lock") - PWINDOW->m_groupRules |= GROUP_LOCK_ALWAYS; - else - Debug::log(ERR, "windowrule `group` does not support `{} always`", vPrev); - } - vPrev = v; - } - } - } - - if (PWINDOW->m_ruleApplicator->static_.content) - PWINDOW->setContentType(sc(PWINDOW->m_ruleApplicator->static_.content.value())); - - if (PWINDOW->m_ruleApplicator->static_.noCloseFor) - PWINDOW->m_closeableSince = Time::steadyNow() + std::chrono::milliseconds(PWINDOW->m_ruleApplicator->static_.noCloseFor.value()); - } - - // make it uncloseable if it's a Hyprland dialog - // TODO: make some closeable? - if (CAsyncDialogBox::isAsyncDialogBox(PWINDOW->getPID())) - PWINDOW->m_closeableSince = Time::steadyNow() + std::chrono::years(10 /* Should be enough, no? */); - - // disallow tiled pinned - if (PWINDOW->m_pinned && !PWINDOW->m_isFloating) - PWINDOW->m_pinned = false; - - CVarList2 WORKSPACEARGS = CVarList2(std::move(requestedWorkspace), 0, ' ', false, false); - - if (!WORKSPACEARGS[0].empty()) { - WORKSPACEID requestedWorkspaceID; - std::string requestedWorkspaceName; - if (WORKSPACEARGS.contains("silent")) - workspaceSilent = true; - - if (WORKSPACEARGS.contains("empty") && PWORKSPACE->getWindows() <= 1) { - requestedWorkspaceID = PWORKSPACE->m_id; - requestedWorkspaceName = PWORKSPACE->m_name; - } else { - auto result = getWorkspaceIDNameFromString(WORKSPACEARGS.join(" ", 0, workspaceSilent ? WORKSPACEARGS.size() - 1 : 0)); - requestedWorkspaceID = result.id; - requestedWorkspaceName = result.name; - } - - if (requestedWorkspaceID != WORKSPACE_INVALID) { - auto pWorkspace = g_pCompositor->getWorkspaceByID(requestedWorkspaceID); - - if (!pWorkspace) - pWorkspace = g_pCompositor->createNewWorkspace(requestedWorkspaceID, PWINDOW->monitorID(), requestedWorkspaceName, false); - - PWORKSPACE = pWorkspace; - - PWINDOW->m_workspace = pWorkspace; - PWINDOW->m_monitor = pWorkspace->m_monitor; - - if (PWINDOW->m_monitor.lock()->m_activeSpecialWorkspace && !pWorkspace->m_isSpecialWorkspace) - workspaceSilent = true; - - if (!workspaceSilent) { - if (pWorkspace->m_isSpecialWorkspace) - pWorkspace->m_monitor->setSpecialWorkspace(pWorkspace); - else if (PMONITOR->activeWorkspaceID() != requestedWorkspaceID && !PWINDOW->m_noInitialFocus) - g_pKeybindManager->m_dispatchers["workspace"](requestedWorkspaceName); - - PMONITOR = Desktop::focusState()->monitor(); - } - - requestedFSMonitor = MONITOR_INVALID; - } else - workspaceSilent = false; - } - - if (PWINDOW->m_suppressedEvents & SUPPRESS_FULLSCREEN_OUTPUT) - requestedFSMonitor = MONITOR_INVALID; - else if (requestedFSMonitor != MONITOR_INVALID) { - if (const auto PM = g_pCompositor->getMonitorFromID(requestedFSMonitor); PM) - PWINDOW->m_monitor = PM; - - const auto PMONITORFROMID = PWINDOW->m_monitor.lock(); - - if (PWINDOW->m_monitor != PMONITOR) { - g_pKeybindManager->m_dispatchers["focusmonitor"](std::to_string(PWINDOW->monitorID())); - PMONITOR = PMONITORFROMID; - } - PWINDOW->m_workspace = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace; - PWORKSPACE = PWINDOW->m_workspace; - - Debug::log(LOG, "Requested monitor, applying to {:mw}", PWINDOW); - } - - if (PWORKSPACE->m_defaultFloating) - PWINDOW->m_isFloating = true; - - if (PWORKSPACE->m_defaultPseudo) { - PWINDOW->m_isPseudotiled = true; - CBox desiredGeometry = g_pXWaylandManager->getGeometryForWindow(PWINDOW); - PWINDOW->m_pseudoSize = Vector2D(desiredGeometry.width, desiredGeometry.height); - } - - PWINDOW->updateWindowData(); - - // Verify window swallowing. Get the swallower before calling onWindowCreated(PWINDOW) because getSwallower() wouldn't get it after if PWINDOW gets auto grouped. - const auto SWALLOWER = PWINDOW->getSwallower(); - PWINDOW->m_swallowed = SWALLOWER; - if (PWINDOW->m_swallowed) - PWINDOW->m_swallowed->m_currentlySwallowed = true; - - // 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},{},{},{}", PWINDOW, PWORKSPACE->m_name, PWINDOW->m_class, PWINDOW->m_title)}); - EMIT_HOOK_EVENT("openWindowEarly", PWINDOW); - - if (PWINDOW->m_isFloating) { - g_pLayoutManager->getCurrentLayout()->onWindowCreated(PWINDOW); - PWINDOW->m_createdOverFullscreen = true; - - if (!PWINDOW->m_ruleApplicator->static_.size.empty()) { - const auto COMPUTED = PWINDOW->calculateExpression(PWINDOW->m_ruleApplicator->static_.size); - if (!COMPUTED) - Debug::log(ERR, "failed to parse {} as an expression", PWINDOW->m_ruleApplicator->static_.size); - else { - *PWINDOW->m_realSize = *COMPUTED; - PWINDOW->setHidden(false); - } - } - - if (!PWINDOW->m_ruleApplicator->static_.position.empty()) { - const auto COMPUTED = PWINDOW->calculateExpression(PWINDOW->m_ruleApplicator->static_.position); - if (!COMPUTED) - Debug::log(ERR, "failed to parse {} as an expression", PWINDOW->m_ruleApplicator->static_.position); - else { - *PWINDOW->m_realPosition = *COMPUTED + PMONITOR->m_position; - PWINDOW->setHidden(false); - } - } - - if (PWINDOW->m_ruleApplicator->static_.center) { - auto RESERVEDOFFSET = (PMONITOR->m_reservedTopLeft - PMONITOR->m_reservedBottomRight) / 2.f; - *PWINDOW->m_realPosition = PMONITOR->middle() - PWINDOW->m_realSize->goal() / 2.f + RESERVEDOFFSET; - } - - // set the pseudo size to the GOAL of our current size - // because the windows are animated on RealSize - PWINDOW->m_pseudoSize = PWINDOW->m_realSize->goal(); - - g_pCompositor->changeWindowZOrder(PWINDOW, true); - } else { - g_pLayoutManager->getCurrentLayout()->onWindowCreated(PWINDOW); - - bool setPseudo = false; - - if (!PWINDOW->m_ruleApplicator->static_.size.empty()) { - const auto COMPUTED = PWINDOW->calculateExpression(PWINDOW->m_ruleApplicator->static_.size); - if (!COMPUTED) - Debug::log(ERR, "failed to parse {} as an expression", PWINDOW->m_ruleApplicator->static_.size); - else { - setPseudo = true; - PWINDOW->m_pseudoSize = *COMPUTED; - PWINDOW->setHidden(false); - } - } - - if (!setPseudo) - PWINDOW->m_pseudoSize = PWINDOW->m_realSize->goal() - Vector2D(10, 10); - } - - const auto PFOCUSEDWINDOWPREV = Desktop::focusState()->window(); - - if (PWINDOW->m_ruleApplicator->allowsInput().valueOrDefault()) { // if default value wasn't set to false getPriority() would throw an exception - PWINDOW->m_ruleApplicator->noFocusOverride(Desktop::Types::COverridableVar(false, PWINDOW->m_ruleApplicator->allowsInput().getPriority())); - PWINDOW->m_noInitialFocus = false; - PWINDOW->m_X11ShouldntFocus = false; - } - - // check LS focus grab - const auto PFORCEFOCUS = g_pCompositor->getForceFocus(); - const auto PLSFROMFOCUS = g_pCompositor->getLayerSurfaceFromSurface(Desktop::focusState()->surface()); - if (PLSFROMFOCUS && PLSFROMFOCUS->m_layerSurface->m_current.interactivity != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE) - PWINDOW->m_noInitialFocus = true; - - if (PWINDOW->m_workspace->m_hasFullscreenWindow && !requestedInternalFSMode.has_value() && !requestedClientFSMode.has_value() && !PWINDOW->m_isFloating) { - if (*PNEWTAKESOVERFS == 0) - PWINDOW->m_noInitialFocus = true; - else if (*PNEWTAKESOVERFS == 1) - requestedInternalFSMode = PWINDOW->m_workspace->m_fullscreenMode; - else if (*PNEWTAKESOVERFS == 2) - g_pCompositor->setWindowFullscreenInternal(PWINDOW->m_workspace->getFullscreenWindow(), FSMODE_NONE); - } - - if (!PWINDOW->m_ruleApplicator->noFocus().valueOrDefault() && !PWINDOW->m_noInitialFocus && - (!PWINDOW->isX11OverrideRedirect() || (PWINDOW->m_isX11 && PWINDOW->m_xwaylandSurface->wantsFocus())) && !workspaceSilent && (!PFORCEFOCUS || PFORCEFOCUS == PWINDOW) && - !g_pInputManager->isConstrained()) { - Desktop::focusState()->fullWindowFocus(PWINDOW); - PWINDOW->m_activeInactiveAlpha->setValueAndWarp(*PACTIVEALPHA); - PWINDOW->m_dimPercent->setValueAndWarp(PWINDOW->m_ruleApplicator->noDim().valueOrDefault() ? 0.f : *PDIMSTRENGTH); - } else { - PWINDOW->m_activeInactiveAlpha->setValueAndWarp(*PINACTIVEALPHA); - PWINDOW->m_dimPercent->setValueAndWarp(0); - } - - if (requestedClientFSMode.has_value() && (PWINDOW->m_suppressedEvents & SUPPRESS_FULLSCREEN)) - requestedClientFSMode = sc(sc(requestedClientFSMode.value_or(FSMODE_NONE)) & ~sc(FSMODE_FULLSCREEN)); - if (requestedClientFSMode.has_value() && (PWINDOW->m_suppressedEvents & SUPPRESS_MAXIMIZE)) - requestedClientFSMode = sc(sc(requestedClientFSMode.value_or(FSMODE_NONE)) & ~sc(FSMODE_MAXIMIZED)); - - if (!PWINDOW->m_noInitialFocus && (requestedInternalFSMode.has_value() || requestedClientFSMode.has_value() || requestedFSState.has_value())) { - // fix fullscreen on requested (basically do a switcheroo) - if (PWINDOW->m_workspace->m_hasFullscreenWindow) - g_pCompositor->setWindowFullscreenInternal(PWINDOW->m_workspace->getFullscreenWindow(), FSMODE_NONE); - - PWINDOW->m_realPosition->warp(); - PWINDOW->m_realSize->warp(); - if (requestedFSState.has_value()) { - PWINDOW->m_ruleApplicator->syncFullscreenOverride(Desktop::Types::COverridableVar(false, Desktop::Types::PRIORITY_WINDOW_RULE)); - g_pCompositor->setWindowFullscreenState(PWINDOW, requestedFSState.value()); - } else if (requestedInternalFSMode.has_value() && requestedClientFSMode.has_value() && !PWINDOW->m_ruleApplicator->syncFullscreen().valueOrDefault()) - g_pCompositor->setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = requestedInternalFSMode.value(), .client = requestedClientFSMode.value()}); - else if (requestedInternalFSMode.has_value()) - g_pCompositor->setWindowFullscreenInternal(PWINDOW, requestedInternalFSMode.value()); - else if (requestedClientFSMode.has_value()) - g_pCompositor->setWindowFullscreenClient(PWINDOW, requestedClientFSMode.value()); - } - - // recheck idle inhibitors - g_pInputManager->recheckIdleInhibitorStatus(); - - PWINDOW->updateToplevel(); - PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ALL); - - if (workspaceSilent) { - if (validMapped(PFOCUSEDWINDOWPREV)) { - Desktop::focusState()->rawWindowFocus(PFOCUSEDWINDOWPREV); - PFOCUSEDWINDOWPREV->updateWindowDecos(); // need to for some reason i cba to find out why - } else if (!PFOCUSEDWINDOWPREV) - Desktop::focusState()->rawWindowFocus(nullptr); - } - - // swallow - if (SWALLOWER) { - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(SWALLOWER); - g_pHyprRenderer->damageWindow(SWALLOWER); - SWALLOWER->setHidden(true); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID()); - } - - PWINDOW->m_firstMap = false; - - Debug::log(LOG, "Map request dispatched, monitor {}, window pos: {:5j}, window size: {:5j}", PMONITOR->m_name, PWINDOW->m_realPosition->goal(), PWINDOW->m_realSize->goal()); - - // emit the hook event here after basic stuff has been initialized - EMIT_HOOK_EVENT("openWindow", PWINDOW); - - // apply data from default decos. Borders, shadows. - g_pDecorationPositioner->forceRecalcFor(PWINDOW); - PWINDOW->updateWindowDecos(); - g_pLayoutManager->getCurrentLayout()->recalculateWindow(PWINDOW); - - // do animations - g_pDesktopAnimationManager->startAnimation(PWINDOW, CDesktopAnimationManager::ANIMATION_TYPE_IN); - - PWINDOW->m_realPosition->setCallbackOnEnd(setVector2DAnimToMove); - PWINDOW->m_realSize->setCallbackOnEnd(setVector2DAnimToMove); - - // recalc the values for this window - PWINDOW->updateDecorationValues(); - // avoid this window being visible - if (PWORKSPACE->m_hasFullscreenWindow && !PWINDOW->isFullscreen() && !PWINDOW->m_isFloating) - PWINDOW->m_alpha->setValueAndWarp(0.f); - - g_pCompositor->setPreferredScaleForSurface(PWINDOW->m_wlSurface->resource(), PMONITOR->m_scale); - g_pCompositor->setPreferredTransformForSurface(PWINDOW->m_wlSurface->resource(), PMONITOR->m_transform); - - if (g_pSeatManager->m_mouse.expired() || !g_pInputManager->isConstrained()) - g_pInputManager->sendMotionEventsToFocused(); - - // fix some xwayland apps that don't behave nicely - PWINDOW->m_reportedSize = PWINDOW->m_pendingReportedSize; - - if (PWINDOW->m_workspace) - PWINDOW->m_workspace->updateWindows(); - - if (PMONITOR && PWINDOW->isX11OverrideRedirect()) - PWINDOW->m_X11SurfaceScaledBy = PMONITOR->m_scale; -} - -void Events::listener_unmapWindow(void* owner, void* data) { - PHLWINDOW PWINDOW = sc(owner)->m_self.lock(); - - Debug::log(LOG, "{:c} unmapped", PWINDOW); - - static auto PEXITRETAINSFS = CConfigValue("misc:exit_window_retains_fullscreen"); - - const auto CURRENTWINDOWFSSTATE = PWINDOW->isFullscreen(); - const auto CURRENTFSMODE = PWINDOW->m_fullscreenState.internal; - - if (!PWINDOW->m_wlSurface->exists() || !PWINDOW->m_isMapped) { - Debug::log(WARN, "{} unmapped without being mapped??", PWINDOW); - PWINDOW->m_fadingOut = false; - return; - } - - const auto PMONITOR = PWINDOW->m_monitor.lock(); - if (PMONITOR) { - PWINDOW->m_originalClosedPos = PWINDOW->m_realPosition->value() - PMONITOR->m_position; - PWINDOW->m_originalClosedSize = PWINDOW->m_realSize->value(); - PWINDOW->m_originalClosedExtents = PWINDOW->getFullWindowExtents(); - } - - g_pEventManager->postEvent(SHyprIPCEvent{"closewindow", std::format("{:x}", PWINDOW)}); - EMIT_HOOK_EVENT("closeWindow", PWINDOW); - - if (PWINDOW->m_isFloating && !PWINDOW->m_isX11 && PWINDOW->m_ruleApplicator->persistentSize().valueOrDefault()) { - Debug::log(LOG, "storing floating size {}x{} for window {}::{} on close", PWINDOW->m_realSize->value().x, PWINDOW->m_realSize->value().y, PWINDOW->m_class, - PWINDOW->m_title); - g_pConfigManager->storeFloatingSize(PWINDOW, PWINDOW->m_realSize->value()); - } - - PROTO::toplevelExport->onWindowUnmap(PWINDOW); - - if (PWINDOW->isFullscreen()) - g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); - - // Allow the renderer to catch the last frame. - if (g_pHyprRenderer->shouldRenderWindow(PWINDOW)) - g_pHyprRenderer->makeSnapshot(PWINDOW); - - // swallowing - if (valid(PWINDOW->m_swallowed)) { - if (PWINDOW->m_swallowed->m_currentlySwallowed) { - PWINDOW->m_swallowed->m_currentlySwallowed = false; - PWINDOW->m_swallowed->setHidden(false); - - if (PWINDOW->m_groupData.pNextWindow.lock()) - PWINDOW->m_swallowed->m_groupSwallowed = true; // flag for the swallowed window to be created into the group where it belongs when auto_group = false. - - g_pLayoutManager->getCurrentLayout()->onWindowCreated(PWINDOW->m_swallowed.lock()); - } - - PWINDOW->m_swallowed->m_groupSwallowed = false; - PWINDOW->m_swallowed.reset(); - } - - bool wasLastWindow = false; - - if (PWINDOW == Desktop::focusState()->window()) { - wasLastWindow = true; - Desktop::focusState()->window().reset(); - Desktop::focusState()->surface().reset(); - - g_pInputManager->releaseAllMouseButtons(); - } - - if (PWINDOW == g_pInputManager->m_currentlyDraggedWindow.lock()) - g_pKeybindManager->changeMouseBindMode(MBIND_INVALID); - - // remove the fullscreen window status from workspace if we closed it - const auto PWORKSPACE = PWINDOW->m_workspace; - - if (PWORKSPACE->m_hasFullscreenWindow && PWINDOW->isFullscreen()) - PWORKSPACE->m_hasFullscreenWindow = false; - - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(PWINDOW); - - g_pHyprRenderer->damageWindow(PWINDOW); - - // do this after onWindowRemoved because otherwise it'll think the window is invalid - PWINDOW->m_isMapped = false; - - // refocus on a new window if needed - if (wasLastWindow) { - static auto FOCUSONCLOSE = CConfigValue("input:focus_on_close"); - PHLWINDOW PWINDOWCANDIDATE = nullptr; - if (*FOCUSONCLOSE) - PWINDOWCANDIDATE = (g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING)); - else - PWINDOWCANDIDATE = g_pLayoutManager->getCurrentLayout()->getNextWindowCandidate(PWINDOW); - - Debug::log(LOG, "On closed window, new focused candidate is {}", PWINDOWCANDIDATE); - - if (PWINDOWCANDIDATE != Desktop::focusState()->window() && PWINDOWCANDIDATE) { - Desktop::focusState()->fullWindowFocus(PWINDOWCANDIDATE); - if (*PEXITRETAINSFS && CURRENTWINDOWFSSTATE) - g_pCompositor->setWindowFullscreenInternal(PWINDOWCANDIDATE, CURRENTFSMODE); - } - - if (!PWINDOWCANDIDATE && PWINDOW->m_workspace && PWINDOW->m_workspace->getWindows() == 0) - g_pInputManager->refocus(); - - g_pInputManager->sendMotionEventsToFocused(); - - // CWindow::onUnmap will remove this window's active status, but we can't really do it above. - if (PWINDOW == Desktop::focusState()->window() || !Desktop::focusState()->window()) { - g_pEventManager->postEvent(SHyprIPCEvent{"activewindow", ","}); - g_pEventManager->postEvent(SHyprIPCEvent{"activewindowv2", ""}); - EMIT_HOOK_EVENT("activeWindow", PHLWINDOW{nullptr}); - } - } else { - Debug::log(LOG, "Unmapped was not focused, ignoring a refocus."); - } - - PWINDOW->m_fadingOut = true; - - g_pCompositor->addToFadingOutSafe(PWINDOW); - - if (!PWINDOW->m_X11DoesntWantBorders) // don't animate out if they weren't animated in. - *PWINDOW->m_realPosition = PWINDOW->m_realPosition->value() + Vector2D(0.01f, 0.01f); // it has to be animated, otherwise CesktopAnimationManager will ignore it - - // anims - g_pDesktopAnimationManager->startAnimation(PWINDOW, CDesktopAnimationManager::ANIMATION_TYPE_OUT); - - // recheck idle inhibitors - g_pInputManager->recheckIdleInhibitorStatus(); - - // force report all sizes (QT sometimes has an issue with this) - if (PWINDOW->m_workspace) - PWINDOW->m_workspace->forceReportSizesToWindows(); - - // update lastwindow after focus - PWINDOW->onUnmap(); -} - -void Events::listener_commitWindow(void* owner, void* data) { - PHLWINDOW PWINDOW = sc(owner)->m_self.lock(); - - if (!PWINDOW->m_isX11 && PWINDOW->m_xdgSurface->m_initialCommit) { - Vector2D predSize = g_pLayoutManager->getCurrentLayout()->predictSizeForNewWindow(PWINDOW); - - Debug::log(LOG, "Layout predicts size {} for {}", predSize, PWINDOW); - - PWINDOW->m_xdgSurface->m_toplevel->setSize(predSize); - return; - } - - if (!PWINDOW->m_isMapped || PWINDOW->isHidden()) - return; - - if (PWINDOW->m_isX11) - PWINDOW->m_reportedSize = PWINDOW->m_pendingReportedSize; - - if (!PWINDOW->m_isX11 && !PWINDOW->isFullscreen() && PWINDOW->m_isFloating) { - const auto MINSIZE = PWINDOW->m_xdgSurface->m_toplevel->layoutMinSize(); - const auto MAXSIZE = PWINDOW->m_xdgSurface->m_toplevel->layoutMaxSize(); - - PWINDOW->clampWindowSize(MINSIZE, MAXSIZE > Vector2D{1, 1} ? std::optional{MAXSIZE} : std::nullopt); - g_pHyprRenderer->damageWindow(PWINDOW); - } - - if (!PWINDOW->m_workspace->m_visible) - return; - - const auto PMONITOR = PWINDOW->m_monitor.lock(); - - if (PMONITOR) - PMONITOR->debugLastPresentation(g_pSeatManager->m_isPointerFrameCommit ? "listener_commitWindow skip" : "listener_commitWindow"); - - if (g_pSeatManager->m_isPointerFrameCommit) { - g_pSeatManager->m_isPointerFrameSkipped = false; - g_pSeatManager->m_isPointerFrameCommit = false; - } else - g_pHyprRenderer->damageSurface(PWINDOW->m_wlSurface->resource(), PWINDOW->m_realPosition->goal().x, PWINDOW->m_realPosition->goal().y, - PWINDOW->m_isX11 ? 1.0 / PWINDOW->m_X11SurfaceScaledBy : 1.0); - - if (g_pSeatManager->m_isPointerFrameSkipped) { - g_pPointerManager->sendStoredMovement(); - g_pSeatManager->sendPointerFrame(); - g_pSeatManager->m_isPointerFrameCommit = true; - } - - if (!PWINDOW->m_isX11) { - PWINDOW->m_subsurfaceHead->recheckDamageForSubsurfaces(); - PWINDOW->m_popupHead->recheckTree(); - } - - // tearing: if solitary, redraw it. This still might be a single surface window - if (PMONITOR && PMONITOR->m_solitaryClient.lock() == PWINDOW && PWINDOW->canBeTorn() && PMONITOR->m_tearingState.canTear && - PWINDOW->m_wlSurface->resource()->m_current.texture) { - CRegion damageBox{PWINDOW->m_wlSurface->resource()->m_current.accumulateBufferDamage()}; - - if (!damageBox.empty()) { - if (PMONITOR->m_tearingState.busy) { - PMONITOR->m_tearingState.frameScheduledWhileBusy = true; - } else { - PMONITOR->m_tearingState.nextRenderTorn = true; - g_pHyprRenderer->renderMonitor(PMONITOR); - } - } - } -} - -void Events::listener_destroyWindow(void* owner, void* data) { - PHLWINDOW PWINDOW = sc(owner)->m_self.lock(); - - Debug::log(LOG, "{:c} destroyed, queueing.", PWINDOW); - - if (PWINDOW == Desktop::focusState()->window()) { - Desktop::focusState()->window().reset(); - Desktop::focusState()->surface().reset(); - } - - PWINDOW->m_wlSurface->unassign(); - - PWINDOW->m_listeners = {}; - - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(PWINDOW); - - PWINDOW->m_readyToDelete = true; - - PWINDOW->m_xdgSurface.reset(); - - if (!PWINDOW->m_fadingOut) { - Debug::log(LOG, "Unmapped {} removed instantly", PWINDOW); - g_pCompositor->removeWindowFromVectorSafe(PWINDOW); // most likely X11 unmanaged or sumn - } - - PWINDOW->m_listeners.unmap.reset(); - PWINDOW->m_listeners.destroy.reset(); - PWINDOW->m_listeners.map.reset(); - PWINDOW->m_listeners.commit.reset(); -} - -void Events::listener_activateX11(void* owner, void* data) { - PHLWINDOW PWINDOW = sc(owner)->m_self.lock(); - - Debug::log(LOG, "X11 Activate request for window {}", PWINDOW); - - if (PWINDOW->isX11OverrideRedirect()) { - - Debug::log(LOG, "Unmanaged X11 {} requests activate", PWINDOW); - - if (Desktop::focusState()->window() && Desktop::focusState()->window()->getPID() != PWINDOW->getPID()) - return; - - if (!PWINDOW->m_xwaylandSurface->wantsFocus()) - return; - - Desktop::focusState()->fullWindowFocus(PWINDOW); - return; - } - - if (PWINDOW == Desktop::focusState()->window() || (PWINDOW->m_suppressedEvents & SUPPRESS_ACTIVATE)) - return; - - PWINDOW->activate(); -} - -void Events::listener_unmanagedSetGeometry(void* owner, void* data) { - PHLWINDOW PWINDOW = sc(owner)->m_self.lock(); - - if (!PWINDOW->m_isMapped || !PWINDOW->m_xwaylandSurface || !PWINDOW->m_xwaylandSurface->m_overrideRedirect) - return; - - const auto POS = PWINDOW->m_realPosition->goal(); - const auto SIZ = PWINDOW->m_realSize->goal(); - - if (PWINDOW->m_xwaylandSurface->m_geometry.size() > Vector2D{1, 1}) - PWINDOW->setHidden(false); - else - PWINDOW->setHidden(true); - - if (PWINDOW->isFullscreen() || !PWINDOW->m_isFloating) { - PWINDOW->sendWindowSize(true); - g_pHyprRenderer->damageWindow(PWINDOW); - return; - } - - static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); - - const auto LOGICALPOS = g_pXWaylandManager->xwaylandToWaylandCoords(PWINDOW->m_xwaylandSurface->m_geometry.pos()); - - if (abs(std::floor(POS.x) - LOGICALPOS.x) > 2 || abs(std::floor(POS.y) - LOGICALPOS.y) > 2 || abs(std::floor(SIZ.x) - PWINDOW->m_xwaylandSurface->m_geometry.width) > 2 || - abs(std::floor(SIZ.y) - PWINDOW->m_xwaylandSurface->m_geometry.height) > 2) { - Debug::log(LOG, "Unmanaged window {} requests geometry update to {:j} {:j}", PWINDOW, LOGICALPOS, PWINDOW->m_xwaylandSurface->m_geometry.size()); - - g_pHyprRenderer->damageWindow(PWINDOW); - PWINDOW->m_realPosition->setValueAndWarp(Vector2D(LOGICALPOS.x, LOGICALPOS.y)); - - if (abs(std::floor(SIZ.x) - PWINDOW->m_xwaylandSurface->m_geometry.w) > 2 || abs(std::floor(SIZ.y) - PWINDOW->m_xwaylandSurface->m_geometry.h) > 2) - PWINDOW->m_realSize->setValueAndWarp(PWINDOW->m_xwaylandSurface->m_geometry.size()); - - if (*PXWLFORCESCALEZERO) { - if (const auto PMONITOR = PWINDOW->m_monitor.lock(); PMONITOR) { - PWINDOW->m_realSize->setValueAndWarp(PWINDOW->m_realSize->goal() / PMONITOR->m_scale); - } - } - - PWINDOW->m_position = PWINDOW->m_realPosition->goal(); - PWINDOW->m_size = PWINDOW->m_realSize->goal(); - - PWINDOW->m_workspace = g_pCompositor->getMonitorFromVector(PWINDOW->m_realPosition->value() + PWINDOW->m_realSize->value() / 2.f)->m_activeWorkspace; - - g_pCompositor->changeWindowZOrder(PWINDOW, true); - PWINDOW->updateWindowDecos(); - g_pHyprRenderer->damageWindow(PWINDOW); - - PWINDOW->m_reportedPosition = PWINDOW->m_realPosition->goal(); - PWINDOW->m_pendingReportedSize = PWINDOW->m_realSize->goal(); - } -} diff --git a/src/helpers/AnimatedVariable.hpp b/src/helpers/AnimatedVariable.hpp index e7d5fd8cd..f0bdc5a81 100644 --- a/src/helpers/AnimatedVariable.hpp +++ b/src/helpers/AnimatedVariable.hpp @@ -67,7 +67,7 @@ template using CAnimatedVariable = Hyprutils::Animation::CGenericAnimatedVariable; template -using PHLANIMVAR = SP>; +using PHLANIMVAR = UP>; template using PHLANIMVARREF = WP>; diff --git a/src/helpers/AsyncDialogBox.cpp b/src/helpers/AsyncDialogBox.cpp index 6257dcb0d..8c2c7cd70 100644 --- a/src/helpers/AsyncDialogBox.cpp +++ b/src/helpers/AsyncDialogBox.cpp @@ -4,6 +4,8 @@ #include #include #include "../managers/eventLoop/EventLoopManager.hpp" +#include "../desktop/rule/windowRule/WindowRule.hpp" +#include "../desktop/rule/Engine.hpp" using namespace Hyprutils::OS; @@ -12,7 +14,7 @@ static std::vector>> asyncDialogBoxes; // SP CAsyncDialogBox::create(const std::string& title, const std::string& description, std::vector buttons) { if (!NFsUtils::executableExistsInPath("hyprland-dialog")) { - Debug::log(ERR, "CAsyncDialogBox: cannot create, no hyprland-dialog"); + Log::logger->log(Log::ERR, "CAsyncDialogBox: cannot create, no hyprland-dialog"); return nullptr; } @@ -63,7 +65,7 @@ void CAsyncDialogBox::onWrite(int fd, uint32_t mask) { // TODO: can we avoid this without risking a blocking read()? int fdFlags = fcntl(fd, F_GETFL, 0); if (fcntl(fd, F_SETFL, fdFlags | O_NONBLOCK) < 0) { - Debug::log(ERR, "CAsyncDialogBox::onWrite: fcntl 1 failed!"); + Log::logger->log(Log::ERR, "CAsyncDialogBox::onWrite: fcntl 1 failed!"); return; } @@ -73,13 +75,13 @@ void CAsyncDialogBox::onWrite(int fd, uint32_t mask) { // restore the flags (otherwise libwayland won't give us a hangup) if (fcntl(fd, F_SETFL, fdFlags) < 0) { - Debug::log(ERR, "CAsyncDialogBox::onWrite: fcntl 2 failed!"); + Log::logger->log(Log::ERR, "CAsyncDialogBox::onWrite: fcntl 2 failed!"); return; } } if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { - Debug::log(LOG, "CAsyncDialogBox: dialog {:x} hung up, closed."); + Log::logger->log(Log::DEBUG, "CAsyncDialogBox: dialog {:x} hung up, closed."); m_promiseResolver->resolve(m_stdout); std::erase_if(asyncDialogBoxes, [this](const auto& e) { return e.first == m_dialogPid; }); @@ -102,7 +104,7 @@ SP> CAsyncDialogBox::open() { int outPipe[2]; if (pipe(outPipe)) { - Debug::log(ERR, "CAsyncDialogBox::open: failed to pipe()"); + Log::logger->log(Log::ERR, "CAsyncDialogBox::open: failed to pipe()"); return nullptr; } @@ -113,14 +115,17 @@ SP> CAsyncDialogBox::open() { m_readEventSource = wl_event_loop_add_fd(g_pEventLoopManager->m_wayland.loop, m_pipeReadFd.get(), WL_EVENT_READABLE, ::onFdWrite, this); if (!m_readEventSource) { - Debug::log(ERR, "CAsyncDialogBox::open: failed to add read fd to loop"); + Log::logger->log(Log::ERR, "CAsyncDialogBox::open: failed to add read fd to loop"); return nullptr; } m_selfReference = m_selfWeakReference.lock(); + if (!m_execRuleToken.empty()) + proc.addEnv(Desktop::Rule::EXEC_RULE_ENV_NAME, m_execRuleToken); + if (!proc.runAsync()) { - Debug::log(ERR, "CAsyncDialogBox::open: failed to run async"); + Log::logger->log(Log::ERR, "CAsyncDialogBox::open: failed to run async"); wl_event_source_remove(m_readEventSource); return nullptr; } @@ -147,6 +152,16 @@ bool CAsyncDialogBox::isRunning() const { return m_readEventSource; } +pid_t CAsyncDialogBox::getPID() const { + return m_dialogPid; +} + 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 5f94be0da..1bdeba14d 100644 --- a/src/helpers/AsyncDialogBox.hpp +++ b/src/helpers/AsyncDialogBox.hpp @@ -26,6 +26,8 @@ class CAsyncDialogBox { SP> open(); void kill(); bool isRunning() const; + pid_t getPID() const; + void setExecRule(std::string&& s); SP lockSelf(); @@ -40,7 +42,8 @@ class CAsyncDialogBox { pid_t m_dialogPid = 0; wl_event_source* m_readEventSource = nullptr; Hyprutils::OS::CFileDescriptor m_pipeReadFd; - std::string m_stdout = ""; + std::string m_stdout = ""; + std::string m_execRuleToken = ""; const std::string m_title; const std::string m_description; @@ -51,4 +54,4 @@ class CAsyncDialogBox { // WARNING: cyclic reference. This will be removed once the event source is removed to avoid dangling pointers SP m_selfReference; WP m_selfWeakReference; -}; \ No newline at end of file +}; diff --git a/src/helpers/CMType.hpp b/src/helpers/CMType.hpp index 8802cca82..67478dd92 100644 --- a/src/helpers/CMType.hpp +++ b/src/helpers/CMType.hpp @@ -1,3 +1,5 @@ +#pragma once + #include #include #include diff --git a/src/helpers/Drm.cpp b/src/helpers/Drm.cpp new file mode 100644 index 000000000..4f3119f83 --- /dev/null +++ b/src/helpers/Drm.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include +#include +#include "Drm.hpp" + +namespace { + using SDRMNodePair = std::array; + + std::optional getDrmNodePair(int fd1, int fd2) { + const auto DEVA = DRM::devIDFromFD(fd1); + const auto DEVB = DRM::devIDFromFD(fd2); + if (!DEVA || !DEVB) + return std::nullopt; + + SDRMNodePair pair = {*DEVA, *DEVB}; + if (pair[0] > pair[1]) + std::swap(pair[0], pair[1]); + + return pair; + } +} + +std::optional DRM::devIDFromFD(int fd) { + struct stat stat = {}; + if (fstat(fd, &stat) != 0 || !S_ISCHR(stat.st_mode)) + return std::nullopt; + + return stat.st_rdev; +} + +bool DRM::sameGpu(int fd1, int fd2) { + if (fd1 < 0 || fd2 < 0 || fd1 == fd2) + return true; + + static std::mutex cacheMutex; + static std::map sameGpuCache; + + const auto NODEPAIR = getDrmNodePair(fd1, fd2); + if (NODEPAIR) { + std::scoped_lock lock(cacheMutex); + if (const auto it = sameGpuCache.find(*NODEPAIR); it != sameGpuCache.end()) + return it->second; + } + + 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); + + if (NODEPAIR) { + std::scoped_lock lock(cacheMutex); + sameGpuCache[*NODEPAIR] = same; + } + + return same; +} diff --git a/src/helpers/Drm.hpp b/src/helpers/Drm.hpp new file mode 100644 index 000000000..755ee0502 --- /dev/null +++ b/src/helpers/Drm.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + +namespace DRM { + std::optional devIDFromFD(int fd); + bool sameGpu(int fd1, int fd2); +} diff --git a/src/helpers/Format.cpp b/src/helpers/Format.cpp index ce64c113f..4ba4c4fed 100644 --- a/src/helpers/Format.cpp +++ b/src/helpers/Format.cpp @@ -1,196 +1,11 @@ #include "Format.hpp" #include #include "../includes.hpp" -#include "debug/Log.hpp" +#include "debug/log/Logger.hpp" #include "../macros.hpp" #include #include -/* - DRM formats are LE, while OGL is BE. The two primary formats - will be flipped, so we will set flipRB which will later use swizzle - to flip the red and blue channels. - This will not work on GLES2, but I want to drop support for it one day anyways. -*/ -inline const std::vector GLES3_FORMATS = { - { - .drmFormat = DRM_FORMAT_ARGB8888, - .flipRB = true, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_BYTE, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_XRGB8888, - .bytesPerBlock = 4, - }, - { - .drmFormat = DRM_FORMAT_XRGB8888, - .flipRB = true, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_BYTE, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_XRGB8888, - .bytesPerBlock = 4, - }, - { - .drmFormat = DRM_FORMAT_XBGR8888, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_BYTE, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_XBGR8888, - .bytesPerBlock = 4, - }, - { - .drmFormat = DRM_FORMAT_ABGR8888, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_BYTE, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_XBGR8888, - .bytesPerBlock = 4, - }, - { - .drmFormat = DRM_FORMAT_BGR888, - .glFormat = GL_RGB, - .glType = GL_UNSIGNED_BYTE, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_BGR888, - .bytesPerBlock = 3, - }, - { - .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, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_SHORT_4_4_4_4, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_RGBX4444, - .bytesPerBlock = 2, - }, - { - .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, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_SHORT_5_5_5_1, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_RGBX5551, - .bytesPerBlock = 2, - }, - { - .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, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_INT_2_10_10_10_REV, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_XBGR2101010, - .bytesPerBlock = 4, - }, - { - .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, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_INT_2_10_10_10_REV, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_XRGB2101010, - .bytesPerBlock = 4, - }, - { - .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, - .glFormat = GL_RGBA, - .glType = GL_HALF_FLOAT, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_XBGR16161616F, - .bytesPerBlock = 8, - }, - { - .drmFormat = DRM_FORMAT_ABGR16161616F, - .glFormat = GL_RGBA, - .glType = GL_HALF_FLOAT, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_XBGR16161616F, - .bytesPerBlock = 8, - }, - { - .drmFormat = DRM_FORMAT_XBGR16161616, - .glFormat = GL_RGBA16UI, - .glType = GL_UNSIGNED_SHORT, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_XBGR16161616, - .bytesPerBlock = 8, - }, - { - .drmFormat = DRM_FORMAT_ABGR16161616, - .glFormat = GL_RGBA16UI, - .glType = GL_UNSIGNED_SHORT, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_XBGR16161616, - .bytesPerBlock = 8, - }, - { - .drmFormat = DRM_FORMAT_YVYU, - .bytesPerBlock = 4, - .blockSize = {2, 1}, - }, - { - .drmFormat = DRM_FORMAT_VYUY, - .bytesPerBlock = 4, - .blockSize = {2, 1}, - }, - { - .drmFormat = DRM_FORMAT_R8, - .bytesPerBlock = 1, - }, - { - .drmFormat = DRM_FORMAT_GR88, - .bytesPerBlock = 2, - }, - { - .drmFormat = DRM_FORMAT_RGB888, - .bytesPerBlock = 3, - }, - { - .drmFormat = DRM_FORMAT_BGR888, - .bytesPerBlock = 3, - }, - { - .drmFormat = DRM_FORMAT_RGBX4444, - .bytesPerBlock = 2, - }, -}; - SHMFormat NFormatUtils::drmToShm(DRMFormat drm) { switch (drm) { case DRM_FORMAT_XRGB8888: return WL_SHM_FORMAT_XRGB8888; @@ -211,66 +26,58 @@ DRMFormat NFormatUtils::shmToDRM(SHMFormat shm) { return shm; } -const SPixelFormat* NFormatUtils::getPixelFormatFromDRM(DRMFormat drm) { - for (auto const& fmt : GLES3_FORMATS) { - if (fmt.drmFormat == drm) - return &fmt; +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; } - - return nullptr; -} - -const SPixelFormat* NFormatUtils::getPixelFormatFromGL(uint32_t glFormat, uint32_t glType, bool alpha) { - for (auto const& fmt : GLES3_FORMATS) { - if (fmt.glFormat == sc(glFormat) && fmt.glType == sc(glType) && fmt.withAlpha == alpha) - return &fmt; - } - - return nullptr; -} - -bool NFormatUtils::isFormatOpaque(DRMFormat drm) { - const auto FMT = NFormatUtils::getPixelFormatFromDRM(drm); - if (!FMT) - return false; - - return !FMT->withAlpha; -} - -int NFormatUtils::pixelsPerBlock(const SPixelFormat* const fmt) { - return fmt->blockSize.x * fmt->blockSize.y > 0 ? fmt->blockSize.x * fmt->blockSize.y : 1; -} - -int NFormatUtils::minStride(const SPixelFormat* const fmt, int32_t width) { - return std::ceil((width * fmt->bytesPerBlock) / pixelsPerBlock(fmt)); -} - -uint32_t NFormatUtils::drmFormatToGL(DRMFormat drm) { - switch (drm) { - case DRM_FORMAT_XRGB8888: - case DRM_FORMAT_XBGR8888: return GL_RGBA; // doesn't matter, opengl is gucci in this case. - case DRM_FORMAT_XRGB2101010: - case DRM_FORMAT_XBGR2101010: return GL_RGB10_A2; - default: return GL_RGBA; - } - UNREACHABLE(); - return GL_RGBA; -} - -uint32_t NFormatUtils::glFormatToType(uint32_t gl) { - return gl != GL_RGBA ? GL_UNSIGNED_INT_2_10_10_10_REV : GL_UNSIGNED_BYTE; } std::string NFormatUtils::drmFormatName(DRMFormat drm) { - auto n = drmGetFormatName(drm); + auto n = drmGetFormatName(drm); + + if (!n) + return "unknown"; + std::string name = n; free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors) return name; } std::string NFormatUtils::drmModifierName(uint64_t mod) { - auto n = drmGetFormatModifierName(mod); + auto n = drmGetFormatModifierName(mod); + + if (!n) + return "unknown"; + std::string name = n; free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors) return name; } + +DRMFormat NFormatUtils::alphaFormat(DRMFormat prevFormat) { + switch (prevFormat) { + case DRM_FORMAT_XRGB8888: return DRM_FORMAT_ARGB8888; + case DRM_FORMAT_XBGR8888: return DRM_FORMAT_ABGR8888; + case DRM_FORMAT_BGRX8888: return DRM_FORMAT_BGRA8888; + case DRM_FORMAT_RGBX8888: return DRM_FORMAT_RGBA8888; + case DRM_FORMAT_XRGB2101010: return DRM_FORMAT_ARGB2101010; + case DRM_FORMAT_XBGR2101010: return DRM_FORMAT_ABGR2101010; + case DRM_FORMAT_RGBX1010102: return DRM_FORMAT_RGBA1010102; + case DRM_FORMAT_BGRX1010102: return DRM_FORMAT_BGRA1010102; + default: return 0; + } +} diff --git a/src/helpers/Format.hpp b/src/helpers/Format.hpp index fe68f763b..e0dfcd3a4 100644 --- a/src/helpers/Format.hpp +++ b/src/helpers/Format.hpp @@ -2,37 +2,20 @@ #include #include +#include #include "math/Math.hpp" #include using DRMFormat = uint32_t; using SHMFormat = uint32_t; -struct SPixelFormat { - DRMFormat drmFormat = 0; /* DRM_FORMAT_INVALID */ - bool flipRB = false; - int glInternalFormat = 0; - int glFormat = 0; - int glType = 0; - bool withAlpha = true; - DRMFormat alphaStripped = 0; /* DRM_FORMAT_INVALID */ - uint32_t bytesPerBlock = 0; - Vector2D blockSize; -}; - using SDRMFormat = Aquamarine::SDRMFormat; namespace NFormatUtils { - SHMFormat drmToShm(DRMFormat drm); - DRMFormat shmToDRM(SHMFormat shm); - - const SPixelFormat* getPixelFormatFromDRM(DRMFormat drm); - const SPixelFormat* getPixelFormatFromGL(uint32_t glFormat, uint32_t glType, bool alpha); - 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); + SHMFormat drmToShm(DRMFormat drm); + DRMFormat shmToDRM(SHMFormat shm); + bool isFormatYUV(uint32_t drmFormat); + 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 8632d93b3..bd17fe5dc 100644 --- a/src/helpers/MainLoopExecutor.cpp +++ b/src/helpers/MainLoopExecutor.cpp @@ -1,6 +1,7 @@ #include "MainLoopExecutor.hpp" #include "../managers/eventLoop/EventLoopManager.hpp" #include "../macros.hpp" +#include #include static int onDataRead(int fd, uint32_t mask, void* data) { @@ -11,10 +12,7 @@ static int onDataRead(int fd, uint32_t mask, void* data) { CMainLoopExecutor::CMainLoopExecutor(std::function&& callback) : m_fn(std::move(callback)) { int fds[2]; - pipe(fds); - - RASSERT(fds[0] != 0, "CMainLoopExecutor: failed to open a pipe"); - RASSERT(fds[1] != 0, "CMainLoopExecutor: failed to open a pipe"); + RASSERT(pipe(fds) == 0, "CMainLoopExecutor: failed to open a pipe"); m_event = wl_event_loop_add_fd(g_pEventLoopManager->m_wayland.loop, fds[0], WL_EVENT_READABLE, ::onDataRead, this); @@ -29,7 +27,8 @@ CMainLoopExecutor::~CMainLoopExecutor() { void CMainLoopExecutor::signal() { const char* amogus = "h"; - write(m_writeFd.get(), amogus, 1); + if (write(m_writeFd.get(), amogus, 1) < 0) + Log::logger->log(Log::ERR, "signal: failed to write to fd {}: {}", m_writeFd.get(), strerror(errno)); } void CMainLoopExecutor::onFired() { diff --git a/src/helpers/MiscFunctions.cpp b/src/helpers/MiscFunctions.cpp index 25b179e9d..d056cb199 100644 --- a/src/helpers/MiscFunctions.cpp +++ b/src/helpers/MiscFunctions.cpp @@ -4,8 +4,9 @@ #include "../Compositor.hpp" #include "../managers/TokenManager.hpp" #include "../desktop/state/FocusState.hpp" +#include "../desktop/history/WorkspaceHistoryTracker.hpp" #include "Monitor.hpp" -#include "../config/ConfigManager.hpp" +#include "../config/shared/workspace/WorkspaceRuleManager.hpp" #include "fs/FsUtils.hpp" #include #include @@ -19,10 +20,12 @@ #include #include #include +#include #ifdef HAS_EXECINFO #include #endif #include +#include #include #include "../version.h" @@ -103,7 +106,7 @@ std::optional getPlusMinusKeywordResult(std::string source, float relativ try { return relative + stof(source); } catch (...) { - Debug::log(ERR, "Invalid arg \"{}\" in getPlusMinusKeywordResult!", source); + Log::logger->log(Log::ERR, "Invalid arg \"{}\" in getPlusMinusKeywordResult!", source); return {}; } } @@ -148,16 +151,16 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { const bool same_mon = in.substr(5).contains("m"); const bool next = in.substr(5).contains("n"); if ((same_mon || next) && !Desktop::focusState()->monitor()) { - Debug::log(ERR, "Empty monitor workspace on monitor null!"); + Log::logger->log(Log::ERR, "Empty monitor workspace on monitor null!"); return {WORKSPACE_INVALID}; } std::set invalidWSes; if (same_mon) { - for (auto const& rule : g_pConfigManager->getAllWorkspaceRules()) { - const auto PMONITOR = g_pCompositor->getMonitorFromString(rule.monitor); + for (auto const& rule : Config::workspaceRuleMgr()->getAllWorkspaceRules()) { + const auto PMONITOR = g_pCompositor->getMonitorFromString(rule.m_monitor); if (PMONITOR && (PMONITOR->m_id != Desktop::focusState()->monitor()->m_id)) - invalidWSes.insert(rule.workspaceId); + invalidWSes.insert(rule.m_workspaceId); } } @@ -170,15 +173,16 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { } } } else if (in.starts_with("prev")) { - if (!Desktop::focusState()->monitor()) + auto monitor = Desktop::focusState()->monitor(); + if (!monitor) return {WORKSPACE_INVALID}; - const auto PWORKSPACE = Desktop::focusState()->monitor()->m_activeWorkspace; + const auto PWORKSPACE = monitor->m_activeWorkspace; if (!valid(PWORKSPACE)) return {WORKSPACE_INVALID}; - const auto PREVWORKSPACEIDNAME = PWORKSPACE->getPrevWorkspaceIDName(); + const auto PREVWORKSPACEIDNAME = Desktop::History::workspaceTracker()->previousWorkspaceIDName(PWORKSPACE); if (PREVWORKSPACEIDNAME.id == -1) return {WORKSPACE_INVALID}; @@ -186,14 +190,14 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { const auto PLASTWORKSPACE = g_pCompositor->getWorkspaceByID(PREVWORKSPACEIDNAME.id); if (!PLASTWORKSPACE) { - Debug::log(LOG, "previous workspace {} doesn't exist yet", PREVWORKSPACEIDNAME.id); + Log::logger->log(Log::DEBUG, "previous workspace {} doesn't exist yet", PREVWORKSPACEIDNAME.id); return {PREVWORKSPACEIDNAME.id, PREVWORKSPACEIDNAME.name}; } return {PLASTWORKSPACE->m_id, PLASTWORKSPACE->m_name}; } else if (in == "next") { if (!Desktop::focusState()->monitor() || !Desktop::focusState()->monitor()->m_activeWorkspace) { - Debug::log(ERR, "no active monitor or workspace for 'next'"); + Log::logger->log(Log::ERR, "no active monitor or workspace for 'next'"); return {WORKSPACE_INVALID}; } @@ -211,7 +215,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { if (in[0] == 'r' && (in[1] == '-' || in[1] == '+' || in[1] == '~') && isNumber(in.substr(2))) { bool absolute = in[1] == '~'; if (!Desktop::focusState()->monitor()) { - Debug::log(ERR, "Relative monitor workspace on monitor null!"); + Log::logger->log(Log::ERR, "Relative monitor workspace on monitor null!"); return {WORKSPACE_INVALID}; } @@ -233,14 +237,14 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { invalidWSes.insert(ws->m_id); } } - for (auto const& rule : g_pConfigManager->getAllWorkspaceRules()) { - const auto PMONITOR = g_pCompositor->getMonitorFromString(rule.monitor); + for (auto const& rule : Config::workspaceRuleMgr()->getAllWorkspaceRules()) { + const auto PMONITOR = g_pCompositor->getMonitorFromString(rule.m_monitor); if (!PMONITOR || PMONITOR->m_id == Desktop::focusState()->monitor()->m_id) { // Can't be invalid continue; } // WS is bound to another monitor, can't jump to this - invalidWSes.insert(rule.workspaceId); + invalidWSes.insert(rule.m_workspaceId); } // Prepare all named workspaces in case when we need them @@ -373,7 +377,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { bool absolute = in[1] == '~'; if (!Desktop::focusState()->monitor()) { - Debug::log(ERR, "Relative monitor workspace on monitor null!"); + Log::logger->log(Log::ERR, "Relative monitor workspace on monitor null!"); return {WORKSPACE_INVALID}; } @@ -445,7 +449,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { result.id = std::max(sc(PLUSMINUSRESULT.value()), 1); } else { - Debug::log(ERR, "Relative workspace on no mon!"); + Log::logger->log(Log::ERR, "Relative workspace on no mon!"); return {WORKSPACE_INVALID}; } } else if (isNumber(in)) @@ -524,12 +528,12 @@ void logSystemInfo() { uname(&unameInfo); - Debug::log(LOG, "System name: {}", std::string{unameInfo.sysname}); - Debug::log(LOG, "Node name: {}", std::string{unameInfo.nodename}); - Debug::log(LOG, "Release: {}", std::string{unameInfo.release}); - Debug::log(LOG, "Version: {}", std::string{unameInfo.version}); + Log::logger->log(Log::DEBUG, "System name: {}", std::string{unameInfo.sysname}); + Log::logger->log(Log::DEBUG, "Node name: {}", std::string{unameInfo.nodename}); + Log::logger->log(Log::DEBUG, "Release: {}", std::string{unameInfo.release}); + Log::logger->log(Log::DEBUG, "Version: {}", std::string{unameInfo.version}); - Debug::log(NONE, "\n"); + Log::logger->log(Log::DEBUG, "\n"); #if defined(__DragonFly__) || defined(__FreeBSD__) const std::string GPUINFO = execAndGet("pciconf -lv | grep -F -A4 vga"); @@ -555,16 +559,16 @@ void logSystemInfo() { #else const std::string GPUINFO = execAndGet("lspci -vnn | grep -E '(VGA|Display|3D)'"); #endif - Debug::log(LOG, "GPU information:\n{}\n", GPUINFO); + Log::logger->log(Log::DEBUG, "GPU information:\n{}\n", GPUINFO); if (GPUINFO.contains("NVIDIA")) { - Debug::log(WARN, "Warning: you're using an NVIDIA GPU. Make sure you follow the instructions on the wiki if anything is amiss.\n"); + Log::logger->log(Log::WARN, "Warning: you're using an NVIDIA GPU. Make sure you follow the instructions on the wiki if anything is amiss.\n"); } // log etc - Debug::log(LOG, "os-release:"); + Log::logger->log(Log::DEBUG, "os-release:"); - Debug::log(NONE, "{}", NFsUtils::readFileAsString("/etc/os-release").value_or("error")); + Log::logger->log(Log::DEBUG, "{}", NFsUtils::readFileAsString("/etc/os-release").value_or("error")); } int64_t getPPIDof(int64_t pid) { @@ -767,17 +771,10 @@ std::vector getBacktrace() { } void throwError(const std::string& err) { - Debug::log(CRIT, "Critical error thrown: {}", err); + Log::logger->log(Log::CRIT, "Critical error thrown: {}", err); throw std::runtime_error(err); } -bool envEnabled(const std::string& env) { - const auto ENV = getenv(env.c_str()); - if (!ENV) - return false; - return std::string(ENV) == "1"; -} - std::pair openExclusiveShm() { // Only absolute paths can be shared across different shm_open() calls std::string name = "/" + g_pTokenManager->getRandomUUID(); @@ -872,7 +869,7 @@ bool isNvidiaDriverVersionAtLeast(int threshold) { if (firstDot != std::string::npos) driverMajor = std::stoi(driverInfo.substr(0, firstDot)); - Debug::log(LOG, "Parsed NVIDIA major version: {}", driverMajor); + Log::logger->log(Log::DEBUG, "Parsed NVIDIA major version: {}", driverMajor); } catch (std::exception& e) { driverMajor = 0; // Default to 0 if parsing fails @@ -931,15 +928,21 @@ std::expected binaryNameForPid(pid_t pid) { return fullPath; } -std::string deviceNameToInternalString(std::string in) { - std::ranges::replace(in, ' ', '-'); - std::ranges::replace(in, '\n', '-'); - std::ranges::replace(in, ',', '-'); - std::ranges::transform(in, in.begin(), ::tolower); - return in; +std::string deviceNameToInternalString(const std::string& in) { + auto result = in | std::views::transform([](unsigned char ch) -> char { + switch (ch) { + case ' ': + case '\n': + case ',': return '-'; + + default: return static_cast(std::tolower(ch)); + } + }); + + return result | std::ranges::to(); } -static const std::vector PKGCONF_PATHS = {"/usr/lib/pkgconfig", "/usr/local/lib/pkgconfig"}; +static const std::vector PKGCONF_PATHS = {"/usr/lib/pkgconfig", "/usr/local/lib/pkgconfig", "/usr/lib64/pkgconfig"}; // std::string getSystemLibraryVersion(const std::string& name) { diff --git a/src/helpers/MiscFunctions.hpp b/src/helpers/MiscFunctions.hpp index 183b6face..26c5c7adc 100644 --- a/src/helpers/MiscFunctions.hpp +++ b/src/helpers/MiscFunctions.hpp @@ -35,15 +35,14 @@ Vector2D configStringToVector2D(const std::string std::optional getPlusMinusKeywordResult(std::string in, float relative); double normalizeAngleRad(double ang); std::vector getBacktrace(); -void throwError(const std::string& err); -bool envEnabled(const std::string& env); +[[noreturn]] void throwError(const std::string& err); Hyprutils::OS::CFileDescriptor allocateSHMFile(size_t len); bool allocateSHMFilePair(size_t size, Hyprutils::OS::CFileDescriptor& rw_fd_ptr, Hyprutils::OS::CFileDescriptor& ro_fd_ptr); float stringToPercentage(const std::string& VALUE, const float REL); bool isNvidiaDriverVersionAtLeast(int threshold); std::expected binaryNameForWlClient(wl_client* client); std::expected binaryNameForPid(pid_t pid); -std::string deviceNameToInternalString(std::string in); +std::string deviceNameToInternalString(const std::string& in); std::string getSystemLibraryVersion(const std::string& name); std::string getBuiltSystemLibraryNames(); bool truthy(const std::string& str); diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index abf74b023..b65cb5753 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -2,11 +2,15 @@ #include "MiscFunctions.hpp" #include "../macros.hpp" #include "SharedDefs.hpp" +#include "../helpers/TransferFunction.hpp" #include "math/Math.hpp" #include "../protocols/ColorManagement.hpp" #include "../Compositor.hpp" #include "../config/ConfigValue.hpp" #include "../config/ConfigManager.hpp" +#include "../config/shared/monitor/MonitorRuleManager.hpp" +#include "../config/shared/workspace/WorkspaceRuleManager.hpp" +#include "../config/shared/animation/AnimationTree.hpp" #include "../protocols/GammaControl.hpp" #include "../devices/ITouch.hpp" #include "../protocols/LayerShell.hpp" @@ -22,24 +26,30 @@ #include "../protocols/core/DataDevice.hpp" #include "../render/Renderer.hpp" #include "../managers/EventManager.hpp" -#include "../managers/LayoutManager.hpp" +#include "../managers/screenshare/ScreenshareManager.hpp" #include "../managers/animation/AnimationManager.hpp" #include "../managers/animation/DesktopAnimationManager.hpp" #include "../managers/input/InputManager.hpp" -#include "../hyprerror/HyprError.hpp" +#include "../errorOverlay/Overlay.hpp" +#include "../layout/LayoutManager.hpp" #include "../i18n/Engine.hpp" +#include "../helpers/cm/ColorManagement.hpp" #include "sync/SyncTimeline.hpp" #include "time/Time.hpp" -#include "../desktop/LayerSurface.hpp" +#include "../desktop/view/LayerSurface.hpp" #include "../desktop/state/FocusState.hpp" +#include "../event/EventBus.hpp" +#include "Drm.hpp" #include -#include "debug/Log.hpp" -#include "debug/HyprNotificationOverlay.hpp" +#include "debug/log/Logger.hpp" +#include "notification/NotificationOverlay.hpp" #include "MonitorFrameScheduler.hpp" +#include #include #include #include #include +#include #include #include #include @@ -49,45 +59,51 @@ using namespace Hyprutils::Utils; using namespace Hyprutils::OS; using enum NContentType::eContentType; using namespace NColorManagement; +using namespace Render::GL; +using namespace Monitor; -CMonitor::CMonitor(SP output_) : m_state(this), m_output(output_) { - g_pAnimationManager->createAnimation(0.f, m_specialFade, g_pConfigManager->getAnimationPropertyConfig("specialWorkspaceIn"), AVARDAMAGE_NONE); +CMonitor::CMonitor(SP output_) : m_state(this), m_output(output_), m_imageDescription(getDefaultImageDescription()) { + g_pAnimationManager->createAnimation(0.f, m_specialFade, Config::animationTree()->getAnimationPropertyConfig("specialWorkspaceIn"), AVARDAMAGE_NONE); m_specialFade->setUpdateCallback([this](auto) { g_pHyprRenderer->damageMonitor(m_self.lock()); }); static auto PZOOMFACTOR = CConfigValue("cursor:zoom_factor"); - g_pAnimationManager->createAnimation(*PZOOMFACTOR, m_cursorZoom, g_pConfigManager->getAnimationPropertyConfig("zoomFactor"), AVARDAMAGE_NONE); + g_pAnimationManager->createAnimation(*PZOOMFACTOR, m_cursorZoom, Config::animationTree()->getAnimationPropertyConfig("zoomFactor"), AVARDAMAGE_NONE); m_cursorZoom->setUpdateCallback([this](auto) { g_pHyprRenderer->damageMonitor(m_self.lock()); }); - g_pAnimationManager->createAnimation(0.F, m_zoomAnimProgress, g_pConfigManager->getAnimationPropertyConfig("monitorAdded"), AVARDAMAGE_NONE); + g_pAnimationManager->createAnimation(0.F, m_zoomAnimProgress, Config::animationTree()->getAnimationPropertyConfig("monitorAdded"), AVARDAMAGE_NONE); m_zoomAnimProgress->setUpdateCallback([this](auto) { g_pHyprRenderer->damageMonitor(m_self.lock()); }); - g_pAnimationManager->createAnimation(0.F, m_backgroundOpacity, g_pConfigManager->getAnimationPropertyConfig("monitorAdded"), AVARDAMAGE_NONE); + g_pAnimationManager->createAnimation(0.F, m_backgroundOpacity, Config::animationTree()->getAnimationPropertyConfig("monitorAdded"), AVARDAMAGE_NONE); m_backgroundOpacity->setUpdateCallback([this](auto) { g_pHyprRenderer->damageMonitor(m_self.lock()); }); - g_pAnimationManager->createAnimation(0.F, m_dpmsBlackOpacity, g_pConfigManager->getAnimationPropertyConfig("fadeDpms"), AVARDAMAGE_NONE); + g_pAnimationManager->createAnimation(0.F, m_dpmsBlackOpacity, Config::animationTree()->getAnimationPropertyConfig("fadeDpms"), AVARDAMAGE_NONE); m_dpmsBlackOpacity->setUpdateCallback([this](auto) { g_pHyprRenderer->damageMonitor(m_self.lock()); }); } CMonitor::~CMonitor() { m_events.destroy.emit(); - if (g_pHyprOpenGL) - g_pHyprOpenGL->destroyMonitorResources(m_self); + if (g_pHyprRenderer && g_pHyprRenderer->glBackend()) + g_pHyprRenderer->glBackend()->destroyMonitorResources(m_self); } void CMonitor::onConnect(bool noRule) { - EMIT_HOOK_EVENT("preMonitorAdded", m_self.lock()); + Event::bus()->m_events.monitor.preAdded.emit(m_self.lock()); CScopeGuard x = {[]() { g_pCompositor->arrangeMonitors(); }}; m_zoomAnimProgress->setValueAndWarp(0.F); m_zoomAnimFrameCounter = 0; - g_pEventLoopManager->doLater([] { g_pConfigManager->ensurePersistentWorkspacesPresent(); }); + g_pEventLoopManager->doLater([] { + g_pCompositor->ensurePersistentWorkspacesPresent(); + g_pCompositor->ensureWorkspacesOnAssignedMonitors(); + }); m_listeners.frame = m_output->events.frame.listen([this] { if (m_frameScheduler) m_frameScheduler->onFrame(); }); m_listeners.commit = m_output->events.commit.listen([this] { - if (true) { // FIXME: E->state->committed & WLR_OUTPUT_STATE_BUFFER - PROTO::screencopy->onOutputCommit(m_self.lock()); - PROTO::toplevelExport->onOutputCommit(m_self.lock()); - } + m_events.commit.emit(); + + // FIXME: E->state->committed & WLR_OUTPUT_STATE_BUFFER + if (true && Screenshare::mgr()) + Screenshare::mgr()->onOutputCommit(m_self.lock()); }); m_listeners.needsFrame = m_output->events.needsFrame.listen([this] { g_pCompositor->scheduleFrameForMonitor(m_self.lock(), Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); }); @@ -114,10 +130,12 @@ void CMonitor::onConnect(bool noRule) { ts = nullptr; } - if (!ts) - PROTO::presentation->onPresented(m_self.lock(), Time::steadyNow(), event.refresh, event.seq, event.flags); - else - PROTO::presentation->onPresented(m_self.lock(), Time::fromTimespec(event.when), event.refresh, event.seq, event.flags); + if (!ts) { + timespec mono{}; + clock_gettime(CLOCK_MONOTONIC, &mono); + PROTO::presentation->onPresented(m_self.lock(), mono, event.refresh, event.seq, event.flags & ~Aquamarine::IOutput::AQ_OUTPUT_PRESENT_HW_CLOCK); + } else + PROTO::presentation->onPresented(m_self.lock(), *ts, event.refresh, event.seq, event.flags); if (m_zoomAnimFrameCounter < 5) { m_zoomAnimFrameCounter++; @@ -146,14 +164,14 @@ void CMonitor::onConnect(bool noRule) { }); m_listeners.destroy = m_output->events.destroy.listen([this] { - Debug::log(LOG, "Destroy called for monitor {}", m_name); + Log::logger->log(Log::DEBUG, "Destroy called for monitor {}", m_name); onDisconnect(true); m_output = nullptr; m_renderingInitPassed = false; - Debug::log(LOG, "Removing monitor {} from realMonitors", m_name); + Log::logger->log(Log::DEBUG, "Removing monitor {} from realMonitors", m_name); std::erase_if(g_pCompositor->m_realMonitors, [&](PHLMONITOR& el) { return el.get() == this; }); }); @@ -165,8 +183,9 @@ void CMonitor::onConnect(bool noRule) { if (m_createdByUser) return; - Debug::log(LOG, "Reapplying monitor rule for {} from a state request", m_name); - applyMonitorRule(&m_activeMonitorRule, true); + Log::logger->log(Log::DEBUG, "Reapplying monitor rule for {} from a state request", m_name); + auto cpy = m_activeMonitorRule; + applyMonitorRule(std::move(cpy), true); return; } @@ -177,14 +196,14 @@ void CMonitor::onConnect(bool noRule) { m_forceSize = SIZE; - SMonitorRule rule = m_activeMonitorRule; + auto rule = m_activeMonitorRule; - if (SIZE == rule.resolution) + if (SIZE == rule.m_resolution) return; - rule.resolution = SIZE; + rule.m_resolution = SIZE; - applyMonitorRule(&rule); + applyMonitorRule(std::move(rule)); }); m_frameScheduler = makeUnique(m_self.lock()); @@ -206,10 +225,11 @@ void CMonitor::onConnect(bool noRule) { m_createdByUser = true; // should be true. WL and Headless backends should be addable / removable // get monitor rule that matches - SMonitorRule monitorRule = g_pConfigManager->getMonitorRuleFor(m_self.lock()); + auto monitorRule = Config::monitorRuleMgr()->get(m_self.lock()); - if (m_enabled && !monitorRule.disabled) { - applyMonitorRule(&monitorRule, m_pixelSize == Vector2D{}); + if (m_enabled && !monitorRule.m_disabled) { + auto cpy = monitorRule; + applyMonitorRule(std::move(cpy), m_pixelSize == Vector2D{}); m_output->state->resetExplicitFences(); m_output->state->setEnabled(true); @@ -218,13 +238,13 @@ void CMonitor::onConnect(bool noRule) { } // if it's disabled, disable and ignore - if (monitorRule.disabled) { + if (monitorRule.m_disabled) { m_output->state->resetExplicitFences(); m_output->state->setEnabled(false); if (!m_state.commit()) - Debug::log(ERR, "Couldn't commit disabled state on output {}", m_output->name); + Log::logger->log(Log::ERR, "Couldn't commit disabled state on output {}", m_output->name); m_enabled = false; @@ -233,7 +253,7 @@ void CMonitor::onConnect(bool noRule) { } if (m_output->nonDesktop) { - Debug::log(LOG, "Not configuring non-desktop output"); + Log::logger->log(Log::DEBUG, "Not configuring non-desktop output"); for (auto& [name, lease] : PROTO::lease) { if (!lease || m_output->getBackend() != lease->getBackend()) @@ -266,15 +286,17 @@ void CMonitor::onConnect(bool noRule) { m_output->state->setEnabled(true); // set mode, also applies - if (!noRule) - applyMonitorRule(&monitorRule, true); + if (!noRule) { + auto cpy = monitorRule; + applyMonitorRule(std::move(cpy), true); + } if (!m_state.commit()) - Debug::log(WARN, "state.commit() failed in CMonitor::onCommit"); + Log::logger->log(Log::WARN, "state.commit() failed in CMonitor::onCommit"); m_damage.setSize(m_transformedSize); - Debug::log(LOG, "Added new monitor with name {} at {:j0} with size {:j0}, pointer {:x}", m_output->name, m_position, m_pixelSize, rc(m_output.get())); + Log::logger->log(Log::DEBUG, "Added new monitor with name {} at {:j0} with size {:j0}, pointer {:x}", m_output->name, m_position, m_pixelSize, rc(m_output.get())); setupDefaultWS(monitorRule); @@ -282,31 +304,37 @@ void CMonitor::onConnect(bool noRule) { if (!valid(ws)) continue; - if (ws->m_lastMonitor == m_name || g_pCompositor->m_monitors.size() == 1 /* avoid lost workspaces on recover */) { + const auto CURRENTMON = ws->m_monitor.lock(); + const bool ORPHANED = !CURRENTMON || std::ranges::none_of(g_pCompositor->m_monitors, [&](const auto& mon) { return mon == CURRENTMON; }); + const bool RETURNING = ws->m_lastMonitor == m_name; + const bool RECOVERY = g_pCompositor->m_monitors.size() == 1 && ORPHANED; // temporarily recover orphaned workspaces + + if (RETURNING || RECOVERY) { g_pCompositor->moveWorkspaceToMonitor(ws, m_self.lock()); g_pDesktopAnimationManager->startAnimation(ws, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); - ws->m_lastMonitor = ""; + if (RETURNING) + ws->m_lastMonitor = ""; } } - m_scale = monitorRule.scale; + m_scale = monitorRule.m_scale; if (m_scale < 0.1) m_scale = getDefaultScale(); m_forceFullFrames = 3; // force 3 full frames to make sure there is no blinking due to double-buffering. // - if (!m_activeMonitorRule.mirrorOf.empty()) - setMirror(m_activeMonitorRule.mirrorOf); + if (!m_activeMonitorRule.m_mirrorOf.empty()) + setMirror(m_activeMonitorRule.m_mirrorOf); if (!Desktop::focusState()->monitor()) // set the last monitor if it isn't set yet Desktop::focusState()->rawMonitorFocus(m_self.lock()); g_pHyprRenderer->arrangeLayersForMonitor(m_id); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id); + g_layoutManager->recalculateMonitor(m_self.lock()); // ensure VRR (will enable if necessary) - g_pConfigManager->ensureVRR(m_self.lock()); + Config::monitorRuleMgr()->ensureVRR(m_self.lock()); // verify last mon valid bool found = false; @@ -317,18 +345,18 @@ void CMonitor::onConnect(bool noRule) { } } - Debug::log(LOG, "checking if we have seen this monitor before: {}", m_name); + Log::logger->log(Log::DEBUG, "checking if we have seen this monitor before: {}", m_name); // if we saw this monitor before, set it to the workspace it was on if (g_pCompositor->m_seenMonitorWorkspaceMap.contains(m_name)) { auto workspaceID = g_pCompositor->m_seenMonitorWorkspaceMap[m_name]; - Debug::log(LOG, "Monitor {} was on workspace {}, setting it to that", m_name, workspaceID); + Log::logger->log(Log::DEBUG, "Monitor {} was on workspace {}, setting it to that", m_name, workspaceID); auto ws = g_pCompositor->getWorkspaceByID(workspaceID); if (ws) { g_pCompositor->moveWorkspaceToMonitor(ws, m_self.lock()); changeWorkspace(ws, true, false, false); } } else - Debug::log(LOG, "Monitor {} was not on any workspace", m_name); + Log::logger->log(Log::DEBUG, "Monitor {} was not on any workspace", m_name); if (!found) Desktop::focusState()->rawMonitorFocus(m_self.lock()); @@ -341,17 +369,17 @@ void CMonitor::onConnect(bool noRule) { g_pEventManager->postEvent(SHyprIPCEvent{"monitoradded", m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"monitoraddedv2", std::format("{},{},{}", m_id, m_name, m_shortDescription)}); - EMIT_HOOK_EVENT("monitorAdded", m_self.lock()); + Event::bus()->m_events.monitor.added.emit(m_self.lock()); } void CMonitor::onDisconnect(bool destroy) { - EMIT_HOOK_EVENT("preMonitorRemoved", m_self.lock()); + Event::bus()->m_events.monitor.preRemoved.emit(m_self.lock()); CScopeGuard x = {[this]() { if (g_pCompositor->m_isShuttingDown) return; g_pEventManager->postEvent(SHyprIPCEvent{"monitorremoved", m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"monitorremovedv2", std::format("{},{},{}", m_id, m_name, m_shortDescription)}); - EMIT_HOOK_EVENT("monitorRemoved", m_self.lock()); + Event::bus()->m_events.monitor.removed.emit(m_self.lock()); g_pCompositor->scheduleMonitorStateRecheck(); }}; @@ -360,15 +388,15 @@ void CMonitor::onDisconnect(bool destroy) { if (!m_enabled || g_pCompositor->m_isShuttingDown) return; - Debug::log(LOG, "onDisconnect called for {}", m_output->name); + Log::logger->log(Log::DEBUG, "onDisconnect called for {}", m_output->name); m_events.disconnect.emit(); - if (g_pHyprOpenGL) - g_pHyprOpenGL->destroyMonitorResources(m_self); + if (g_pHyprRenderer && g_pHyprRenderer->glBackend()) + g_pHyprRenderer->glBackend()->destroyMonitorResources(m_self); // record what workspace this monitor was on if (m_activeWorkspace) { - Debug::log(LOG, "Disconnecting Monitor {} was on workspace {}", m_name, m_activeWorkspace->m_id); + Log::logger->log(Log::DEBUG, "Disconnecting Monitor {} was on workspace {}", m_name, m_activeWorkspace->m_id); g_pCompositor->m_seenMonitorWorkspaceMap[m_name] = m_activeWorkspace->m_id; } @@ -395,7 +423,7 @@ void CMonitor::onDisconnect(bool destroy) { m->setMirror(""); } - g_pConfigManager->m_wantsMonitorReload = true; + Config::monitorRuleMgr()->scheduleReload(); } m_listeners.frame.reset(); @@ -411,29 +439,34 @@ void CMonitor::onDisconnect(bool destroy) { m_layerSurfaceLayers[i].clear(); } - Debug::log(LOG, "Removed monitor {}!", m_name); + Log::logger->log(Log::DEBUG, "Removed monitor {}!", m_name); if (!BACKUPMON) { - Debug::log(WARN, "Unplugged last monitor, entering an unsafe state. Good luck my friend."); + Log::logger->log(Log::WARN, "Unplugged last monitor, entering an unsafe state. Good luck my friend."); g_pCompositor->enterUnsafeState(); } 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); } @@ -452,7 +485,7 @@ void CMonitor::onDisconnect(bool destroy) { m_output->state->setEnabled(false); if (!m_state.commit()) - Debug::log(WARN, "state.commit() failed in CMonitor::onDisconnect"); + Log::logger->log(Log::WARN, "state.commit() failed in CMonitor::onDisconnect"); if (Desktop::focusState()->monitor() == m_self) Desktop::focusState()->rawMonitorFocus(BACKUPMON ? BACKUPMON : g_pCompositor->m_unsafeOutput.lock()); @@ -470,107 +503,141 @@ void CMonitor::onDisconnect(bool destroy) { g_pHyprRenderer->m_mostHzMonitor = pMonitorMostHz; } + std::erase_if(g_pCompositor->m_monitors, [&](PHLMONITOR& el) { return el.get() == this; }); } -void CMonitor::applyCMType(NCMType::eCMType cmType, int cmSdrEotf) { - auto oldImageDescription = m_imageDescription; - static auto PSDREOTF = CConfigValue("render:cm_sdr_eotf"); - auto chosenSdrEotf = cmSdrEotf == 0 ? (*PSDREOTF > 0 ? NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22 : NColorManagement::CM_TRANSFER_FUNCTION_SRGB) : - (cmSdrEotf == 1 ? NColorManagement::CM_TRANSFER_FUNCTION_SRGB : NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22); +static NColorManagement::eTransferFunction chooseTF(NTransferFunction::eTF tf) { + const auto sdrEOTF = NTransferFunction::fromConfig(); + + switch (tf) { + case NTransferFunction::TF_GAMMA22: + case NTransferFunction::TF_FORCED_GAMMA22: return NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22; + case NTransferFunction::TF_DEFAULT: + case NTransferFunction::TF_SRGB: return NColorManagement::CM_TRANSFER_FUNCTION_SRGB; + + case NTransferFunction::TF_AUTO: // use global setting + switch (sdrEOTF) { + case NTransferFunction::TF_AUTO: return NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22; + default: return chooseTF(sdrEOTF); + } + + default: UNREACHABLE(); + } +} + +void CMonitor::applyCMType(NCMType::eCMType cmType, NTransferFunction::eTF cmSdrEotf) { + auto oldImageDescription = m_imageDescription; + const auto chosenSdrEotf = chooseTF(cmSdrEotf); + + const auto masteringPrimaries = getMasteringPrimaries(); + const NColorManagement::SImageDescription::SPCMasteringLuminances masteringLuminances = getMasteringLuminances(); + + const auto maxFALL = this->maxFALL(); + const auto maxCLL = this->maxCLL(); switch (cmType) { - case NCMType::CM_SRGB: m_imageDescription = {.transferFunction = chosenSdrEotf}; break; // assumes SImageDescription defaults to sRGB + 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_WIDE: - m_imageDescription = {.transferFunction = chosenSdrEotf, - .primariesNameSet = true, - .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, - .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020)}; + m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020), + .masteringPrimaries = masteringPrimaries, + .masteringLuminances = masteringLuminances, + .maxCLL = maxCLL, + .maxFALL = maxFALL}); break; case NCMType::CM_DCIP3: - m_imageDescription = {.transferFunction = chosenSdrEotf, - .primariesNameSet = true, - .primariesNamed = NColorManagement::CM_PRIMARIES_DCI_P3, - .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DCI_P3)}; + m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_DCI_P3, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DCI_P3), + .masteringPrimaries = masteringPrimaries, + .masteringLuminances = masteringLuminances, + .maxCLL = maxCLL, + .maxFALL = maxFALL}); break; case NCMType::CM_DP3: - m_imageDescription = {.transferFunction = chosenSdrEotf, - .primariesNameSet = true, - .primariesNamed = NColorManagement::CM_PRIMARIES_DISPLAY_P3, - .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DISPLAY_P3)}; + m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_DISPLAY_P3, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DISPLAY_P3), + .masteringPrimaries = masteringPrimaries, + .masteringLuminances = masteringLuminances, + .maxCLL = maxCLL, + .maxFALL = maxFALL}); break; case NCMType::CM_ADOBE: - m_imageDescription = {.transferFunction = chosenSdrEotf, - .primariesNameSet = true, - .primariesNamed = NColorManagement::CM_PRIMARIES_ADOBE_RGB, - .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_ADOBE_RGB)}; + m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_ADOBE_RGB, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_ADOBE_RGB), + .masteringPrimaries = masteringPrimaries, + .masteringLuminances = masteringLuminances, + .maxCLL = maxCLL, + .maxFALL = maxFALL}); break; case NCMType::CM_EDID: - m_imageDescription = {.transferFunction = chosenSdrEotf, - .primariesNameSet = true, - .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 = {.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}}; + m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, + .primariesNameSet = false, + .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, + .primaries = masteringPrimaries, + .masteringPrimaries = masteringPrimaries, + .masteringLuminances = masteringLuminances, + .maxCLL = maxCLL, + .maxFALL = maxFALL}); break; + case NCMType::CM_HDR: m_imageDescription = DEFAULT_HDR_IMAGE_DESCRIPTION; break; case NCMType::CM_HDR_EDID: - m_imageDescription = {.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ, - .primariesNameSet = false, - .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, - .primaries = m_output->parsedEDID.chromaticityCoords.has_value() ? - NColorManagement::SPCPRimaries{ - .red = {.x = m_output->parsedEDID.chromaticityCoords->red.x, .y = m_output->parsedEDID.chromaticityCoords->red.y}, - .green = {.x = m_output->parsedEDID.chromaticityCoords->green.x, .y = m_output->parsedEDID.chromaticityCoords->green.y}, - .blue = {.x = m_output->parsedEDID.chromaticityCoords->blue.x, .y = m_output->parsedEDID.chromaticityCoords->blue.y}, - .white = {.x = m_output->parsedEDID.chromaticityCoords->white.x, .y = m_output->parsedEDID.chromaticityCoords->white.y}, - } : - NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020), - .luminances = {.min = m_output->parsedEDID.hdrMetadata->desiredContentMinLuminance, - .max = m_output->parsedEDID.hdrMetadata->desiredContentMaxLuminance, - .reference = m_output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance}}; + m_imageDescription = CImageDescription::from( + {.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ, + .primariesNameSet = false, + .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, + .primaries = m_output->parsedEDID.chromaticityCoords.has_value() ? masteringPrimaries : NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020), + .masteringPrimaries = masteringPrimaries, + .luminances = {.min = DEFAULT_HDR_IMAGE_DESCRIPTION->value().getTFMinLuminance(), + .max = DEFAULT_HDR_IMAGE_DESCRIPTION->value().getTFMaxLuminance(), + .reference = DEFAULT_HDR_IMAGE_DESCRIPTION->value().getTFRefLuminance()}, + .masteringLuminances = masteringLuminances, + .maxCLL = maxCLL, + .maxFALL = maxFALL}); break; default: UNREACHABLE(); } - if (m_minLuminance >= 0) - m_imageDescription.luminances.min = m_minLuminance; - if (m_maxLuminance >= 0) - m_imageDescription.luminances.max = m_maxLuminance; - if (m_maxAvgLuminance >= 0) - m_imageDescription.luminances.reference = m_maxAvgLuminance; + if ((m_minLuminance >= 0 || m_maxLuminance >= 0 || m_maxAvgLuminance >= 0) && (cmType == NCMType::CM_HDR || cmType == NCMType::CM_HDR_EDID)) + m_imageDescription = m_imageDescription->with({ + .min = m_minLuminance >= 0 ? m_minLuminance : m_imageDescription->value().luminances.min, // + .max = m_maxLuminance >= 0 ? m_maxLuminance : m_imageDescription->value().luminances.max, // + .reference = m_imageDescription->value().luminances.reference // + }); if (oldImageDescription != m_imageDescription) { - m_imageDescription.updateId(); if (PROTO::colorManagement) PROTO::colorManagement->onMonitorImageDescriptionChanged(m_self); } } -bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { +bool CMonitor::applyMonitorRule(Config::CMonitorRule&& pMonitorRule, bool force) { static auto PDISABLESCALECHECKS = CConfigValue("debug:disable_scale_checks"); - Debug::log(LOG, "Applying monitor rule for {}", m_name); + Log::logger->log(Log::DEBUG, "Applying monitor rule for {}", m_name); - m_activeMonitorRule = *pMonitorRule; + m_activeMonitorRule = std::move(pMonitorRule); if (m_forceSize.has_value()) - m_activeMonitorRule.resolution = m_forceSize.value(); + m_activeMonitorRule.m_resolution = m_forceSize.value(); const auto RULE = &m_activeMonitorRule; // if it's disabled, disable and ignore - if (RULE->disabled) { + if (RULE->m_disabled) { if (m_enabled) onDisconnect(); @@ -585,46 +652,54 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { if (!m_enabled) { onConnect(true); // enable it. - Debug::log(LOG, "Monitor {} is disabled but is requested to be enabled", m_name); + Log::logger->log(Log::DEBUG, "Monitor {} is disabled but is requested to be enabled", m_name); force = true; } - // Check if the rule isn't already applied - // TODO: clean this up lol - if (!force && DELTALESSTHAN(m_pixelSize.x, RULE->resolution.x, 1) /* ↓ */ - && DELTALESSTHAN(m_pixelSize.y, RULE->resolution.y, 1) /* Resolution is the same */ - && m_pixelSize.x > 1 && m_pixelSize.y > 1 /* Active resolution is not invalid */ - && DELTALESSTHAN(m_refreshRate, RULE->refreshRate, 1) /* Refresh rate is the same */ - && m_setScale == RULE->scale /* Scale is the same */ - && m_autoDir == RULE->autoDir /* Auto direction is the same */ - /* position is set correctly */ - && ((DELTALESSTHAN(m_position.x, RULE->offset.x, 1) && DELTALESSTHAN(m_position.y, RULE->offset.y, 1)) || RULE->offset == Vector2D(-INT32_MAX, -INT32_MAX)) - /* other properties hadn't changed */ - && m_transform == RULE->transform && RULE->enable10bit == m_enabled10bit && RULE->cmType == m_cmType && RULE->sdrSaturation == m_sdrSaturation && - RULE->sdrBrightness == m_sdrBrightness && RULE->sdrMinLuminance == m_minLuminance && RULE->sdrMaxLuminance == m_maxLuminance && - RULE->supportsWideColor == m_supportsWideColor && RULE->supportsHDR == m_supportsHDR && RULE->minLuminance == m_minLuminance && RULE->maxLuminance == m_maxLuminance && - RULE->maxAvgLuminance == m_maxAvgLuminance && !std::memcmp(&m_customDrmMode, &RULE->drmMode, sizeof(m_customDrmMode))) { + const bool sameResolution = + DELTALESSTHAN(m_pixelSize.x, RULE->m_resolution.x, 1) && DELTALESSTHAN(m_pixelSize.y, RULE->m_resolution.y, 1) && m_pixelSize.x > 1 && m_pixelSize.y > 1; - Debug::log(LOG, "Not applying a new rule to {} because it's already applied!", m_name); + const bool sameRefreshRate = DELTALESSTHAN(m_refreshRate, RULE->m_refreshRate, 1); - setMirror(RULE->mirrorOf); + const bool sameScale = m_setScale == RULE->m_scale; + const bool sameAutoDir = m_autoDir == RULE->m_autoDir; + + const bool samePosition = + (DELTALESSTHAN(m_position.x, RULE->m_offset.x, 1) && DELTALESSTHAN(m_position.y, RULE->m_offset.y, 1)) || RULE->m_offset == Vector2D(-INT32_MAX, -INT32_MAX); + + const bool sameTransform = m_transform == RULE->m_transform; + const bool sameColorProps = RULE->m_enable10bit == m_enabled10bit && RULE->m_cmType == m_cmType && RULE->m_sdrSaturation == m_sdrSaturation && + RULE->m_sdrBrightness == m_sdrBrightness && RULE->m_sdrMinLuminance == m_minLuminance && RULE->m_sdrMaxLuminance == m_maxLuminance && + RULE->m_supportsWideColor == m_supportsWideColor && RULE->m_supportsHDR == m_supportsHDR && RULE->m_minLuminance == m_minLuminance && + RULE->m_maxLuminance == m_maxLuminance && RULE->m_maxAvgLuminance == m_maxAvgLuminance; + + const bool sameDrmMode = !std::memcmp(&m_customDrmMode, &RULE->m_drmMode, sizeof(m_customDrmMode)); + + const bool sameReservedArea = m_reservedArea == RULE->m_reservedArea; + + if (!force && sameResolution && sameRefreshRate && sameScale && sameAutoDir && samePosition && sameTransform && sameColorProps && sameDrmMode && sameReservedArea) { + + Log::logger->log(Log::DEBUG, "Not applying a new rule to {} because it's already applied!", m_name); + + setMirror(RULE->m_mirrorOf); return true; } bool autoScale = false; - if (RULE->scale > 0.1) { - m_scale = RULE->scale; - } else { + if (RULE->m_scale > 0.1) + m_scale = RULE->m_scale; + else { autoScale = true; const auto DEFAULTSCALE = getDefaultScale(); m_scale = DEFAULTSCALE; } - m_setScale = m_scale; - m_transform = RULE->transform; - m_autoDir = RULE->autoDir; + m_setScale = m_scale; + m_transform = RULE->m_transform; + m_autoDir = RULE->m_autoDir; + m_reservedArea = RULE->m_reservedArea; // accumulate requested modes in reverse order (cause inesrting at front is inefficient) std::vector> requestedModes; @@ -641,11 +716,11 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { // last fallback is always preferred mode if (!m_output->preferredMode()) - Debug::log(ERR, "Monitor {} has NO PREFERRED MODE", m_output->name); + Log::logger->log(Log::ERR, "Monitor {} has NO PREFERRED MODE", m_output->name); else requestedModes.push_back(m_output->preferredMode()); - if (RULE->resolution == Vector2D()) { + if (RULE->m_resolution == Vector2D()) { requestedStr = "preferred"; // fallback to first 3 modes if preferred fails/doesn't exist @@ -656,7 +731,7 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { if (m_output->preferredMode()) requestedModes.push_back(m_output->preferredMode()); - } else if (RULE->resolution == Vector2D(-1, -1)) { + } else if (RULE->m_resolution == Vector2D(-1, -1)) { requestedStr = "highrr"; // sort prioritizing refresh rate 1st and resolution 2nd, then add best 3 @@ -667,7 +742,7 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { return true; return false; }); - } else if (RULE->resolution == Vector2D(-1, -2)) { + } else if (RULE->m_resolution == Vector2D(-1, -2)) { requestedStr = "highres"; // sort prioritizing resolution 1st and refresh rate 2nd, then add best 3 @@ -679,7 +754,7 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { return true; return false; }); - } else if (RULE->resolution == Vector2D(-1, -3)) { + } else if (RULE->m_resolution == Vector2D(-1, -3)) { requestedStr = "maxwidth"; // sort prioritizing widest resolution 1st and refresh rate 2nd, then add best 3 @@ -690,17 +765,17 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { return true; return false; }); - } else if (RULE->resolution != Vector2D()) { + } else if (RULE->m_resolution != Vector2D()) { // user requested mode - requestedStr = std::format("{:X0}@{:.2f}Hz", RULE->resolution, RULE->refreshRate); + requestedStr = std::format("{:X0}@{:.2f}Hz", RULE->m_resolution, RULE->m_refreshRate); // sort by closeness to requested, then add best 3 addBest3Modes([&](auto const& a, auto const& b) { - if (abs(a->pixelSize.x - RULE->resolution.x) < abs(b->pixelSize.x - RULE->resolution.x)) + if (abs(a->pixelSize.x - RULE->m_resolution.x) < abs(b->pixelSize.x - RULE->m_resolution.x)) return true; - if (a->pixelSize.x == b->pixelSize.x && abs(a->pixelSize.y - RULE->resolution.y) < abs(b->pixelSize.y - RULE->resolution.y)) + if (a->pixelSize.x == b->pixelSize.x && abs(a->pixelSize.y - RULE->m_resolution.y) < abs(b->pixelSize.y - RULE->m_resolution.y)) return true; - if (a->pixelSize == b->pixelSize && abs((a->refreshRate / 1000.f) - RULE->refreshRate) < abs((b->refreshRate / 1000.f) - RULE->refreshRate)) + if (a->pixelSize == b->pixelSize && abs((a->refreshRate / 1000.f) - RULE->m_refreshRate) < abs((b->refreshRate / 1000.f) - RULE->m_refreshRate)) return true; return false; }); @@ -708,18 +783,19 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { // if the best mode isn't close to requested, then try requested as custom mode first if (!requestedModes.empty()) { auto bestMode = requestedModes.back(); - if (!DELTALESSTHAN(bestMode->pixelSize.x, RULE->resolution.x, 1) || !DELTALESSTHAN(bestMode->pixelSize.y, RULE->resolution.y, 1) || - !DELTALESSTHAN(bestMode->refreshRate / 1000.f, RULE->refreshRate, 1)) - requestedModes.push_back(makeShared(Aquamarine::SOutputMode{.pixelSize = RULE->resolution, .refreshRate = RULE->refreshRate * 1000.f})); + if (!DELTALESSTHAN(bestMode->pixelSize.x, RULE->m_resolution.x, 1) || !DELTALESSTHAN(bestMode->pixelSize.y, RULE->m_resolution.y, 1) || + !DELTALESSTHAN(bestMode->refreshRate / 1000.f, RULE->m_refreshRate, 1)) + requestedModes.push_back( + makeShared(Aquamarine::SOutputMode{.pixelSize = RULE->m_resolution, .refreshRate = RULE->m_refreshRate * 1000.f})); } // then if requested is custom, try custom mode first - if (RULE->drmMode.type == DRM_MODE_TYPE_USERDEF) { + if (RULE->m_drmMode.type == DRM_MODE_TYPE_USERDEF) { if (m_output->getBackend()->type() != Aquamarine::eBackendType::AQ_BACKEND_DRM) - Debug::log(ERR, "Tried to set custom modeline on non-DRM output"); + Log::logger->log(Log::ERR, "Tried to set custom modeline on non-DRM output"); else - requestedModes.push_back(makeShared( - Aquamarine::SOutputMode{.pixelSize = {RULE->drmMode.hdisplay, RULE->drmMode.vdisplay}, .refreshRate = RULE->drmMode.vrefresh, .modeInfo = RULE->drmMode})); + requestedModes.push_back(makeShared(Aquamarine::SOutputMode{ + .pixelSize = {RULE->m_drmMode.hdisplay, RULE->m_drmMode.vdisplay}, .refreshRate = RULE->m_drmMode.vrefresh, .modeInfo = RULE->m_drmMode})); } } @@ -736,13 +812,13 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { m_drmFormat = DRM_FORMAT_XRGB8888; m_output->state->resetExplicitFences(); - if (Debug::m_trace) { - Debug::log(TRACE, "Monitor {} requested modes:", m_name); + if (Env::isTrace()) { + Log::logger->log(Log::TRACE, "Monitor {} requested modes:", m_name); if (requestedModes.empty()) - Debug::log(TRACE, "| None"); + Log::logger->log(Log::TRACE, "| None"); else { for (auto const& mode : requestedModes | std::views::reverse) { - Debug::log(TRACE, "| {:X0}@{:.2f}Hz", mode->pixelSize, mode->refreshRate / 1000.f); + Log::logger->log(Log::TRACE, "| {:X0}@{:.2f}Hz", mode->pixelSize, mode->refreshRate / 1000.f); } } } @@ -751,21 +827,21 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { std::string modeStr = std::format("{:X0}@{:.2f}Hz", mode->pixelSize, mode->refreshRate / 1000.f); if (mode->modeInfo.has_value() && mode->modeInfo->type == DRM_MODE_TYPE_USERDEF) { - m_output->state->setCustomMode(mode); + m_state.applyCustomModeWithSwapchain(mode); if (!m_state.test()) { - Debug::log(ERR, "Monitor {}: REJECTED custom mode {}!", m_name, modeStr); + Log::logger->log(Log::ERR, "Monitor {}: REJECTED custom mode {}!", m_name, modeStr); continue; } m_customDrmMode = mode->modeInfo.value(); } else { - m_output->state->setMode(mode); + m_state.applyModeWithSwapchain(mode); if (!m_state.test()) { - Debug::log(ERR, "Monitor {}: REJECTED available mode {}!", m_name, modeStr); + Log::logger->log(Log::ERR, "Monitor {}: REJECTED available mode {}!", m_name, modeStr); if (mode->preferred) - Debug::log(ERR, "Monitor {}: REJECTED preferred mode!!!", m_name); + Log::logger->log(Log::ERR, "Monitor {}: REJECTED preferred mode!!!", m_name); continue; } @@ -779,25 +855,25 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { success = true; if (mode->preferred) - Debug::log(LOG, "Monitor {}: requested {}, using preferred mode {}", m_name, requestedStr, modeStr); + Log::logger->log(Log::DEBUG, "Monitor {}: requested {}, using preferred mode {}", m_name, requestedStr, modeStr); else if (mode->modeInfo.has_value() && mode->modeInfo->type == DRM_MODE_TYPE_USERDEF) - Debug::log(LOG, "Monitor {}: requested {}, using custom mode {}", m_name, requestedStr, modeStr); + Log::logger->log(Log::DEBUG, "Monitor {}: requested {}, using custom mode {}", m_name, requestedStr, modeStr); else - Debug::log(LOG, "Monitor {}: requested {}, using available mode {}", m_name, requestedStr, modeStr); + Log::logger->log(Log::DEBUG, "Monitor {}: requested {}, using available mode {}", m_name, requestedStr, modeStr); break; } // try requested as custom mode jic it works - if (!success && RULE->resolution != Vector2D() && RULE->resolution != Vector2D(-1, -1) && RULE->resolution != Vector2D(-1, -2)) { - auto refreshRate = m_output->getBackend()->type() == Aquamarine::eBackendType::AQ_BACKEND_DRM ? RULE->refreshRate * 1000 : 0; - auto mode = makeShared(Aquamarine::SOutputMode{.pixelSize = RULE->resolution, .refreshRate = refreshRate}); + if (!success && RULE->m_resolution != Vector2D() && RULE->m_resolution != Vector2D(-1, -1) && RULE->m_resolution != Vector2D(-1, -2)) { + auto refreshRate = m_output->getBackend()->type() == Aquamarine::eBackendType::AQ_BACKEND_DRM ? RULE->m_refreshRate * 1000 : 0; + auto mode = makeShared(Aquamarine::SOutputMode{.pixelSize = RULE->m_resolution, .refreshRate = refreshRate}); std::string modeStr = std::format("{:X0}@{:.2f}Hz", mode->pixelSize, mode->refreshRate / 1000.f); - m_output->state->setCustomMode(mode); + m_state.applyCustomModeWithSwapchain(mode); if (m_state.test()) { - Debug::log(LOG, "Monitor {}: requested {}, using custom mode {}", m_name, requestedStr, modeStr); + Log::logger->log(Log::DEBUG, "Monitor {}: requested {}, using custom mode {}", m_name, requestedStr, modeStr); refreshRate = mode->refreshRate / 1000.f; m_size = mode->pixelSize; @@ -806,21 +882,21 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { success = true; } else - Debug::log(ERR, "Monitor {}: REJECTED custom mode {}!", m_name, modeStr); + Log::logger->log(Log::ERR, "Monitor {}: REJECTED custom mode {}!", m_name, modeStr); } // try any of the modes if none of the above work if (!success) { for (auto const& mode : m_output->modes) { - m_output->state->setMode(mode); + m_state.applyModeWithSwapchain(mode); if (!m_state.test()) continue; auto errorMessage = I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_MONITOR_MODE_FAIL, {{"name", m_name}, {"mode", std::format("{:X0}@{:.2f}Hz", mode->pixelSize, mode->refreshRate / 1000.f)}}); - Debug::log(WARN, errorMessage); - g_pHyprNotificationOverlay->addNotification(errorMessage, CHyprColor(0xff0000ff), 5000, ICON_WARNING); + Log::logger->log(Log::WARN, errorMessage); + Notification::overlay()->addNotification(errorMessage, CHyprColor(0xff0000ff), 5000, ICON_WARNING); m_refreshRate = mode->refreshRate / 1000.f; m_size = mode->pixelSize; @@ -834,7 +910,7 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { } if (!success) { - Debug::log(ERR, "Monitor {} has NO FALLBACK MODES, and an INVALID one was requested: {:X0}@{:.2f}Hz", m_name, RULE->resolution, RULE->refreshRate); + Log::logger->log(Log::ERR, "Monitor {} has NO FALLBACK MODES, and an INVALID one was requested: {:X0}@{:.2f}Hz", m_name, RULE->m_resolution, RULE->m_refreshRate); return true; } @@ -856,16 +932,16 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { bool set10bit = false; - for (auto const& fmt : formats[sc(!RULE->enable10bit)]) { + for (auto const& fmt : formats[sc(!RULE->m_enable10bit)]) { m_output->state->setFormat(fmt.second); m_prevDrmFormat = m_drmFormat; m_drmFormat = fmt.second; if (!m_state.test()) { - Debug::log(ERR, "output {} failed basic test on format {}", m_name, fmt.first); + Log::logger->log(Log::ERR, "output {} failed basic test on format {}", m_name, fmt.first); } else { - Debug::log(LOG, "output {} succeeded basic test on format {}", m_name, fmt.first); - if (RULE->enable10bit && fmt.first.contains("101010")) + Log::logger->log(Log::DEBUG, "output {} succeeded basic test on format {}", m_name, fmt.first); + if (RULE->m_enable10bit && fmt.first.contains("101010")) set10bit = true; break; } @@ -873,32 +949,49 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { m_enabled10bit = set10bit; - m_supportsWideColor = RULE->supportsHDR; - m_supportsHDR = RULE->supportsHDR; + m_supportsWideColor = RULE->m_supportsWideColor; + m_supportsHDR = RULE->m_supportsHDR; - m_cmType = RULE->cmType; - switch (m_cmType) { - case NCMType::CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? NCMType::CM_WIDE : NCMType::CM_SRGB; break; - case NCMType::CM_EDID: m_cmType = m_output->parsedEDID.chromaticityCoords.has_value() ? NCMType::CM_EDID : NCMType::CM_SRGB; break; - case NCMType::CM_HDR: - case NCMType::CM_HDR_EDID: m_cmType = supportsHDR() ? m_cmType : NCMType::CM_SRGB; break; - default: break; + if (RULE->m_iccFile.empty()) { + // only apply explicit cm settings if we have no icc file + + m_cmType = RULE->m_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->m_sdrEotf; + + m_sdrMinLuminance = RULE->m_sdrMinLuminance; + m_sdrMaxLuminance = RULE->m_sdrMaxLuminance; + + m_minLuminance = RULE->m_minLuminance; + m_maxLuminance = RULE->m_maxLuminance; + m_maxAvgLuminance = RULE->m_maxAvgLuminance; + + applyCMType(m_cmType, m_sdrEotf); + + m_sdrSaturation = RULE->m_sdrSaturation; + m_sdrBrightness = RULE->m_sdrBrightness; + } else { + auto image = NColorManagement::SImageDescription::fromICC(RULE->m_iccFile); + if (!image) { + Log::logger->log(Log::ERR, "icc for {} ({}) failed: {}", m_name, RULE->m_iccFile, image.error()); + ErrorOverlay::overlay()->queueError(std::format("failed to apply icc {} to {}: {}", RULE->m_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->m_iccFile, image.error()); + ErrorOverlay::overlay()->queueError(std::format("failed to apply icc {} to {}: {}", RULE->m_iccFile, m_name, image.error())); + m_imageDescription = CImageDescription::from(SImageDescription{}); + } + } } - m_sdrEotf = RULE->sdrEotf; - - m_sdrMinLuminance = RULE->sdrMinLuminance; - m_sdrMaxLuminance = RULE->sdrMaxLuminance; - - m_minLuminance = RULE->minLuminance; - m_maxLuminance = RULE->maxLuminance; - m_maxAvgLuminance = RULE->maxAvgLuminance; - - applyCMType(m_cmType, m_sdrEotf); - - m_sdrSaturation = RULE->sdrSaturation; - m_sdrBrightness = RULE->sdrBrightness; - Vector2D logicalSize = m_pixelSize / m_scale; if (!*PDISABLESCALECHECKS && (logicalSize.x != std::round(logicalSize.x) || logicalSize.y != std::round(logicalSize.y))) { // invalid scale, will produce fractional pixels. @@ -936,19 +1029,20 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { if (autoScale) m_scale = std::round(scaleZero); else { - Debug::log(ERR, "Invalid scale passed to monitor, {} failed to find a clean divisor", m_scale); - g_pConfigManager->addParseError("Invalid scale passed to monitor " + m_name + ", failed to find a clean divisor"); + Log::logger->log(Log::ERR, "Invalid scale passed to monitor, {} failed to find a clean divisor", m_scale); + ErrorOverlay::overlay()->queueError("Invalid scale passed to monitor " + m_name + ", failed to find a clean divisor"); m_scale = getDefaultScale(); } } else { if (!autoScale) { - Debug::log(ERR, "Invalid scale passed to monitor, {} found suggestion {}", m_scale, searchScale); + Log::logger->log(Log::ERR, "Invalid scale passed to monitor, {} found suggestion {}", m_scale, searchScale); static auto PDISABLENOTIFICATION = CConfigValue("misc:disable_scale_notification"); - if (!*PDISABLENOTIFICATION) - g_pHyprNotificationOverlay->addNotification( + if (!*PDISABLENOTIFICATION) { + Notification::overlay()->addNotification( I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, {{"name", m_name}, {"scale", std::format("{:.2f}", m_scale)}, {"fixed_scale", std::format("{:.2f}", searchScale)}}), CHyprColor(1.0, 0.0, 0.0, 1.0), 5000, ICON_WARNING); + } } m_scale = searchScale; } @@ -958,7 +1052,7 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { m_output->scheduleFrame(); if (!m_state.commit()) - Debug::log(ERR, "Couldn't commit output named {}", m_output->name); + Log::logger->log(Log::ERR, "Couldn't commit output named {}", m_output->name); Vector2D xfmd = m_transform % 2 == 1 ? Vector2D{m_pixelSize.y, m_pixelSize.x} : m_pixelSize; m_size = (xfmd / m_scale).round(); @@ -966,20 +1060,26 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { if (m_createdByUser) { CBox transformedBox = {0, 0, m_transformedSize.x, m_transformedSize.y}; - transformedBox.transform(wlTransformToHyprutils(invertTransform(m_transform)), m_transformedSize.x, m_transformedSize.y); + transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_transform)), m_transformedSize.x, m_transformedSize.y); m_pixelSize = Vector2D(transformedBox.width, transformedBox.height); } updateMatrix(); - if (WAS10B != m_enabled10bit || OLDRES != m_pixelSize) - g_pHyprOpenGL->destroyMonitorResources(m_self); + if ((WAS10B != m_enabled10bit || OLDRES != m_pixelSize)) { + m_resources.reset(); // TODO skip for 10bit change and fp16? + + if (g_pHyprRenderer && g_pHyprRenderer->glBackend()) + g_pHyprRenderer->glBackend()->destroyMonitorResources(m_self); + } g_pCompositor->scheduleMonitorStateRecheck(); 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) { @@ -991,12 +1091,12 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { g_pHyprRenderer->arrangeLayersForMonitor(m_id); // reload to fix mirrors - g_pConfigManager->m_wantsMonitorReload = true; + Config::monitorRuleMgr()->scheduleReload(); - Debug::log(LOG, "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)); + Log::logger->log(Log::DEBUG, "Monitor {} data dump: res {:X}@{:.2f}Hz, scale {:.2f}, transform {}, pos {:X}, 10b {}", m_name, m_pixelSize, m_refreshRate, m_scale, + sc(m_transform), m_position, sc(m_enabled10bit)); - EMIT_HOOK_EVENT("monitorLayoutChanged", nullptr); + Event::bus()->m_events.monitor.layoutChanged.emit(); m_events.modeChanged.emit(); @@ -1019,6 +1119,7 @@ void CMonitor::addDamage(const CBox& box) { if (m_cursorZoom->value() != 1.f && g_pCompositor->getMonitorFromCursor() == m_self) { m_damage.damageEntire(); g_pCompositor->scheduleFrameForMonitor(m_self.lock(), Aquamarine::IOutput::AQ_SCHEDULE_DAMAGE); + return; } if (m_damage.damage(box)) @@ -1030,8 +1131,10 @@ bool CMonitor::shouldSkipScheduleFrameOnMouseEvent() { static auto PMINRR = CConfigValue("cursor:min_refresh_rate"); // skip scheduling extra frames for fullsreen apps with vrr - const bool shouldSkip = inFullscreenMode() && (*PNOBREAK == 1 || (*PNOBREAK == 2 && m_activeWorkspace->getFullscreenWindow()->getContentType() == CONTENT_TYPE_GAME)) && - m_output->state->state().adaptiveSync; + const auto FS_WINDOW = getFullscreenWindow(); + const bool shouldRenderCursor = g_pHyprRenderer->shouldRenderCursor(); + const bool noBreak = FS_WINDOW && (*PNOBREAK == 1 || (*PNOBREAK == 2 && FS_WINDOW->getContentType() == CONTENT_TYPE_GAME)); + const bool shouldSkip = (!shouldRenderCursor || noBreak) && m_output->state->state().adaptiveSync; // keep requested minimum refresh rate if (shouldSkip && *PMINRR && m_lastPresentationTimer.getMillis() > 1000.0f / *PMINRR) { @@ -1064,7 +1167,7 @@ WORKSPACEID CMonitor::findAvailableDefaultWS() { if (g_pCompositor->getWorkspaceByID(i)) continue; - if (const auto BOUND = g_pConfigManager->getBoundMonitorStringForWS(std::to_string(i)); !BOUND.empty() && BOUND != m_name) + if (const auto BOUND = Config::workspaceRuleMgr()->getBoundMonitorStringForWS(std::to_string(i)); !BOUND.empty() && BOUND != m_name) continue; return i; @@ -1073,14 +1176,14 @@ WORKSPACEID CMonitor::findAvailableDefaultWS() { return LONG_MAX; // shouldn't be reachable } -void CMonitor::setupDefaultWS(const SMonitorRule& monitorRule) { +void CMonitor::setupDefaultWS(const Config::CMonitorRule& monitorRule) { // Workspace std::string newDefaultWorkspaceName = ""; int64_t wsID = WORKSPACE_INVALID; - if (g_pConfigManager->getDefaultWorkspaceFor(m_name).empty()) + if (Config::workspaceRuleMgr()->getDefaultWorkspaceFor(m_name).empty()) wsID = findAvailableDefaultWS(); else { - const auto ws = getWorkspaceIDNameFromString(g_pConfigManager->getDefaultWorkspaceFor(m_name)); + const auto ws = getWorkspaceIDNameFromString(Config::workspaceRuleMgr()->getDefaultWorkspaceFor(m_name)); wsID = ws.id; newDefaultWorkspaceName = ws.name; } @@ -1089,18 +1192,19 @@ void CMonitor::setupDefaultWS(const SMonitorRule& monitorRule) { wsID = std::ranges::distance(g_pCompositor->getWorkspaces()) + 1; newDefaultWorkspaceName = std::to_string(wsID); - Debug::log(LOG, "Invalid workspace= directive name in monitor parsing, workspace name \"{}\" is invalid.", g_pConfigManager->getDefaultWorkspaceFor(m_name)); + Log::logger->log(Log::DEBUG, "Invalid workspace= directive name in monitor parsing, workspace name \"{}\" is invalid.", + Config::workspaceRuleMgr()->getDefaultWorkspaceFor(m_name)); } auto PNEWWORKSPACE = g_pCompositor->getWorkspaceByID(wsID); - Debug::log(LOG, "New monitor: WORKSPACEID {}, exists: {}", wsID, sc(PNEWWORKSPACE != nullptr)); + Log::logger->log(Log::DEBUG, "New monitor: WORKSPACEID {}, exists: {}", wsID, sc(PNEWWORKSPACE != nullptr)); if (PNEWWORKSPACE) { // workspace exists, move it to the newly connected monitor g_pCompositor->moveWorkspaceToMonitor(PNEWWORKSPACE, m_self.lock()); m_activeWorkspace = PNEWWORKSPACE; - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id); + g_layoutManager->recalculateMonitor(m_self.lock()); g_pDesktopAnimationManager->startAnimation(PNEWWORKSPACE, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); } else { if (newDefaultWorkspaceName.empty()) @@ -1123,12 +1227,12 @@ void CMonitor::setMirror(const std::string& mirrorOf) { return; if (PMIRRORMON && PMIRRORMON->isMirror()) { - Debug::log(ERR, "Cannot mirror a mirror!"); + Log::logger->log(Log::ERR, "Cannot mirror a mirror!"); return; } if (PMIRRORMON == m_self) { - Debug::log(ERR, "Cannot mirror self!"); + Log::logger->log(Log::ERR, "Cannot mirror self!"); return; } @@ -1145,9 +1249,9 @@ void CMonitor::setMirror(const std::string& mirrorOf) { m_mirrorOf.reset(); // set rule - const auto RULE = g_pConfigManager->getMonitorRuleFor(m_self.lock()); + const auto RULE = Config::monitorRuleMgr()->get(m_self.lock()); - m_position = RULE.offset; + m_position = RULE.m_offset; // push to mvmonitors @@ -1163,13 +1267,13 @@ void CMonitor::setMirror(const std::string& mirrorOf) { RASSERT(thisWrapper->get(), "CMonitor::setMirror: Had no wrapper???"); - if (std::ranges::find_if(g_pCompositor->m_monitors, [&](auto& other) { return other.get() == this; }) == g_pCompositor->m_monitors.end()) { + if (std::ranges::find_if(g_pCompositor->m_monitors, [&](auto& other) { return other.get() == this; }) == g_pCompositor->m_monitors.end()) g_pCompositor->m_monitors.push_back(*thisWrapper); - } setupDefaultWS(RULE); - applyMonitorRule(const_cast(&RULE), true); // will apply the offset and stuff + auto cpy = RULE; + applyMonitorRule(std::move(cpy), true); // will apply the offset and stuff } else { PHLMONITOR BACKUPMON = nullptr; for (auto const& m : g_pCompositor->m_monitors) { @@ -1256,7 +1360,7 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo if (pWorkspace->m_isSpecialWorkspace) { if (m_activeSpecialWorkspace != pWorkspace) { - Debug::log(LOG, "changeworkspace on special, togglespecialworkspace to id {}", pWorkspace->m_id); + Log::logger->log(Log::DEBUG, "changeworkspace on special, togglespecialworkspace to id {}", pWorkspace->m_id); setSpecialWorkspace(pWorkspace); } return; @@ -1277,14 +1381,15 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo if (!internal) { const auto ANIMTOLEFT = POLDWORKSPACE && (shouldWraparound(pWorkspace->m_id, POLDWORKSPACE->m_id) ^ (pWorkspace->m_id > POLDWORKSPACE->m_id)); + const auto ANIMSTYLE = pWorkspace->m_animationStyle; if (POLDWORKSPACE) - g_pDesktopAnimationManager->startAnimation(POLDWORKSPACE, CDesktopAnimationManager::ANIMATION_TYPE_OUT, ANIMTOLEFT); - g_pDesktopAnimationManager->startAnimation(pWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_IN, ANIMTOLEFT); + g_pDesktopAnimationManager->startAnimation(POLDWORKSPACE, CDesktopAnimationManager::ANIMATION_TYPE_OUT, ANIMTOLEFT, false, ANIMSTYLE); + g_pDesktopAnimationManager->startAnimation(pWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_IN, ANIMTOLEFT, false, ANIMSTYLE); // move pinned windows for (auto const& w : g_pCompositor->m_windows) { if (w->m_workspace == POLDWORKSPACE && w->m_pinned) - w->moveToWorkspace(pWorkspace); + w->layoutTarget()->assignToSpace(pWorkspace->m_space); } if (!noFocus && !Desktop::focusState()->monitor()->m_activeSpecialWorkspace && @@ -1294,7 +1399,8 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo if (!pWindow) { if (*PFOLLOWMOUSE == 1) - pWindow = g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING); + pWindow = g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), + Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING); if (!pWindow) pWindow = pWorkspace->getTopLeftWindow(); @@ -1303,17 +1409,23 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo pWindow = pWorkspace->getFirstWindow(); } - Desktop::focusState()->fullWindowFocus(pWindow); + Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND); } if (!noMouseMove) g_pInputManager->simulateMouseMovement(); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id); + g_layoutManager->recalculateMonitor(m_self.lock()); g_pEventManager->postEvent(SHyprIPCEvent{"workspace", pWorkspace->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"workspacev2", std::format("{},{}", pWorkspace->m_id, pWorkspace->m_name)}); - EMIT_HOOK_EVENT("workspace", pWorkspace); + Event::bus()->m_events.workspace.active.emit(pWorkspace); + } + + // set all LSes as not above fullscreen on workspace changes + for (auto const& ls : g_pCompositor->m_layers) { + if (ls->m_monitor == m_self) + ls->m_aboveFullscreen = false; } pWorkspace->m_events.activeChanged.emit(); @@ -1323,7 +1435,7 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo g_pDesktopAnimationManager->setFullscreenFadeAnimation( pWorkspace, pWorkspace->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT); - g_pConfigManager->ensureVRR(m_self.lock()); + Config::monitorRuleMgr()->ensureVRR(m_self.lock()); g_pCompositor->updateSuspendedStates(); @@ -1342,7 +1454,7 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { const auto POLDSPECIAL = m_activeSpecialWorkspace; - m_specialFade->setConfig(g_pConfigManager->getAnimationPropertyConfig(pWorkspace ? "specialWorkspaceIn" : "specialWorkspaceOut")); + m_specialFade->setConfig(Config::animationTree()->getAnimationPropertyConfig(pWorkspace ? "specialWorkspaceIn" : "specialWorkspaceOut")); *m_specialFade = pWorkspace ? 1.F : 0.F; g_pHyprRenderer->damageMonitor(m_self.lock()); @@ -1354,17 +1466,23 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { g_pDesktopAnimationManager->startAnimation(m_activeSpecialWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_OUT, false); g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + m_name}); + + // Reset layer surface state when closing special workspace + for (auto const& ls : g_pCompositor->m_layers) { + if (ls->m_monitor == m_self) + ls->m_aboveFullscreen = false; + } } m_activeSpecialWorkspace.reset(); if (POLDSPECIAL) POLDSPECIAL->m_events.activeChanged.emit(); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id); + g_layoutManager->recalculateMonitor(m_self.lock()); if (!(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) { if (const auto PLAST = m_activeWorkspace->getLastFocusedWindow(); PLAST) - Desktop::focusState()->fullWindowFocus(PLAST); + Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND); else g_pInputManager->refocus(); } @@ -1372,7 +1490,7 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { g_pDesktopAnimationManager->setFullscreenFadeAnimation( m_activeWorkspace, m_activeWorkspace->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT); - g_pConfigManager->ensureVRR(m_self.lock()); + Config::monitorRuleMgr()->ensureVRR(m_self.lock()); g_pCompositor->updateSuspendedStates(); @@ -1386,14 +1504,22 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { bool wasActive = false; //close if open elsewhere - const auto PMONITORWORKSPACEOWNER = pWorkspace->m_monitor.lock(); - if (const auto PMWSOWNER = pWorkspace->m_monitor.lock(); PMWSOWNER && PMWSOWNER->m_activeSpecialWorkspace == pWorkspace) { - PMWSOWNER->m_activeSpecialWorkspace.reset(); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PMWSOWNER->m_id); - g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + PMWSOWNER->m_name}); - g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + PMWSOWNER->m_name}); + const auto PMONITOR = pWorkspace->m_monitor.lock(); - const auto PACTIVEWORKSPACE = PMWSOWNER->m_activeWorkspace; + if (PMONITOR && PMONITOR->m_activeSpecialWorkspace == pWorkspace) { + PMONITOR->m_activeSpecialWorkspace.reset(); + g_layoutManager->recalculateMonitor(PMONITOR); + g_pHyprRenderer->damageMonitor(PMONITOR); + g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + PMONITOR->m_name}); + g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + PMONITOR->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 == PMONITOR) + ls->m_aboveFullscreen = false; + } + + const auto PACTIVEWORKSPACE = PMONITOR->m_activeWorkspace; g_pDesktopAnimationManager->setFullscreenFadeAnimation(PACTIVEWORKSPACE, PACTIVEWORKSPACE && PACTIVEWORKSPACE->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT); @@ -1406,10 +1532,16 @@ 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(); - if (PMONITORWORKSPACEOWNER != m_self) + if (PMONITOR != m_self) pWorkspace->m_events.monitorChanged.emit(); if (!wasActive) @@ -1436,17 +1568,16 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { } else pos = pos - PMONFROMMIDDLE->m_position + m_position; - *w->m_realPosition = pos; - w->m_position = pos; + w->layoutTarget()->setPositionGlobal(CBox{pos, w->layoutTarget()->position().size()}); } } } - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id); + g_layoutManager->recalculateMonitor(m_self.lock()); if (!(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) { if (const auto PLAST = pWorkspace->getLastFocusedWindow(); PLAST) - Desktop::focusState()->fullWindowFocus(PLAST); + Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND); else g_pInputManager->refocus(); } @@ -1459,7 +1590,7 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { g_pDesktopAnimationManager->setFullscreenFadeAnimation( pWorkspace, pWorkspace->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT); - g_pConfigManager->ensureVRR(m_self.lock()); + Config::monitorRuleMgr()->ensureVRR(m_self.lock()); g_pCompositor->updateSuspendedStates(); } @@ -1472,37 +1603,24 @@ void CMonitor::moveTo(const Vector2D& pos) { m_position = pos; } -SWorkspaceIDName CMonitor::getPrevWorkspaceIDName(const WORKSPACEID id) { - while (!m_prevWorkSpaces.empty()) { - const int PREVID = m_prevWorkSpaces.top(); - m_prevWorkSpaces.pop(); - if (PREVID == id) // skip same workspace - continue; - - // recheck if previous workspace's was moved to another monitor - const auto ws = g_pCompositor->getWorkspaceByID(PREVID); - if (ws && ws->monitorID() == m_id) - return {.id = PREVID, .name = ws->m_name}; - } - - return {.id = WORKSPACE_INVALID}; -} - -void CMonitor::addPrevWorkspaceID(const WORKSPACEID id) { - if (!m_prevWorkSpaces.empty() && m_prevWorkSpaces.top() == id) - return; - - m_prevWorkSpaces.emplace(id); -} - 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(wlTransformToHyprutils(m_transform)).translate(-m_transformedSize / 2.0); + 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() { @@ -1517,8 +1635,8 @@ CBox CMonitor::logicalBox() { return {m_position, m_size}; } -CBox CMonitor::logicalBoxMinusExtents() { - return {m_position + m_reservedTopLeft, m_size - m_reservedTopLeft - m_reservedBottomRight}; +CBox CMonitor::logicalBoxMinusReserved() { + return m_reservedArea.apply(logicalBox()); } void CMonitor::scheduleDone() { @@ -1546,25 +1664,7 @@ void CMonitor::setCTM(const Mat3x3& ctm_) { } uint32_t CMonitor::isSolitaryBlocked(bool full) { - uint32_t reasons = 0; - - if (g_pHyprNotificationOverlay->hasAny()) { - reasons |= SC_NOTIFICATION; - if (!full) - return reasons; - } - - if (g_pHyprError->active() && Desktop::focusState()->monitor() == m_self) { - reasons |= SC_ERRORBAR; - if (!full) - return reasons; - } - - if (g_pSessionLockManager->isSessionLocked()) { - reasons |= SC_LOCK; - if (!full) - return reasons; - } + uint32_t reasons = 0; const auto PWORKSPACE = m_activeWorkspace; if (!PWORKSPACE) { @@ -1578,14 +1678,32 @@ uint32_t CMonitor::isSolitaryBlocked(bool full) { return reasons; } - if (PROTO::data->dndActive()) { - reasons |= SC_DND; + if (m_activeSpecialWorkspace) { + reasons |= SC_SPECIAL; if (!full) return reasons; } - if (m_activeSpecialWorkspace) { - reasons |= SC_SPECIAL; + if (Notification::overlay()->hasAny()) { + reasons |= SC_NOTIFICATION; + if (!full) + return reasons; + } + + if (ErrorOverlay::overlay()->active() && Desktop::focusState()->monitor() == m_self) { + reasons |= SC_ERRORBAR; + if (!full) + return reasons; + } + + if (g_pSessionLockManager->isSessionLocked()) { + reasons |= SC_LOCK; + if (!full) + return reasons; + } + + if (PROTO::data->dndActive()) { + reasons |= SC_DND; if (!full) return reasons; } @@ -1665,10 +1783,15 @@ uint32_t CMonitor::isSolitaryBlocked(bool full) { void CMonitor::recheckSolitary() { m_solitaryClient.reset(); // reset it, if we find one it will be set. + + const auto PWORKSPACE = m_activeWorkspace; + if (!PWORKSPACE) + return; + if (isSolitaryBlocked()) return; - m_solitaryClient = m_activeWorkspace->getFullscreenWindow(); + m_solitaryClient = PWORKSPACE->getFullscreenWindow(); } uint8_t CMonitor::isTearingBlocked(bool full) { @@ -1685,15 +1808,15 @@ uint8_t CMonitor::isTearingBlocked(bool full) { if (!*PTEARINGENABLED) { reasons |= TC_USER; if (!full) { - Debug::log(WARN, "Tearing commit requested but the master switch general:allow_tearing is off, ignoring"); + Log::logger->log(Log::WARN, "Tearing commit requested but the master switch general:allow_tearing is off, ignoring"); return reasons; } } - if (g_pHyprOpenGL->m_renderData.mouseZoomFactor != 1.0) { + if (g_pHyprRenderer->m_renderData.mouseZoomFactor != 1.0) { reasons |= TC_ZOOM; if (!full) { - Debug::log(WARN, "Tearing commit requested but scale factor is not 1, ignoring"); + Log::logger->log(Log::WARN, "Tearing commit requested but scale factor is not 1, ignoring"); return reasons; } } @@ -1701,11 +1824,15 @@ uint8_t CMonitor::isTearingBlocked(bool full) { if (!m_tearingState.canTear) { reasons |= TC_SUPPORT; if (!full) { - Debug::log(WARN, "Tearing commit requested but monitor doesn't support it, ignoring"); + Log::logger->log(Log::WARN, "Tearing commit requested but monitor doesn't support it, ignoring"); return reasons; } } + // 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; @@ -1726,8 +1853,16 @@ bool CMonitor::updateTearing() { uint16_t CMonitor::isDSBlocked(bool full) { uint16_t reasons = 0; static auto PDIRECTSCANOUT = CConfigValue("render:direct_scanout"); - static auto PPASS = CConfigValue("render:cm_fs_passthrough"); static auto PNONSHADER = CConfigValue("render:non_shader_cm"); + const auto PWORKSPACE = m_activeWorkspace; + + // Fast reject for the hot render path; full=true callers still collect + // the remaining blockers for hyprctl/debug output below. + if (!canAttemptDirectScanoutFast()) { + reasons |= DS_BLOCK_CANDIDATE; + if (!full) + return reasons; + } if (*PDIRECTSCANOUT == 0) { reasons |= DS_BLOCK_USER; @@ -1736,23 +1871,17 @@ uint16_t CMonitor::isDSBlocked(bool full) { } if (*PDIRECTSCANOUT == 2) { - if (!m_activeWorkspace || !m_activeWorkspace->m_hasFullscreenWindow || m_activeWorkspace->m_fullscreenMode != FSMODE_FULLSCREEN) { + if (!PWORKSPACE || !PWORKSPACE->m_hasFullscreenWindow || PWORKSPACE->m_fullscreenMode != FSMODE_FULLSCREEN) { reasons |= DS_BLOCK_WINDOWED; if (!full) return reasons; - } else if (m_activeWorkspace->getFullscreenWindow()->getContentType() != CONTENT_TYPE_GAME) { + } else if (PWORKSPACE->getFullscreenWindow()->getContentType() != CONTENT_TYPE_GAME) { reasons |= DS_BLOCK_CONTENT; if (!full) return reasons; } } - if (m_tearingState.activelyTearing) { - reasons |= DS_BLOCK_TEARING; - if (!full) - return reasons; - } - if (!m_mirrors.empty() || isMirror()) { reasons |= DS_BLOCK_MIRROR; if (!full) @@ -1791,45 +1920,51 @@ uint16_t CMonitor::isDSBlocked(bool full) { // we can't scanout shm buffers. const auto params = PSURFACE->m_current.buffer->dmabuf(); - if (!params.success || !PSURFACE->m_current.texture->m_eglImage /* dmabuf */) { + if (!params.success || !PSURFACE->m_current.texture->isDMA() /* dmabuf */) { reasons |= DS_BLOCK_DMA; if (!full) return reasons; } - if (needsCM() && *PNONSHADER != CM_NS_IGNORE && !canNoShaderCM() && (!inHDR() || (PSURFACE->m_colorManagement.valid() && PSURFACE->m_colorManagement->isWindowsScRGB())) && - *PPASS != 1) - reasons |= DS_BLOCK_CM; + const bool surfaceIsHDR = PSURFACE->m_colorManagement.valid() && PSURFACE->m_colorManagement->isHDR(); + const bool surfaceIsScRGB = surfaceIsHDR && PSURFACE->m_colorManagement->isWindowsScRGB(); + + if (surfaceIsScRGB) + reasons |= DS_BLOCK_CM; // block scRGB + else if (*PNONSHADER != CM_NS_IGNORE) { + if (!surfaceIsHDR && needsCM() && !canNoShaderCM(true)) + reasons |= DS_BLOCK_CM; // block SDR that needs CM while non-shader CM isn't available + else if (surfaceIsHDR && !inHDR()) + reasons |= DS_BLOCK_CM; // block HDR while monitor isn't in HDR mode + } return reasons; } bool CMonitor::attemptDirectScanout() { - const auto blockedReason = isDSBlocked(); + static const auto PSAME = CConfigValue("debug:ds_handle_same_buffer"); + static const auto PSAMEFIFO = CConfigValue("debug:ds_handle_same_buffer_fifo"); + + const auto blockedReason = isDSBlocked(); if (blockedReason) return false; const auto PCANDIDATE = m_solitaryClient.lock(); const auto PSURFACE = PCANDIDATE->getSolitaryResource(); - const auto params = PSURFACE->m_current.buffer->dmabuf(); - - Debug::log(TRACE, "attemptDirectScanout: surface {:x} passed, will attempt, buffer {} fmt: {} -> {} (mod {})", rc(PSURFACE.get()), - rc(PSURFACE->m_current.buffer.m_buffer.get()), m_drmFormat, params.format, params.modifier); - - auto PBUFFER = PSURFACE->m_current.buffer.m_buffer; + auto PBUFFER = PSURFACE->m_current.buffer.m_buffer; // #TODO this entire bit needs figuring out, vrr goes down the drain without it - if (PBUFFER == m_output->state->state().buffer) { + if (PBUFFER == m_output->state->state().buffer && *PSAME) { PSURFACE->presentFeedback(Time::steadyNow(), m_self.lock()); if (m_scanoutNeedsCursorUpdate) { if (!m_state.test()) { - Debug::log(TRACE, "attemptDirectScanout: failed basic test on cursor update"); + Log::logger->log(Log::TRACE, "attemptDirectScanout: failed basic test on cursor update"); return false; } if (!m_output->commit()) { - Debug::log(TRACE, "attemptDirectScanout: failed to commit cursor update"); + Log::logger->log(Log::TRACE, "attemptDirectScanout: failed to commit cursor update"); m_lastScanout.reset(); return false; } @@ -1838,12 +1973,17 @@ bool CMonitor::attemptDirectScanout() { } //#TODO this entire bit is bootleg deluxe, above bit is to not make vrr go down the drain, returning early here means fifo gets forever locked. - if (PSURFACE->m_fifo) + if (PSURFACE->m_fifo && !m_tearingState.activelyTearing && *PSAMEFIFO) PSURFACE->m_stateQueue.unlockFirst(LOCK_REASON_FIFO); return true; } + const auto params = PSURFACE->m_current.buffer->dmabuf(); + + Log::logger->log(Log::TRACE, "attemptDirectScanout: surface {:x} passed, will attempt, buffer {} fmt: {} -> {} (mod {})", rc(PSURFACE.get()), + rc(PSURFACE->m_current.buffer.m_buffer.get()), m_drmFormat, params.format, params.modifier); + // FIXME: make sure the buffer actually follows the available scanout dmabuf formats // and comes from the appropriate device. This may implode on multi-gpu!! @@ -1851,39 +1991,51 @@ bool CMonitor::attemptDirectScanout() { if (m_lastScanout.expired()) m_prevDrmFormat = m_drmFormat; + const bool NEEDS_TEST = !m_lastScanout || m_drmFormat != params.format; // do not retest while it's active if (m_drmFormat != params.format) { m_output->state->setFormat(params.format); m_drmFormat = params.format; } m_output->state->setBuffer(PBUFFER); - Debug::log(TRACE, "attemptDirectScanout: setting presentation mode"); + Log::logger->log(Log::TRACE, "attemptDirectScanout: setting presentation mode"); m_output->state->setPresentationMode(m_tearingState.activelyTearing ? Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE : Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_VSYNC); - if (!m_state.test()) { - Debug::log(TRACE, "attemptDirectScanout: failed basic test"); + if (NEEDS_TEST && !m_state.test()) { + Log::logger->log(Log::TRACE, "attemptDirectScanout: failed basic test"); return false; } PSURFACE->presentFeedback(Time::steadyNow(), m_self.lock()); m_output->state->addDamage(PSURFACE->m_current.accumulateBufferDamage()); - m_output->state->resetExplicitFences(); + + // multigpu needs a fence to trigger fence syncing blits and also committing with the recreated dgpu fence + if (g_pHyprRenderer->explicitSyncSupported() && isMultiGPU()) { + auto sync = g_pHyprRenderer->createSyncFDManager(); + + if (sync->fd().isValid()) { + m_inFence = sync->takeFd(); + m_output->state->setExplicitInFence(m_inFence.get()); + } else + m_output->state->resetExplicitFences(); // good luck. + } else + m_output->state->resetExplicitFences(); // no need to do explicit sync here as surface current can only ever be ready to read bool ok = m_output->commit(); if (!ok) { - Debug::log(TRACE, "attemptDirectScanout: failed to scanout surface"); + Log::logger->log(Log::TRACE, "attemptDirectScanout: failed to scanout surface"); m_lastScanout.reset(); return false; } if (m_lastScanout.expired()) { m_lastScanout = PCANDIDATE; - Debug::log(LOG, "Entered a direct scanout to {:x}: \"{}\"", rc(PCANDIDATE.get()), PCANDIDATE->m_title); + Log::logger->log(Log::DEBUG, "Entered a direct scanout to {:x}: \"{}\"", rc(PCANDIDATE.get()), PCANDIDATE->m_title); } m_scanoutNeedsCursorUpdate = false; @@ -1902,6 +2054,67 @@ bool CMonitor::attemptDirectScanout() { return true; } +bool CMonitor::canAttemptDirectScanoutFast() const { + return !m_solitaryClient.expired() || !m_lastScanout.expired() || m_directScanoutIsActive; +} + +bool CMonitor::isMultiGPU() { + if (!m_output || !g_pCompositor) + return false; + + const auto PREFERREDALLOCATOR = m_output->getBackend()->preferredAllocator(); + const int allocatorFD = PREFERREDALLOCATOR ? PREFERREDALLOCATOR->drmFD() : -1; + const int compositorFD = g_pCompositor->m_drm.fd; + + if (allocatorFD < 0 || compositorFD < 0) { + m_cachedAllocatorDRMDev.reset(); + m_cachedCompositorDRMDev.reset(); + m_cachedAllocatorDRMFD = allocatorFD; + m_cachedCompositorDRMFD = compositorFD; + m_cachedSameGPU = true; + return false; + } + + const auto allocatorDev = DRM::devIDFromFD(allocatorFD); + const auto compositorDev = DRM::devIDFromFD(compositorFD); + + // AQ can reopen DRM nodes for refcounting, so raw fd numbers are not a stable cache key. + const bool useDeviceIDCache = allocatorDev.has_value() && compositorDev.has_value(); + const bool cacheStale = !m_cachedSameGPU || + (useDeviceIDCache ? m_cachedAllocatorDRMDev != allocatorDev || m_cachedCompositorDRMDev != compositorDev : + m_cachedAllocatorDRMFD != allocatorFD || m_cachedCompositorDRMFD != compositorFD); + + if (cacheStale) { + m_cachedAllocatorDRMDev = allocatorDev; + m_cachedCompositorDRMDev = compositorDev; + m_cachedAllocatorDRMFD = allocatorFD; + m_cachedCompositorDRMFD = compositorFD; + m_cachedSameGPU = DRM::sameGpu(allocatorFD, compositorFD); + } + + return !*m_cachedSameGPU; +} + +bool CMonitor::shouldUseSoftwareCursors() { + static auto PNOHW = CConfigValue("cursor:no_hardware_cursors"); + static auto PINVISIBLE = CConfigValue("cursor:invisible"); + + if (m_tearingState.activelyTearing) + return true; + + if (*PINVISIBLE != 0) + return true; + + switch (*PNOHW) { + case 0: return false; + case 1: return true; + case 2: return g_pHyprRenderer->isNvidia() && (g_pHyprRenderer->isMgpu() || g_pCompositor->isVRRActiveOnAnyMonitor()); + default: break; + } + + return true; +} + void CMonitor::setDPMS(bool on) { // Don't trigger animation if the target state is the same if (m_dpmsStatus == on) @@ -1939,7 +2152,7 @@ void CMonitor::commitDPMSState(bool state) { m_output->state->setEnabled(state); if (!m_state.commit()) { - Debug::log(ERR, "Couldn't commit output {} for DPMS = {}, will retry.", m_name, state); + Log::logger->log(Log::ERR, "Couldn't commit output {} for DPMS = {}, will retry.", m_name, state); // retry in 2 frames. This could happen when the DRM backend rejects our commit // because disable + enable were sent almost instantly @@ -1953,7 +2166,7 @@ void CMonitor::commitDPMSState(bool state) { m_output->state->resetExplicitFences(); m_output->state->setEnabled(m_dpmsStatus); if (!m_state.commit()) { - Debug::log(ERR, "Couldn't retry committing output {} for DPMS = {}", m_name, m_dpmsStatus); + Log::logger->log(Log::ERR, "Couldn't retry committing output {} for DPMS = {}", m_name, m_dpmsStatus); return; } @@ -1970,8 +2183,8 @@ void CMonitor::commitDPMSState(bool state) { } void CMonitor::debugLastPresentation(const std::string& message) { - Debug::log(TRACE, "{} (last presentation {} - {} fps)", message, m_lastPresentationTimer.getMillis(), - m_lastPresentationTimer.getMillis() > 0 ? 1000.0f / m_lastPresentationTimer.getMillis() : 0.0f); + Log::logger->log(Log::TRACE, "{} (last presentation {} - {} fps)", message, m_lastPresentationTimer.getMillis(), + m_lastPresentationTimer.getMillis() > 0 ? 1000.0f / m_lastPresentationTimer.getMillis() : 0.0f); } void CMonitor::onCursorMovedOnMonitor() { @@ -1983,7 +2196,7 @@ void CMonitor::onCursorMovedOnMonitor() { // output->state->addDamage(CRegion{}); // output->state->setPresentationMode(Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE); // if (!output->commit()) - // Debug::log(ERR, "onCursorMovedOnMonitor: tearing and wanted to update cursor, failed."); + // Log::logger->log(Log::ERR, "onCursorMovedOnMonitor: tearing and wanted to update cursor, failed."); // FIXME: try to do the above. We currently can't just render because drm is a fucking bitch // and throws a "nO pRoP cAn Be ChAnGeD dUrInG AsYnC fLiP" on crtc_x @@ -1993,11 +2206,22 @@ void CMonitor::onCursorMovedOnMonitor() { } bool CMonitor::supportsWideColor() { - return m_supportsWideColor || m_output->parsedEDID.supportsBT2020; + switch (m_supportsWideColor) { + case -1: return false; + case 1: return true; + default: return m_output->parsedEDID.supportsBT2020; + } } bool CMonitor::supportsHDR() { - return supportsWideColor() && (m_supportsHDR || (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->supportsPQ : false)); + if (!supportsWideColor()) + return false; + + switch (m_supportsHDR) { + case -1: return false; + case 1: return true; + default: return m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->supportsPQ : false; + } } float CMonitor::minLuminance(float defaultValue) { @@ -2013,8 +2237,16 @@ 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.primariesNamed == CM_PRIMARIES_BT2020); + return supportsWideColor() && (wantsHDR() || m_imageDescription->value().primariesNamed == CM_PRIMARIES_BT2020); } bool CMonitor::wantsHDR() { @@ -2026,20 +2258,63 @@ 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; } -std::optional CMonitor::getFSImageDescription() { +PHLWINDOW CMonitor::getFullscreenWindow() { + // Check special workspace first since it renders on top of regular workspaces + if (m_activeSpecialWorkspace && m_activeSpecialWorkspace->m_hasFullscreenWindow && m_activeSpecialWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN) + return m_activeSpecialWorkspace->getFullscreenWindow(); + if (m_activeWorkspace && m_activeWorkspace->m_hasFullscreenWindow && m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN) + return m_activeWorkspace->getFullscreenWindow(); + return nullptr; +} + +std::optional CMonitor::getFSImageDescription() { if (!inFullscreenMode()) return {}; - const auto FS_WINDOW = m_activeWorkspace->getFullscreenWindow(); + const auto FS_WINDOW = getFullscreenWindow(); if (!FS_WINDOW) - return {}; // should be unreachable + return {}; - const auto ROOT_SURF = FS_WINDOW->m_wlSurface->resource(); + const auto ROOT_SURF = FS_WINDOW->wlSurface()->resource(); const auto SURF = ROOT_SURF->findWithCM(); - return SURF ? SURF->m_colorManagement->imageDescription() : SImageDescription{}; + return SURF ? NColorManagement::CImageDescription::from(SURF->m_colorManagement->imageDescription()) : getDefaultImageDescription(); +} + +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), + }; +} + +uint32_t CMonitor::getPreferredReadFormat() { + static const auto PFORCE8BIT = CConfigValue("misc:screencopy_force_8b"); + + auto monFmt = 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; + + return monFmt; } bool CMonitor::needsCM() { @@ -2047,8 +2322,20 @@ bool CMonitor::needsCM() { return SRC_DESC.has_value() && SRC_DESC.value() != m_imageDescription; } +static bool isCompatibleTF(eTransferFunction sourceTF, eTransferFunction targetTF) { + static auto PNONSHADER = CConfigValue("render:non_shader_cm"); + const auto sdrEOTF = NTransferFunction::fromConfig(); + return sourceTF == targetTF // same + || (sdrEOTF == NTransferFunction::TF_FORCED_GAMMA22 && sourceTF == NColorManagement::CM_TRANSFER_FUNCTION_SRGB // forced source gamma22 to output gamma22 + && targetTF == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22) // + || (*PNONSHADER == CM_NS_ONDEMAND // FIXME incorrect but good enough for DS + && (sourceTF == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22 || sourceTF == NColorManagement::CM_TRANSFER_FUNCTION_SRGB) // + && (targetTF == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22 || targetTF == NColorManagement::CM_TRANSFER_FUNCTION_SRGB)) // + ; +} + // TODO support more drm properties -bool CMonitor::canNoShaderCM() { +bool CMonitor::canNoShaderCM(bool forDSmode) { static auto PNONSHADER = CConfigValue("render:non_shader_cm"); if (*PNONSHADER == CM_NS_DISABLE) return false; @@ -2057,33 +2344,104 @@ bool CMonitor::canNoShaderCM() { if (!SRC_DESC.has_value()) return false; - if (SRC_DESC.value() == m_imageDescription) + const auto& DST_DESC = forDSmode ? m_imageDescription : resources()->m_imageDescription; + if (SRC_DESC.value() == DST_DESC) return true; // no CM needed - if (SRC_DESC->icc.fd >= 0 || m_imageDescription.icc.fd >= 0) - return false; // no ICC support + const auto& SRC_DESC_VALUE = SRC_DESC.value()->value(); + + if (m_imageDescription->value().icc.present) + return false; + + Log::logger->log(Log::TRACE, "CM: can no shder compares src={} to output={}", SRC_DESC_VALUE, m_imageDescription->value()); // only primaries differ - if (SRC_DESC->transferFunction == m_imageDescription.transferFunction && SRC_DESC->transferFunctionPower == m_imageDescription.transferFunctionPower && - (!inHDR() || SRC_DESC->luminances == m_imageDescription.luminances) && SRC_DESC->masteringLuminances == m_imageDescription.masteringLuminances && - SRC_DESC->maxCLL == m_imageDescription.maxCLL && SRC_DESC->maxFALL == m_imageDescription.maxFALL) - return true; - - return false; + return ( + isCompatibleTF(SRC_DESC_VALUE.transferFunction, DST_DESC->value().transferFunction) // + && SRC_DESC_VALUE.transferFunctionPower == DST_DESC->value().transferFunctionPower // + && (!inHDR() || SRC_DESC_VALUE.luminances == DST_DESC->value().luminances) + // not used by shaders atm + // && SRC_DESC_VALUE.masteringLuminances == m_imageDescription->value().masteringLuminances && SRC_DESC_VALUE.maxCLL == m_imageDescription->value().maxCLL && SRC_DESC_VALUE.maxFALL == m_imageDescription->value().maxFALL + ); } bool CMonitor::doesNoShaderCM() { return m_noShaderCTM; } +static std::vector resampleInterleavedToKms(const SVCGTTable16& t, size_t gammaSize) { + std::vector out; + out.resize(gammaSize * 3); + + // + auto sample = [&](int c, float x) -> uint16_t { + const float maxX = t.entries - 1; + x = std::clamp(x, 0.F, maxX); + + const size_t i0 = (size_t)std::floor(x); + const size_t i1 = std::min(i0 + 1, (size_t)t.entries - 1); + const float f = x - sc(i0); + + const float v0 = sc(t.ch[c][i0]); + const float v1 = sc(t.ch[c][i1]); + const float v = v0 + ((v1 - v0) * f); + + int64_t vi = std::round(v); + vi = std::clamp(vi, sc(0), sc(65535)); + return sc(vi); + }; + + for (size_t i = 0; i < gammaSize; ++i) { + float x = sc(i) * sc(t.entries - 1) / sc(gammaSize - 1); + + const uint16_t r = sample(0, x); + const uint16_t g = sample(1, x); + const uint16_t b = sample(2, x); + + out[i * 3 + 0] = r; + out[i * 3 + 1] = g; + out[i * 3 + 2] = b; + } + + return out; +} + +void CMonitor::updateVCGTRamps() { + auto gammaSize = m_output->getGammaSize(); + + if (gammaSize <= 10) { + Log::logger->log(Log::DEBUG, "CMonitor::updateVCGTRamps: skipping, no gamma ramp for output"); + return; + } + + if (!m_imageDescription->value().icc.vcgt) { + if (m_vcgtRampsSet) + m_output->state->setGammaLut({}); + + m_vcgtRampsSet = false; + return; + } + + // build table + auto table = resampleInterleavedToKms(*m_imageDescription->value().icc.vcgt, gammaSize); + + m_output->state->setGammaLut(table); + + m_vcgtRampsSet = true; +} + +bool CMonitor::gammaRampsInUse() { + return m_vcgtRampsSet; +} + CMonitorState::CMonitorState(CMonitor* owner) : m_owner(owner) { ; } void CMonitorState::ensureBufferPresent() { - const auto STATE = m_owner->m_output->state->state(); + const auto& STATE = m_owner->m_output->state->state(); if (!STATE.enabled) { - Debug::log(TRACE, "CMonitorState::ensureBufferPresent: Ignoring, monitor is not enabled"); + Log::logger->log(Log::TRACE, "CMonitorState::ensureBufferPresent: Ignoring, monitor is not enabled"); return; } @@ -2094,7 +2452,7 @@ void CMonitorState::ensureBufferPresent() { // this is required for modesetting being possible and might be missing in case of first tests in the renderer // where we test modes and buffers - Debug::log(LOG, "CMonitorState::ensureBufferPresent: no buffer or mismatched format, attaching one from the swapchain for modeset being possible"); + Log::logger->log(Log::DEBUG, "CMonitorState::ensureBufferPresent: no buffer or mismatched format, attaching one from the swapchain for modeset being possible"); m_owner->m_output->state->setBuffer(m_owner->m_output->swapchain->next(nullptr)); m_owner->m_output->swapchain->rollback(); // restore the counter, don't advance the swapchain } @@ -2103,7 +2461,7 @@ bool CMonitorState::commit() { if (!updateSwapchain()) return false; - EMIT_HOOK_EVENT("preMonitorCommit", m_owner->m_self.lock()); + Event::bus()->m_events.monitor.preCommit.emit(m_owner->m_self.lock()); ensureBufferPresent(); @@ -2121,16 +2479,76 @@ bool CMonitorState::test() { } bool CMonitorState::updateSwapchain() { - auto options = m_owner->m_output->swapchain->currentOptions(); + const auto& OPTIONS = m_owner->m_output->swapchain->currentOptions(); const auto& STATE = m_owner->m_output->state->state(); const auto& MODE = STATE.mode ? STATE.mode : STATE.customMode; if (!MODE) { - Debug::log(WARN, "updateSwapchain: No mode?"); + Log::logger->log(Log::WARN, "updateSwapchain: No mode?"); return true; } + + if (OPTIONS.format == m_owner->m_drmFormat && OPTIONS.scanout && OPTIONS.length == 3 && OPTIONS.size == MODE->pixelSize) + return true; + + auto options = OPTIONS; options.format = m_owner->m_drmFormat; options.scanout = true; options.length = 3; options.size = MODE->pixelSize; return m_owner->m_output->swapchain->reconfigure(options); } +void CMonitorState::applyModeWithSwapchain(const SP& mode) { + m_owner->m_output->state->setMode(mode); + updateSwapchain(); +} + +void CMonitorState::applyCustomModeWithSwapchain(const SP& mode) { + m_owner->m_output->state->setCustomMode(mode); + updateSwapchain(); +} + +bool CMonitor::needsACopyFB() { + return !m_mirrors.empty() || Screenshare::mgr()->isOutputBeingSSd(m_self.lock()); +} + +bool CMonitor::needsUnmodifiedCopy() { + static const auto PKEEP = CConfigValue("render:keep_unmodified_copy"); + if (*PKEEP == 1) + return true; + + const bool HAS_MODS = m_sdrMinLuminance != SDR_MIN_LUMINANCE || m_sdrMaxLuminance != SDR_MAX_LUMINANCE || (m_sdrBrightness > 0 && m_sdrBrightness != 1.0) || + (m_sdrSaturation > 0 && m_sdrSaturation != 1.0); + + if (!HAS_MODS) + return false; + + // TODO handle some FP16 cases + if (m_imageDescription->value().transferFunction != CM_TRANSFER_FUNCTION_ST2084_PQ && m_imageDescription->value().transferFunction != CM_TRANSFER_FUNCTION_HLG) + return false; + + return *PKEEP == 2 ? true : needsACopyFB(); +} + +bool CMonitor::useFP16() { + static const auto PFP16 = CConfigValue("render:use_fp16"); + bool shouldUse = *PFP16 == 1 || (*PFP16 == 2 && m_imageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ); + static bool usedBefore = shouldUse; + if (usedBefore != shouldUse) { + usedBefore = shouldUse; + m_blurFBDirty = true; + } + return shouldUse; +} + +WP CMonitor::resources() { + const auto DRM_FORMAT = useFP16() ? DRM_FORMAT_ABGR16161616F : m_output->state->state().drmFormat; + const auto DESC = useFP16() ? LINEAR_IMAGE_DESCRIPTION : m_imageDescription; + + if (!m_resources || m_resources->m_drmFormat != DRM_FORMAT || m_resources->m_size != m_pixelSize) + m_resources = makeUnique(m_self, DRM_FORMAT, m_pixelSize, DESC); + + if (m_resources->m_imageDescription != DESC) + m_resources->setImageDescription(DESC); + + return m_resources; +} diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index f1f466698..59a64237e 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -11,17 +11,29 @@ #include "CMType.hpp" #include +#include "MonitorZoomController.hpp" +#include "../render/Texture.hpp" +#include "../render/Framebuffer.hpp" +#include "MonitorResources.hpp" #include "time/Timer.hpp" #include "math/Math.hpp" +#include "../desktop/reserved/ReservedArea.hpp" #include -#include "../protocols/types/ColorManagement.hpp" +#include "cm/ColorManagement.hpp" #include "signal/Signal.hpp" #include "DamageRing.hpp" #include #include #include +#include + +#include "../helpers/TransferFunction.hpp" +#include "../config/shared/monitor/MonitorRule.hpp" class CMonitorFrameScheduler; +namespace Monitor { + class CMonitorResources; +} // Enum for the different types of auto directions, e.g. auto-left, auto-up. enum eAutoDirs : uint8_t { @@ -37,25 +49,27 @@ enum eAutoDirs : uint8_t { }; struct SMonitorRule { - eAutoDirs autoDir = DIR_AUTO_NONE; - std::string name = ""; - Vector2D resolution = Vector2D(1280, 720); - Vector2D offset = Vector2D(0, 0); - float scale = 1; - float refreshRate = 60; // Hz - bool disabled = false; - wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; - std::string mirrorOf = ""; - bool enable10bit = false; - NCMType::eCMType cmType = NCMType::CM_SRGB; - int sdrEotf = 0; - float sdrSaturation = 1.0f; // SDR -> HDR - float sdrBrightness = 1.0f; // SDR -> HDR + eAutoDirs autoDir = DIR_AUTO_NONE; + std::string name = ""; + Vector2D resolution = Vector2D(1280, 720); + Vector2D offset = Vector2D(0, 0); + float scale = 1; + float refreshRate = 60; // Hz + bool disabled = false; + wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; + std::string mirrorOf = ""; + bool enable10bit = false; + NCMType::eCMType cmType = NCMType::CM_SRGB; + NTransferFunction::eTF sdrEotf = NTransferFunction::TF_DEFAULT; + float sdrSaturation = 1.0f; // SDR -> HDR + float sdrBrightness = 1.0f; // SDR -> HDR + Desktop::CReservedArea reservedArea; + std::string iccFile; - bool supportsWideColor = false; // false does nothing, true overrides EDID - bool supportsHDR = false; // false does nothing, true overrides EDID - float sdrMinLuminance = 0.2f; // SDR -> HDR - int sdrMaxLuminance = 80; // SDR -> HDR + int supportsWideColor = 0; // 0 - auto, 1 - force enable, -1 - force disable + int supportsHDR = 0; // 0 - auto, 1 - force enable, -1 - force disable + float sdrMinLuminance = 0.2f; // SDR -> HDR + int sdrMaxLuminance = 80; // SDR -> HDR // Incorrect values will result in reduced luminance range or incorrect tonemapping. Shouldn't damage the HW. Use with care in case of a faulty monitor firmware. float minLuminance = -1.0f; // >= 0 overrides EDID @@ -68,7 +82,6 @@ struct SMonitorRule { class CMonitor; class CSyncTimeline; -class CEGLSync; class CEventLoopTimer; class CMonitorState { @@ -79,6 +92,8 @@ class CMonitorState { bool commit(); bool test(); bool updateSwapchain(); + void applyModeWithSwapchain(const SP& mode); + void applyCustomModeWithSwapchain(const SP& mode); private: void ensureBufferPresent(); @@ -93,7 +108,7 @@ class CMonitor { Vector2D m_position = Vector2D(-1, -1); // means unset Vector2D m_xwaylandPosition = Vector2D(-1, -1); // means unset - eAutoDirs m_autoDir = DIR_AUTO_NONE; + Config::eAutoDirs m_autoDir = Config::DIR_AUTO_NONE; Vector2D m_size = Vector2D(0, 0); Vector2D m_pixelSize = Vector2D(0, 0); Vector2D m_transformedSize = Vector2D(0, 0); @@ -108,11 +123,10 @@ class CMonitor { std::string m_description = ""; std::string m_shortDescription = ""; - Vector2D m_reservedTopLeft = Vector2D(0, 0); - Vector2D m_reservedBottomRight = Vector2D(0, 0); - drmModeModeInfo m_customDrmMode = {}; + Desktop::CReservedArea m_reservedArea; + CMonitorState m_state; CDamageRing m_damage; @@ -122,18 +136,20 @@ 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; uint32_t m_drmFormat = DRM_FORMAT_INVALID; uint32_t m_prevDrmFormat = DRM_FORMAT_INVALID; + CMonitorZoomController m_zoomController; + bool m_dpmsStatus = true; bool m_vrrActive = false; // this can be TRUE even if VRR is not active in the case that this display does not support it. bool m_enabled10bit = false; // as above, this can be TRUE even if 10 bit failed. NCMType::eCMType m_cmType = NCMType::CM_SRGB; - int m_sdrEotf = 0; + NTransferFunction::eTF m_sdrEotf = NTransferFunction::TF_DEFAULT; float m_sdrSaturation = 1.0f; float m_sdrBrightness = 1.0f; float m_sdrMinLuminance = 0.2f; @@ -151,7 +167,10 @@ class CMonitor { bool m_isBeingLeased = false; - SMonitorRule m_activeMonitorRule; + Config::CMonitorRule m_activeMonitorRule; + + SP m_splash; + SP m_background; // explicit sync Hyprutils::OS::CFileDescriptor m_inFence; // TODO: remove when aq uses CFileDescriptor @@ -173,6 +192,7 @@ class CMonitor { // for direct scanout PHLWINDOWREF m_lastScanout; + bool m_directScanoutIsActive = false; // for cleanup logic. m_lastScanout.expired() can become true before the DS cleanup if client crashes/exits while DS is active. bool m_scanoutNeedsCursorUpdate = false; // for special fade/blur @@ -203,6 +223,7 @@ class CMonitor { } m_tearingState; struct { + CSignalT<> commit; CSignalT<> destroy; CSignalT<> connect; CSignalT<> disconnect; @@ -228,9 +249,8 @@ class CMonitor { DS_BLOCK_SURFACE = (1 << 8), DS_BLOCK_TRANSFORM = (1 << 9), DS_BLOCK_DMA = (1 << 10), - DS_BLOCK_TEARING = (1 << 11), - DS_BLOCK_FAILED = (1 << 12), - DS_BLOCK_CM = (1 << 13), + DS_BLOCK_FAILED = (1 << 11), + DS_BLOCK_CM = (1 << 12), DS_CHECKS_COUNT = 14, }; @@ -271,15 +291,16 @@ class CMonitor { TC_SUPPORT = (1 << 4), TC_CANDIDATE = (1 << 5), TC_WINDOW = (1 << 6), + TC_HW_CURSOR = (1 << 7), - TC_CHECKS_COUNT = 7, + TC_CHECKS_COUNT = 8, }; // methods void onConnect(bool noRule); void onDisconnect(bool destroy = false); - void applyCMType(NCMType::eCMType cmType, int cmSdrEotf); - bool applyMonitorRule(SMonitorRule* pMonitorRule, bool force = false); + void applyCMType(NCMType::eCMType cmType, NTransferFunction::eTF cmSdrEotf); + bool applyMonitorRule(Config::CMonitorRule&& pMonitorRule, bool force = false); void addDamage(const pixman_region32_t* rg); void addDamage(const CRegion& rg); void addDamage(const CBox& box); @@ -294,11 +315,10 @@ class CMonitor { void setSpecialWorkspace(const WORKSPACEID& id); void moveTo(const Vector2D& pos); Vector2D middle(); - void updateMatrix(); WORKSPACEID activeWorkspaceID(); WORKSPACEID activeSpecialWorkspaceID(); CBox logicalBox(); - CBox logicalBoxMinusExtents(); + CBox logicalBoxMinusReserved(); void scheduleDone(); uint32_t isSolitaryBlocked(bool full = false); void recheckSolitary(); @@ -306,37 +326,66 @@ class CMonitor { bool updateTearing(); uint16_t isDSBlocked(bool full = false); bool attemptDirectScanout(); + bool canAttemptDirectScanoutFast() const; + bool isMultiGPU(); void setCTM(const Mat3x3& ctm); void onCursorMovedOnMonitor(); void setDPMS(bool on); + bool shouldUseSoftwareCursors(); - void debugLastPresentation(const std::string& message); + // + const Mat3x3& getTransformMatrix(); + const Mat3x3& getScaleMatrix(); - bool supportsWideColor(); - bool supportsHDR(); - float minLuminance(float defaultValue = 0); - int maxLuminance(int defaultValue = 80); - int maxAvgLuminance(int defaultValue = 80); + void debugLastPresentation(const std::string& message); - bool wantsWideColor(); - bool wantsHDR(); + bool supportsWideColor(); + bool supportsHDR(); + float minLuminance(float defaultValue = 0); + int maxLuminance(int defaultValue = 80); + int maxAvgLuminance(int defaultValue = 80); + float maxFALL(); + float maxCLL(); - bool inHDR(); + bool wantsWideColor(); + bool wantsHDR(); - /// Has an active workspace with a real fullscreen window - bool inFullscreenMode(); - std::optional getFSImageDescription(); + bool inHDR(); + bool gammaRampsInUse(); - bool needsCM(); - /// Can do CM without shader - bool canNoShaderCM(); + /// 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(); + + uint32_t getPreferredReadFormat(); + + bool needsCM(); + /// Can do CM without shader (forDSmode ? check output image description : check workbuffer image description) + bool canNoShaderCM(bool forDSmode = false); bool doesNoShaderCM(); bool m_enabled = false; bool m_renderingInitPassed = false; - WP m_previousFSWindow; - NColorManagement::SImageDescription m_imageDescription; - bool m_noShaderCTM = false; // sets drm CTM, restore needed + + PHLWINDOWREF m_previousFSWindow; + bool m_needsHDRupdate = false; + + std::optional m_cachedAllocatorDRMDev; + std::optional m_cachedCompositorDRMDev; + int m_cachedAllocatorDRMFD = -1; + int m_cachedCompositorDRMFD = -1; + std::optional m_cachedSameGPU; + + NColorManagement::PImageDescription m_imageDescription = NColorManagement::CImageDescription::from(NColorManagement::SImageDescription{}); + bool m_noShaderCTM = false; // sets drm CTM, restore needed + + bool m_blurFBDirty = true; + bool m_blurFBShouldRender = false; // For the list lookup @@ -344,18 +393,28 @@ class CMonitor { return m_position == rhs.m_position && m_size == rhs.m_size && m_name == rhs.m_name; } - // workspace previous per monitor functionality - SWorkspaceIDName getPrevWorkspaceIDName(const WORKSPACEID id); - void addPrevWorkspaceID(const WORKSPACEID id); + bool needsACopyFB(); + bool needsUnmodifiedCopy(); + bool useFP16(); + WP resources(); private: - void setupDefaultWS(const SMonitorRule&); + void updateMatrix(); + Mat3x3 m_projMatrix; + Mat3x3 m_projOutputMatrix; + + void setupDefaultWS(const Config::CMonitorRule&); WORKSPACEID findAvailableDefaultWS(); void commitDPMSState(bool state); + void updateVCGTRamps(); bool m_doneScheduled = false; + bool m_vcgtRampsSet = false; std::stack m_prevWorkSpaces; + // Resources + UP m_resources; + struct { CHyprSignalListener frame; CHyprSignalListener destroy; @@ -365,8 +424,8 @@ class CMonitor { CHyprSignalListener commit; } m_listeners; - bool m_supportsWideColor = false; - bool m_supportsHDR = false; + int m_supportsWideColor = 0; + int m_supportsHDR = 0; float m_minLuminance = -1.0f; int m_maxLuminance = -1; int m_maxAvgLuminance = -1; diff --git a/src/helpers/MonitorFrameScheduler.cpp b/src/helpers/MonitorFrameScheduler.cpp index 1e8a81e5c..58caeca5d 100644 --- a/src/helpers/MonitorFrameScheduler.cpp +++ b/src/helpers/MonitorFrameScheduler.cpp @@ -4,6 +4,8 @@ #include "../render/Renderer.hpp" #include "../managers/eventLoop/EventLoopManager.hpp" +using namespace Render::GL; + CMonitorFrameScheduler::CMonitorFrameScheduler(PHLMONITOR m) : m_monitor(m) { ; } @@ -11,25 +13,25 @@ CMonitorFrameScheduler::CMonitorFrameScheduler(PHLMONITOR m) : m_monitor(m) { bool CMonitorFrameScheduler::newSchedulingEnabled() { static auto PENABLENEW = CConfigValue("render:new_render_scheduling"); - return *PENABLENEW && g_pHyprOpenGL->explicitSyncSupported(); + return *PENABLENEW && g_pHyprRenderer->explicitSyncSupported() && m_monitor && !m_monitor->m_directScanoutIsActive; } void CMonitorFrameScheduler::onSyncFired() { - - if (!newSchedulingEnabled()) + const auto PMONITOR = m_monitor.lock(); + if (!PMONITOR || !newSchedulingEnabled()) return; // Sync fired: reset submitted state, set as rendered. Check the last render time. If we are running // late, we will instantly render here. - if (std::chrono::duration_cast(hrc::now() - m_lastRenderBegun).count() / 1000.F < 1000.F / m_monitor->m_refreshRate) { + if (std::chrono::duration_cast(hrc::now() - m_lastRenderBegun).count() / 1000.F < 1000.F / PMONITOR->m_refreshRate) { // we are in. Frame is valid. We can just render as normal. - Debug::log(TRACE, "CMonitorFrameScheduler: {} -> onSyncFired, didn't miss.", m_monitor->m_name); + Log::logger->log(Log::TRACE, "CMonitorFrameScheduler: {} -> onSyncFired, didn't miss.", PMONITOR->m_name); m_renderAtFrame = true; return; } - Debug::log(TRACE, "CMonitorFrameScheduler: {} -> onSyncFired, missed.", m_monitor->m_name); + Log::logger->log(Log::TRACE, "CMonitorFrameScheduler: {} -> onSyncFired, missed.", PMONITOR->m_name); // we are out. The frame is taking too long to render. Begin rendering immediately, but don't commit yet. m_pendingThird = true; @@ -41,7 +43,7 @@ void CMonitorFrameScheduler::onSyncFired() { // FIXME: this is horrible. "renderMonitor" should not be able to do that. auto self = m_self; - g_pHyprRenderer->renderMonitor(m_monitor.lock(), false); + g_pHyprRenderer->renderMonitor(PMONITOR, false); if (!self) return; @@ -50,21 +52,22 @@ void CMonitorFrameScheduler::onSyncFired() { } void CMonitorFrameScheduler::onPresented() { - if (!newSchedulingEnabled()) + const auto PMONITOR = m_monitor.lock(); + if (!PMONITOR || !newSchedulingEnabled()) return; if (!m_pendingThird) return; - Debug::log(TRACE, "CMonitorFrameScheduler: {} -> onPresented, missed, committing pending.", m_monitor->m_name); + Log::logger->log(Log::TRACE, "CMonitorFrameScheduler: {} -> onPresented, missed, committing pending.", PMONITOR->m_name); m_pendingThird = false; - Debug::log(TRACE, "CMonitorFrameScheduler: {} -> onPresented, missed, committing pending at the earliest convenience.", m_monitor->m_name); + Log::logger->log(Log::TRACE, "CMonitorFrameScheduler: {} -> onPresented, missed, committing pending at the earliest convenience.", PMONITOR->m_name); m_pendingThird = false; - g_pEventLoopManager->doLater([m = m_monitor.lock()] { + g_pEventLoopManager->doLater([m = PMONITOR] { if (!m) return; g_pHyprRenderer->commitPendingAndDoExplicitSync(m); // commit the pending frame. If it didn't fire yet (is not rendered) it doesn't matter. Syncs will wait. @@ -77,35 +80,36 @@ void CMonitorFrameScheduler::onPresented() { } void CMonitorFrameScheduler::onFrame() { - if (!canRender()) + const auto PMONITOR = m_monitor.lock(); + if (!PMONITOR || !canRender()) return; - m_monitor->recheckSolitary(); + PMONITOR->recheckSolitary(); - m_monitor->m_tearingState.busy = false; + PMONITOR->m_tearingState.busy = false; - if (m_monitor->m_tearingState.activelyTearing && m_monitor->m_solitaryClient.lock() /* can be invalidated by a recheck */) { + if (PMONITOR->m_tearingState.activelyTearing && PMONITOR->m_solitaryClient.lock() /* can be invalidated by a recheck */) { - if (!m_monitor->m_tearingState.frameScheduledWhileBusy) + if (!PMONITOR->m_tearingState.frameScheduledWhileBusy) return; // we did not schedule a frame yet to be displayed, but we are tearing. Why render? - m_monitor->m_tearingState.nextRenderTorn = true; - m_monitor->m_tearingState.frameScheduledWhileBusy = false; + PMONITOR->m_tearingState.nextRenderTorn = true; + PMONITOR->m_tearingState.frameScheduledWhileBusy = false; } if (!newSchedulingEnabled()) { - m_monitor->m_lastPresentationTimer.reset(); + PMONITOR->m_lastPresentationTimer.reset(); - g_pHyprRenderer->renderMonitor(m_monitor.lock()); + g_pHyprRenderer->renderMonitor(PMONITOR); return; } if (!m_renderAtFrame) { - Debug::log(TRACE, "CMonitorFrameScheduler: {} -> frame event, but m_renderAtFrame = false.", m_monitor->m_name); + Log::logger->log(Log::TRACE, "CMonitorFrameScheduler: {} -> frame event, but m_renderAtFrame = false.", PMONITOR->m_name); return; } - Debug::log(TRACE, "CMonitorFrameScheduler: {} -> frame event, render = true, rendering normally.", m_monitor->m_name); + Log::logger->log(Log::TRACE, "CMonitorFrameScheduler: {} -> frame event, render = true, rendering normally.", PMONITOR->m_name); m_lastRenderBegun = hrc::now(); @@ -113,7 +117,7 @@ void CMonitorFrameScheduler::onFrame() { // FIXME: this is horrible. "renderMonitor" should not be able to do that. auto self = m_self; - g_pHyprRenderer->renderMonitor(m_monitor.lock()); + g_pHyprRenderer->renderMonitor(PMONITOR); if (!self) return; @@ -122,7 +126,7 @@ void CMonitorFrameScheduler::onFrame() { } void CMonitorFrameScheduler::onFinishRender() { - m_sync = CEGLSync::create(); // this destroys the old sync + m_sync = g_pHyprRenderer->createSyncFDManager(); // this destroys the old sync g_pEventLoopManager->doOnReadable(m_sync->fd().duplicate(), [this, self = m_self] { if (!self) // might've gotten destroyed return; @@ -132,7 +136,7 @@ void CMonitorFrameScheduler::onFinishRender() { bool CMonitorFrameScheduler::canRender() { if ((g_pCompositor->m_aqBackend->hasSession() && !g_pCompositor->m_aqBackend->session->active) || !g_pCompositor->m_sessionActive || g_pCompositor->m_unsafeState) { - Debug::log(WARN, "Attempted to render frame on inactive session!"); + Log::logger->log(Log::WARN, "Attempted to render frame on inactive session!"); if (g_pCompositor->m_unsafeState && std::ranges::any_of(g_pCompositor->m_monitors.begin(), g_pCompositor->m_monitors.end(), [&](auto& m) { return m->m_output != g_pCompositor->m_unsafeOutput->m_output; diff --git a/src/helpers/MonitorFrameScheduler.hpp b/src/helpers/MonitorFrameScheduler.hpp index c9b45a64a..b12d8789c 100644 --- a/src/helpers/MonitorFrameScheduler.hpp +++ b/src/helpers/MonitorFrameScheduler.hpp @@ -1,11 +1,10 @@ #pragma once #include "Monitor.hpp" +#include "../render/SyncFDManager.hpp" #include -class CEGLSync; - class CMonitorFrameScheduler { public: using hrc = std::chrono::high_resolution_clock; @@ -32,7 +31,7 @@ class CMonitorFrameScheduler { PHLMONITORREF m_monitor; - UP m_sync; + UP m_sync; WP m_self; diff --git a/src/helpers/MonitorResources.cpp b/src/helpers/MonitorResources.cpp new file mode 100644 index 000000000..8e7723c23 --- /dev/null +++ b/src/helpers/MonitorResources.cpp @@ -0,0 +1,115 @@ +#include "MonitorResources.hpp" +#include "./cm/ColorManagement.hpp" +#include "../render/Renderer.hpp" +#include +#include + +using namespace Monitor; +using namespace NColorManagement; + +static const int MAX_WORK_BUFFERS = 8; +static const int MAX_UNUSED_SECONDS = 5; + +CMonitorResources::CMonitorResources(WP monitor, DRMFormat format, Vector2D size, NColorManagement::PImageDescription imageDescription) : + m_stencilTex(g_pHyprRenderer->createStencilTexture(monitor->m_pixelSize.x, monitor->m_pixelSize.y)), + m_blurFB(g_pHyprRenderer->createFB(std::format("Monitor {} blur FB", monitor->m_name))), m_monitor(monitor), m_drmFormat(format), m_size(size), + m_imageDescription(imageDescription) { + initFB(m_blurFB); + monitor->m_blurFBDirty = true; +} + +void CMonitorResources::initFB(SP fb) { + fb->addStencil(m_stencilTex); + fb->alloc(m_size.x, m_size.y, m_drmFormat); + fb->setImageDescription(m_imageDescription); +} + +void CMonitorResources::setImageDescription(NColorManagement::PImageDescription imageDescription) { + if (m_imageDescription == imageDescription) + return; + m_imageDescription = imageDescription; + m_blurFB->setImageDescription(imageDescription); + for (const auto& res : m_workBuffers) + res.buffer->setImageDescription(imageDescription); + if (m_monitorMirrorFB) + m_monitorMirrorFB->setImageDescription(NColorManagement::getDefaultImageDescription()); + if (m_mirrorTex) + m_mirrorTex->m_imageDescription = getMirrorTexImageDescription(); +} + +SP CMonitorResources::getUnusedWorkBuffer() { + std::erase_if(m_workBuffers, [](const auto& res) { return res.lastUsed.getSeconds() >= MAX_UNUSED_SECONDS; }); + + auto found = std::ranges::find_if(m_workBuffers, [](const auto& res) { return res.buffer.strongRef() < 2; }); + if (found != m_workBuffers.end()) { + found->lastUsed.reset(); + return found->buffer; + } + if (m_workBuffers.size() >= MAX_WORK_BUFFERS) + return nullptr; + + auto& res = m_workBuffers.emplace_back(g_pHyprRenderer->createFB(std::format("Monitor {} workbuffer", m_monitor->m_name))); + initFB(res.buffer); + res.lastUsed.reset(); + return res.buffer; +} + +void CMonitorResources::forEachUnusedFB(std::function)> callback, bool includeNamed) { + for (const auto& res : m_workBuffers) { + if (res.buffer.strongRef() > 1) + continue; + + callback(res.buffer); + } + if (includeNamed) { + if (m_blurFB && m_blurFB->isAllocated() && m_blurFB.strongRef() < 2) + callback(m_blurFB); + if (hasMirrorFB() && m_monitorMirrorFB.strongRef() < 2) + callback(m_monitorMirrorFB); + } +} + +bool CMonitorResources::hasMirrorFB() { + return m_monitorMirrorFB && m_monitorMirrorFB->isAllocated(); +} + +SP CMonitorResources::mirrorFB() { + if (!m_monitorMirrorFB) + m_monitorMirrorFB = g_pHyprRenderer->createFB(std::format("Monitor {} mirror FB", m_monitor->m_name)); + + if (!m_monitorMirrorFB->isAllocated()) { + m_monitorMirrorFB->alloc(m_size.x, m_size.y, DRM_FORMAT_XRGB8888); + m_monitorMirrorFB->setImageDescription(NColorManagement::getDefaultImageDescription()); + } + + return m_monitorMirrorFB; +} + +SP CMonitorResources::getMirrorTexture() { + return hasMirrorFB() ? mirrorFB()->getTexture() : nullptr; +} + +NColorManagement::PImageDescription CMonitorResources::getMirrorTexImageDescription() { + return CImageDescription::from(SImageDescription{ + .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_SRGB, + .primariesNameSet = m_imageDescription->value().primariesNameSet, + .primariesNamed = m_imageDescription->value().primariesNamed, + .primaries = m_imageDescription->value().primaries, + .luminances = {.min = SDR_MIN_LUMINANCE, .max = 80, .reference = 80}, + }); +} + +void CMonitorResources::enableMirror() { + if (m_mirrorTex) + return; + m_mirrorTex = g_pHyprRenderer->createTexture(); + m_mirrorTex->allocate({m_size.x, m_size.y}, DRM_FORMAT_XRGB8888); + m_mirrorTex->m_imageDescription = getMirrorTexImageDescription(); + m_monitor->m_blurFBDirty = true; +} + +void CMonitorResources::disableMirror() { + if (m_mirrorTex) + m_monitor->m_blurFBDirty = true; + m_mirrorTex.reset(); +} diff --git a/src/helpers/MonitorResources.hpp b/src/helpers/MonitorResources.hpp new file mode 100644 index 000000000..6092b9c9f --- /dev/null +++ b/src/helpers/MonitorResources.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "Monitor.hpp" +#include "Format.hpp" +#include "time/Timer.hpp" +#include "../render/Framebuffer.hpp" +#include +#include + +namespace Monitor { + class CMonitorResources { + public: + CMonitorResources(WP monitor, DRMFormat format, Vector2D size, NColorManagement::PImageDescription imageDescription); + + SP getUnusedWorkBuffer(); + void forEachUnusedFB(std::function)> callback, bool includeNamed = false); + bool hasMirrorFB(); + void enableMirror(); + void disableMirror(); + SP mirrorFB(); + SP getMirrorTexture(); + SP m_mirrorTex; + + SP m_stencilTex; // TODO fix blur ignore alpha and remove + SP m_blurFB; + + private: + void initFB(SP fb); + void setImageDescription(NColorManagement::PImageDescription imageDescription); + NColorManagement::PImageDescription getMirrorTexImageDescription(); + + struct SResource { + SP buffer; + CTimer lastUsed; + }; + + SP m_monitorMirrorFB; + WP m_monitor; + DRMFormat m_drmFormat; + Vector2D m_size; + NColorManagement::PImageDescription m_imageDescription; + + std::vector m_workBuffers; + + friend class ::CMonitor; + }; +} diff --git a/src/helpers/MonitorZoomController.cpp b/src/helpers/MonitorZoomController.cpp new file mode 100644 index 000000000..3e1bab99a --- /dev/null +++ b/src/helpers/MonitorZoomController.cpp @@ -0,0 +1,98 @@ +#include "MonitorZoomController.hpp" + +#include +#include "../config/ConfigValue.hpp" +#include "../managers/input/InputManager.hpp" +#include "../render/OpenGL.hpp" +#include "desktop/DesktopTypes.hpp" +#include "render/Renderer.hpp" + +void CMonitorZoomController::zoomWithDetachedCamera(CBox& result, const Render::SRenderData& m_renderData) { + const auto m = m_renderData.pMonitor; + auto monbox = CBox(0, 0, m->m_size.x, m->m_size.y); + const auto ZOOM = g_pHyprRenderer->m_renderData.mouseZoomFactor; + const auto MOUSE = g_pInputManager->getMouseCoordsInternal() - m->m_position; + + if (m_lastZoomLevel != ZOOM) { + if (m_resetCameraState) { + m_resetCameraState = false; + m_camera = CBox(0, 0, m->m_size.x, m->m_size.y); + m_lastZoomLevel = 1.0f; + } + const CBox old = m_camera; + + // mouse normalized inside screen (0..1) + const float mx = MOUSE.x / m->m_size.x; + const float my = MOUSE.y / m->m_size.y; + // world-space point under the cursor before zoom + const float mouseWorldX = old.x + (mx * old.w); + const float mouseWorldY = old.y + (my * old.h); + + const auto CAMERAW = monbox.w / ZOOM; + const auto CAMERAH = monbox.h / ZOOM; + + // compute new top-left so the same world point stays under the cursor + const float newX = mouseWorldX - (mx * CAMERAW); + const float newY = mouseWorldY - (my * CAMERAH); + + m_camera = CBox(newX, newY, CAMERAW, CAMERAH); + // Detect if this zoom would've caused jerk to keep mouse in view and disable edges if so + if (!m_camera.copy().scaleFromCenter(.9).containsPoint(MOUSE)) + m_padCamEdges = false; + m_lastZoomLevel = ZOOM; + } + + // Keep mouse inside cameraview + auto smallerbox = m_camera; + // Prevent zoom step from causing us to jerk to keep mouse in padded camera view, + // but let us switch to the padded camera once the mouse moves into the safe area + if (!m_padCamEdges) + if (smallerbox.copy().scaleFromCenter(.9).containsPoint(MOUSE)) + m_padCamEdges = true; + if (m_padCamEdges) + smallerbox.scaleFromCenter(.9); + if (!smallerbox.containsPoint(MOUSE)) { + if (MOUSE.x < smallerbox.x) + m_camera.x -= smallerbox.x - MOUSE.x; + if (MOUSE.y < smallerbox.y) + m_camera.y -= smallerbox.y - MOUSE.y; + if (MOUSE.y > smallerbox.y + smallerbox.h) + m_camera.y += MOUSE.y - (smallerbox.y + smallerbox.h); + if (MOUSE.x > smallerbox.x + smallerbox.w) + m_camera.x += MOUSE.x - (smallerbox.x + smallerbox.w); + } + + auto z = ZOOM * m->m_scale; + monbox.scale(z).translate(-m_camera.pos() * z); + + result = monbox; +} + +void CMonitorZoomController::applyZoomTransform(CBox& monbox, const Render::SRenderData& m_renderData) { + static auto PZOOMRIGID = CConfigValue("cursor:zoom_rigid"); + static auto PZOOMDETACHEDCAMERA = CConfigValue("cursor:zoom_detached_camera"); + const auto ZOOM = g_pHyprRenderer->m_renderData.mouseZoomFactor; + + if (ZOOM == 1.0f) + return; + + const auto m = m_renderData.pMonitor; + const auto ORIGINAL = monbox; + const auto INITANIM = m->m_zoomAnimProgress->value() != 1.0; + + if (*PZOOMDETACHEDCAMERA && !INITANIM) + zoomWithDetachedCamera(monbox, m_renderData); + else { + const auto ZOOMCENTER = + g_pHyprRenderer->m_renderData.mouseZoomUseMouse ? (g_pInputManager->getMouseCoordsInternal() - m->m_position) * m->m_scale : m->m_transformedSize / 2.f; + + monbox.translate(-ZOOMCENTER).scale(ZOOM).translate(*PZOOMRIGID ? m->m_transformedSize / 2.0 : ZOOMCENTER); + } + + monbox.x = std::min(monbox.x, 0.0); + monbox.y = std::min(monbox.y, 0.0); + if (monbox.x + monbox.width < ORIGINAL.w) + monbox.x = ORIGINAL.w - monbox.width; + if (monbox.y + monbox.height < ORIGINAL.h) + monbox.y = ORIGINAL.h - monbox.height; +} diff --git a/src/helpers/MonitorZoomController.hpp b/src/helpers/MonitorZoomController.hpp new file mode 100644 index 000000000..94373bafc --- /dev/null +++ b/src/helpers/MonitorZoomController.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "./math/Math.hpp" + +namespace Render { + struct SRenderData; +} + +class CMonitorZoomController { + public: + bool m_resetCameraState = true; + + void applyZoomTransform(CBox& monbox, const Render::SRenderData& m_renderData); + + private: + void zoomWithDetachedCamera(CBox& result, const Render::SRenderData& m_renderData); + + CBox m_camera; + float m_lastZoomLevel = 1.0f; + bool m_padCamEdges = true; +}; diff --git a/src/helpers/SdDaemon.cpp b/src/helpers/SdDaemon.cpp index d914eecf9..b6c207d8f 100644 --- a/src/helpers/SdDaemon.cpp +++ b/src/helpers/SdDaemon.cpp @@ -38,10 +38,10 @@ int NSystemd::sdNotify(int unsetEnvironment, const char* state) { if (!addr) return 0; - // address length must be at most this; see man 7 unix - size_t addrLen = strnlen(addr, 107); + struct sockaddr_un unixAddr = {0}; + + size_t addrLen = strnlen(addr, sizeof(unixAddr.sun_path) - 1); - struct sockaddr_un unixAddr; unixAddr.sun_family = AF_UNIX; strncpy(unixAddr.sun_path, addr, addrLen); if (unixAddr.sun_path[0] == '@') diff --git a/src/helpers/TransferFunction.cpp b/src/helpers/TransferFunction.cpp new file mode 100644 index 000000000..074f4b19c --- /dev/null +++ b/src/helpers/TransferFunction.cpp @@ -0,0 +1,38 @@ +#include "TransferFunction.hpp" +#include "../config/ConfigValue.hpp" +#include "../event/EventBus.hpp" +#include +#include +#include + +using namespace NTransferFunction; + +static std::unordered_map const table = {{"default", TF_DEFAULT}, {"0", TF_DEFAULT}, {"auto", TF_AUTO}, {"srgb", TF_SRGB}, + {"3", TF_SRGB}, {"gamma22", TF_GAMMA22}, {"1", TF_GAMMA22}, {"gamma22force", TF_FORCED_GAMMA22}, + {"2", TF_FORCED_GAMMA22}}; + +eTF NTransferFunction::fromString(const std::string tfName) { + auto it = table.find(tfName); + if (it == table.end()) + return TF_DEFAULT; + return it->second; +} + +std::string NTransferFunction::toString(eTF tf) { + for (const auto& [key, value] : table) { + if (value == tf) + return key; + } + return ""; +} + +eTF NTransferFunction::fromConfig(bool useICC) { + if (useICC) + return TF_SRGB; + + static auto PSDREOTF = CConfigValue("render:cm_sdr_eotf"); + static auto sdrEOTF = NTransferFunction::fromString(*PSDREOTF); + static auto P = Event::bus()->m_events.config.reloaded.listen([]() { sdrEOTF = NTransferFunction::fromString(*PSDREOTF); }); + + return sdrEOTF; +} diff --git a/src/helpers/TransferFunction.hpp b/src/helpers/TransferFunction.hpp new file mode 100644 index 000000000..ae5751589 --- /dev/null +++ b/src/helpers/TransferFunction.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +namespace NTransferFunction { + enum eTF : uint8_t { + TF_DEFAULT = 0, + TF_AUTO = 1, + TF_SRGB = 2, + TF_GAMMA22 = 3, + TF_FORCED_GAMMA22 = 4, + }; + + eTF fromString(const std::string tfName); + std::string toString(eTF tf); + + eTF fromConfig(bool useICC = false); +} diff --git a/src/helpers/WLClasses.hpp b/src/helpers/WLClasses.hpp index 9a22b77fb..ec0737876 100644 --- a/src/helpers/WLClasses.hpp +++ b/src/helpers/WLClasses.hpp @@ -1,9 +1,9 @@ #pragma once #include "../defines.hpp" -#include "../desktop/Subsurface.hpp" -#include "../desktop/Popup.hpp" -#include "../desktop/WLSurface.hpp" +#include "../desktop/view/Subsurface.hpp" +#include "../desktop/view/Popup.hpp" +#include "../desktop/view/WLSurface.hpp" #include "../macros.hpp" #include "../desktop/DesktopTypes.hpp" #include "memory/Memory.hpp" diff --git a/src/helpers/cm/ColorManagement.cpp b/src/helpers/cm/ColorManagement.cpp new file mode 100644 index 000000000..5e4725d40 --- /dev/null +++ b/src/helpers/cm/ColorManagement.cpp @@ -0,0 +1,221 @@ +#include "ColorManagement.hpp" +#include "../../macros.hpp" +#include "helpers/TransferFunction.hpp" +#include +#include +#include + +using namespace NColorManagement; +using namespace NTransferFunction; + +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 uint64_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 uint64_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; +} + +uint64_t CImageDescription::id() const { + return m_id; +} + +WP CImageDescription::getPrimaries() const { + return CPrimaries::from(m_primariesId); +} + +bool CImageDescription::needsCM(WP target) const { + if (m_id == target->m_id) + return false; + + return m_imageDescription.icc.present || target->m_imageDescription.icc.present // TODO compare ICC + || m_imageDescription.transferFunction != target->m_imageDescription.transferFunction // + //|| m_imageDescription.transferFunctionPower != target->m_imageDescription.transferFunctionPower // TODO unsupported + || m_imageDescription.getPrimaries() != target->m_imageDescription.getPrimaries() // + // || m_imageDescription.masteringPrimaries != target->m_imageDescription.masteringPrimaries // TODO unused + || m_imageDescription.luminances != target->m_imageDescription.luminances // + // || m_imageDescription.masteringLuminances != target->m_imageDescription.masteringLuminances // TODO unused + ; +} + +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; +} + +PImageDescription NColorManagement::getDefaultImageDescription() { + const auto TF = fromConfig(); + switch (TF) { + case TF_AUTO: + case TF_GAMMA22: + case TF_FORCED_GAMMA22: return DEFAULT_GAMMA22_IMAGE_DESCRIPTION; + case TF_DEFAULT: + case TF_SRGB: return DEFAULT_SRGB_IMAGE_DESCRIPTION; + default: UNREACHABLE(); + } +} diff --git a/src/helpers/cm/ColorManagement.hpp b/src/helpers/cm/ColorManagement.hpp new file mode 100644 index 000000000..183a8544d --- /dev/null +++ b/src/helpers/cm/ColorManagement.hpp @@ -0,0 +1,423 @@ +#pragma once + +#include "color-management-v1.hpp" +#include +#include +#include "../../helpers/memory/Memory.hpp" +#include "../../helpers/math/Math.hpp" + +#include +#include +#include +#include + +#define SDR_MIN_LUMINANCE 0.2f +#define SDR_MAX_LUMINANCE 80.0f +#define SDR_REF_LUMINANCE 80.0f +#define HDR_MIN_LUMINANCE 0.005f +#define HDR_MAX_LUMINANCE 10000.0f +#define HDR_REF_LUMINANCE 203.0f +#define HLG_MAX_LUMINANCE 1000.0f + +namespace Render { + class ITexture; +} + +namespace NColorManagement { + enum eNoShader : uint8_t { + CM_NS_DISABLE = 0, + CM_NS_ALWAYS = 1, + CM_NS_ONDEMAND = 2, + CM_NS_IGNORE = 3, + }; + + enum ePrimaries : uint8_t { + CM_PRIMARIES_SRGB = 1, + CM_PRIMARIES_PAL_M = 2, + CM_PRIMARIES_PAL = 3, + CM_PRIMARIES_NTSC = 4, + CM_PRIMARIES_GENERIC_FILM = 5, + CM_PRIMARIES_BT2020 = 6, + CM_PRIMARIES_CIE1931_XYZ = 7, + CM_PRIMARIES_DCI_P3 = 8, + CM_PRIMARIES_DISPLAY_P3 = 9, + CM_PRIMARIES_ADOBE_RGB = 10, + }; + + enum eTransferFunction : uint8_t { + CM_TRANSFER_FUNCTION_BT1886 = 1, + CM_TRANSFER_FUNCTION_GAMMA22 = 2, + CM_TRANSFER_FUNCTION_GAMMA28 = 3, + CM_TRANSFER_FUNCTION_ST240 = 4, + CM_TRANSFER_FUNCTION_EXT_LINEAR = 5, + CM_TRANSFER_FUNCTION_LOG_100 = 6, + CM_TRANSFER_FUNCTION_LOG_316 = 7, + CM_TRANSFER_FUNCTION_XVYCC = 8, + CM_TRANSFER_FUNCTION_SRGB = 9, + CM_TRANSFER_FUNCTION_EXT_SRGB = 10, + CM_TRANSFER_FUNCTION_ST2084_PQ = 11, + CM_TRANSFER_FUNCTION_ST428 = 12, + CM_TRANSFER_FUNCTION_HLG = 13, + }; + + // NOTE should be ok this way. unsupported primaries/tfs must be rejected earlier. supported enum values should be in sync with proto. + // might need a proper switch-case and additional INVALID enum value. + inline wpColorManagerV1Primaries convertPrimaries(ePrimaries primaries) { + return sc(primaries); + } + inline ePrimaries convertPrimaries(wpColorManagerV1Primaries primaries) { + return sc(primaries); + } + inline wpColorManagerV1TransferFunction convertTransferFunction(eTransferFunction tf) { + switch (tf) { + case CM_TRANSFER_FUNCTION_SRGB: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_COMPOUND_POWER_2_4; + default: return sc(tf); + } + } + inline eTransferFunction convertTransferFunction(wpColorManagerV1TransferFunction tf) { + switch (tf) { + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_COMPOUND_POWER_2_4: return CM_TRANSFER_FUNCTION_SRGB; + default: return sc(tf); + } + } + inline std::string tfToString(eTransferFunction tf) { + switch (tf) { + case CM_TRANSFER_FUNCTION_BT1886: return "TF:BT1886"; + case CM_TRANSFER_FUNCTION_GAMMA22: return "TF:GAMMA22"; + case CM_TRANSFER_FUNCTION_GAMMA28: return "TF:GAMMA28"; + case CM_TRANSFER_FUNCTION_ST240: return "TF:ST240"; + case CM_TRANSFER_FUNCTION_EXT_LINEAR: return "TF:EXT_LINEAR"; + case CM_TRANSFER_FUNCTION_LOG_100: return "TF:LOG_100"; + case CM_TRANSFER_FUNCTION_LOG_316: return "TF:LOG_316"; + case CM_TRANSFER_FUNCTION_XVYCC: return "TF:XVYCC"; + case CM_TRANSFER_FUNCTION_SRGB: return "TF:SRGB"; + case CM_TRANSFER_FUNCTION_EXT_SRGB: return "TF:EXT_SRGB"; + case CM_TRANSFER_FUNCTION_ST2084_PQ: return "TF:ST2084_PQ"; + case CM_TRANSFER_FUNCTION_ST428: return "TF:ST428"; + case CM_TRANSFER_FUNCTION_HLG: return "TF:HLG"; + default: return "TF:ERROR"; + } + } + + using SPCPRimaries = Hyprgraphics::SPCPRimaries; + + namespace NColorPrimaries { + + static const auto BT709 = SPCPRimaries{ + .red = {.x = 0.64, .y = 0.33}, + .green = {.x = 0.30, .y = 0.60}, + .blue = {.x = 0.15, .y = 0.06}, + .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}, + .blue = {.x = 0.14, .y = 0.08}, + .white = {.x = 0.310, .y = 0.316}, + }; + + static const auto PAL = SPCPRimaries{ + .red = {.x = 0.640, .y = 0.330}, + .green = {.x = 0.290, .y = 0.600}, + .blue = {.x = 0.150, .y = 0.060}, + .white = {.x = 0.3127, .y = 0.3290}, + }; + + static const auto NTSC = SPCPRimaries{ + .red = {.x = 0.630, .y = 0.340}, + .green = {.x = 0.310, .y = 0.595}, + .blue = {.x = 0.155, .y = 0.070}, + .white = {.x = 0.3127, .y = 0.3290}, + }; + + static const auto GENERIC_FILM = SPCPRimaries{ + .red = {.x = 0.243, .y = 0.692}, + .green = {.x = 0.145, .y = 0.049}, + .blue = {.x = 0.681, .y = 0.319}, // NOLINT(modernize-use-std-numbers) + .white = {.x = 0.310, .y = 0.316}, + }; + + static const auto BT2020 = SPCPRimaries{ + .red = {.x = 0.708, .y = 0.292}, + .green = {.x = 0.170, .y = 0.797}, + .blue = {.x = 0.131, .y = 0.046}, + .white = {.x = 0.3127, .y = 0.3290}, + }; + + static const auto CIE1931_XYZ = SPCPRimaries{ + .red = {.x = 1.0, .y = 0.0}, + .green = {.x = 0.0, .y = 1.0}, + .blue = {.x = 0.0, .y = 0.0}, + .white = {.x = 1.0 / 3.0, .y = 1.0 / 3.0}, + }; + + static const auto DCI_P3 = SPCPRimaries{ + .red = {.x = 0.680, .y = 0.320}, + .green = {.x = 0.265, .y = 0.690}, + .blue = {.x = 0.150, .y = 0.060}, + .white = {.x = 0.314, .y = 0.351}, + }; + + static const auto DISPLAY_P3 = SPCPRimaries{ + .red = {.x = 0.680, .y = 0.320}, + .green = {.x = 0.265, .y = 0.690}, + .blue = {.x = 0.150, .y = 0.060}, + .white = {.x = 0.3127, .y = 0.3290}, + }; + + static const auto ADOBE_RGB = SPCPRimaries{ + .red = {.x = 0.6400, .y = 0.3300}, + .green = {.x = 0.2100, .y = 0.7100}, + .blue = {.x = 0.1500, .y = 0.0600}, + .white = {.x = 0.3127, .y = 0.3290}, + }; + } + + struct SVCGTTable16 { + uint16_t channels = 0; + uint16_t entries = 0; + uint16_t entrySize = 0; + std::array, 3> ch; + }; + + const SPCPRimaries& getPrimaries(ePrimaries name); + std::optional rgbToXYZFromPrimaries(SPCPRimaries pr); + Mat3x3 adaptBradford(Hyprgraphics::CColor::xy srcW, Hyprgraphics::CColor::xy dstW); + + class CPrimaries { + public: + static WP from(const SPCPRimaries& primaries); + static WP from(const ePrimaries name); + static WP from(const uint primariesId); + + const SPCPRimaries& value() const; + uint id() const; + + const Hyprgraphics::CMatrix3& toXYZ() const; // toXYZ() * rgb -> xyz + const Hyprgraphics::CMatrix3& convertMatrix(const WP dst) const; // convertMatrix(dst) * rgb with "this" primaries -> rgb with dst primaries + + private: + CPrimaries(const SPCPRimaries& primaries, const uint primariesId); + uint m_id; + SPCPRimaries m_primaries; + + Hyprgraphics::CMatrix3 m_primaries2XYZ; + }; + + struct SImageDescription { + static std::expected fromICC(const std::filesystem::path& file); + + // + std::vector rawICC; + + eTransferFunction transferFunction = CM_TRANSFER_FUNCTION_GAMMA22; + float transferFunctionPower = 1.0f; + bool windowsScRGB = false; + + 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 + SPCPRimaries primaries, masteringPrimaries; + + // luminances in cd/m² + // protos and drm expect min * 10000 + struct SPCLuminances { + float min = 0.2; // 0.2 cd/m² + uint32_t max = 80; // 80 cd/m² + uint32_t reference = 80; // 80 cd/m² + bool operator==(const SPCLuminances& l2) const { + return min == l2.min && max == l2.max && reference == l2.reference; + } + } luminances; + struct SPCMasteringLuminances { + float min = 0; + uint32_t max = 0; + bool operator==(const SPCMasteringLuminances& l2) const { + return min == l2.min && max == l2.max; + } + } 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 && + (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; + } + + const SPCPRimaries& getPrimaries() const { + if (primariesNameSet || primaries == SPCPRimaries{}) + return NColorManagement::getPrimaries(primariesNamed); + return primaries; + } + + float getTFMinLuminance(float sdrMinLuminance = -1.0f) const { + switch (transferFunction) { + 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_ST240: + case CM_TRANSFER_FUNCTION_LOG_100: + case CM_TRANSFER_FUNCTION_LOG_316: + case CM_TRANSFER_FUNCTION_XVYCC: + case CM_TRANSFER_FUNCTION_EXT_SRGB: + case CM_TRANSFER_FUNCTION_ST428: + case CM_TRANSFER_FUNCTION_SRGB: + default: return sdrMinLuminance >= 0 ? sdrMinLuminance : SDR_MIN_LUMINANCE; + } + }; + + float getTFMaxLuminance(int sdrMaxLuminance = -1) const { + switch (transferFunction) { + case CM_TRANSFER_FUNCTION_EXT_LINEAR: + 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_ST240: + case CM_TRANSFER_FUNCTION_LOG_100: + case CM_TRANSFER_FUNCTION_LOG_316: + case CM_TRANSFER_FUNCTION_XVYCC: + case CM_TRANSFER_FUNCTION_EXT_SRGB: + case CM_TRANSFER_FUNCTION_ST428: + case CM_TRANSFER_FUNCTION_SRGB: + default: return sdrMaxLuminance >= 0 ? sdrMaxLuminance : SDR_MAX_LUMINANCE; + } + }; + + float getTFRefLuminance(int sdrRefLuminance = -1) const { + switch (transferFunction) { + 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_ST240: + case CM_TRANSFER_FUNCTION_LOG_100: + case CM_TRANSFER_FUNCTION_LOG_316: + case CM_TRANSFER_FUNCTION_XVYCC: + case CM_TRANSFER_FUNCTION_EXT_SRGB: + case CM_TRANSFER_FUNCTION_ST428: + case CM_TRANSFER_FUNCTION_SRGB: + default: return sdrRefLuminance >= 0 ? sdrRefLuminance : SDR_REF_LUMINANCE; + } + }; + }; + + class CImageDescription { + public: + static WP from(const SImageDescription& imageDescription); + static WP from(const uint64_t imageDescriptionId); + + WP with(const SImageDescription::SPCLuminances& luminances) const; + + const SImageDescription& value() const; + uint64_t id() const; + + WP getPrimaries() const; + bool needsCM(WP target) const; + + private: + CImageDescription(const SImageDescription& imageDescription, const uint64_t imageDescriptionId); + uint64_t m_id = 0; + uint m_primariesId = 0; + SImageDescription m_imageDescription; + }; + + using PImageDescription = WP; + + PImageDescription getDefaultImageDescription(); + + static const auto DEFAULT_GAMMA22_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_SRGB_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{ + .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_SRGB, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_SRGB, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_SRGB), + .luminances = {.min = SDR_MIN_LUMINANCE, .max = 80, .reference = 80}, + }); + + static const auto DEFAULT_HDR_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{ + .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020), + .luminances = {.min = HDR_MIN_LUMINANCE, .max = 10000, .reference = 203}, + }); + + static const auto SCRGB_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{ + .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_EXT_LINEAR, + .windowsScRGB = true, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_SRGB, + .primaries = NColorPrimaries::BT709, + .luminances = {.reference = 203}, + }); + + static const auto LINEAR_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{ + .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_EXT_LINEAR, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_SRGB, + .primaries = NColorPrimaries::BT709, + .luminances = {.min = 0, .max = 10000, .reference = 80}, + }); +} + +template +struct std::formatter : std::formatter { + template + auto format(const Hyprgraphics::SPCPRimaries& primaries, FormatContext& ctx) const { + return std::format_to(ctx.out(), "[r={},{} g={},{} b={},{} w={},{}]", primaries.red.x, primaries.red.y, primaries.green.x, primaries.green.y, primaries.blue.x, + primaries.blue.y, primaries.white.x, primaries.white.y); + } +}; + +template +struct std::formatter : std::formatter { + template + auto format(const NColorManagement::SImageDescription::SPCLuminances& luminances, FormatContext& ctx) const { + return std::format_to(ctx.out(), "[{}-{}({})]", luminances.min, luminances.max, luminances.reference); + } +}; + +template +struct std::formatter : std::formatter { + template + auto format(const NColorManagement::SImageDescription& imageDescription, FormatContext& ctx) const { + return std::format_to(ctx.out(), "[{}{}, primaries={}, luminances={}]", NColorManagement::tfToString(imageDescription.transferFunction), + imageDescription.transferFunctionPower != 1.0f ? std::format("^{}", imageDescription.transferFunctionPower) : "", imageDescription.getPrimaries(), + imageDescription.luminances); + } +}; diff --git a/src/helpers/cm/ICC.cpp b/src/helpers/cm/ICC.cpp new file mode 100644 index 000000000..00140c624 --- /dev/null +++ b/src/helpers/cm/ICC.cpp @@ -0,0 +1,278 @@ +#include "ColorManagement.hpp" +#include "../math/Math.hpp" +#include +#include + +#include "../../debug/log/Logger.hpp" +#include "../../render/Texture.hpp" +#include "../../render/Renderer.hpp" + +#include +using namespace Hyprutils::Utils; + +#include + +using namespace NColorManagement; + +static std::vector readBinary(const std::filesystem::path& file) { + std::ifstream ifs(file, std::ios::binary); + if (!ifs.good()) + return {}; + + ifs.seekg(0, std::ios::end); + size_t len = ifs.tellg(); + ifs.seekg(0, std::ios::beg); + + if (len <= 0) + return {}; + + std::vector buf; + buf.resize(len); + ifs.read(reinterpret_cast(buf.data()), len); + + return buf; +} + +static uint16_t bigEndianU16(const uint8_t* p) { + return (uint16_t)((uint16_t)p[0] << 8 | (uint16_t)p[1]); +} + +static uint32_t bigEndianU32(const uint8_t* p) { + return (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | (uint32_t)p[3]; +} + +static constexpr cmsTagSignature makeSig(char a, char b, char c, char d) { + return sc(sc(a) << 24 | sc(b) << 16 | sc(c) << 8 | sc(d)); +} + +static constexpr cmsTagSignature VCGT_SIG = makeSig('v', 'c', 'g', 't'); + +// + +static std::expected, std::string> readVCGT16(cmsHPROFILE prof) { + if (!cmsIsTag(prof, VCGT_SIG)) + return std::nullopt; + + cmsUInt32Number n = cmsReadRawTag(prof, VCGT_SIG, nullptr, 0); + if (n < 8 + 4 + 2 + 2 + 2 + 2) // header + type + table header + return std::unexpected("Malformed vcgt tag"); + + std::vector raw(n); + if (cmsReadRawTag(prof, VCGT_SIG, raw.data(), n) != n) + return std::unexpected("Malformed vcgt tag"); + + // raw layout: + // 0 ... 3: 'vcgt' + // 4 ... 7: reserved + // 8 ... 11: gammaType (0 = table) + uint32_t gammaType = bigEndianU32(raw.data() + 8); + if (gammaType != 0) + return std::unexpected("VCGT formula type is not supported by Hyprland"); + + SVCGTTable16 table; + table.channels = bigEndianU16(raw.data() + 12); + table.entries = bigEndianU16(raw.data() + 14); + table.entrySize = bigEndianU16(raw.data() + 16); + // raw+18: reserved u16 + + Log::logger->log(Log::DEBUG, "readVCGT16: table has {} channels, {} entries, and entry size of {}", table.channels, table.entries, table.entrySize); + + if (table.channels != 3 || table.entrySize != 2 || table.entries == 0) + return std::unexpected("invalid vcgt table size"); + + size_t tableBytes = (size_t)table.channels * (size_t)table.entries * (size_t)table.entrySize; + + // VCGT is a piece of shit and some absolute fucking mongoloid idiots + // decided it'd be great to have both 18 and 20 + // FUCK YOU + size_t tableOff = 20; + + auto readTable = [&] -> void { + for (int c = 0; c < 3; ++c) { + table.ch[c].resize(table.entries); + for (uint16_t i = 0; i < table.entries; ++i) { + const uint8_t* p = raw.data() + tableOff + static_cast((c * table.entries + i) * 2); + table.ch[c][i] = bigEndianU16(p); // 0 ... 65535 + } + } + }; + + if (raw.size() < tableOff + tableBytes) { + tableOff = 18; + + if (raw.size() < tableOff + tableBytes) { + Log::logger->log(Log::ERR, "readVCGT16: table is too short, tag is invalid"); + return std::unexpected("table is too short"); + } + + Log::logger->log(Log::DEBUG, "readVCGT16: table is too short, but off = 18 fits. Attempting offset = 18"); + + readTable(); + } else { + readTable(); + + // if the table's last entry is suspiciously low, we more than likely read an 18 as a 20. + if (table.ch[0][table.entries - 1] < 30000) { + Log::logger->log(Log::DEBUG, "readVCGT16: table is likely offset 18 not 20, re-reading"); + + tableOff = 18; + + readTable(); + } + } + + if (table.ch[0][table.entries - 1] < 30000) { + Log::logger->log(Log::ERR, "readVCGT16: table is malformed, last value of a gamma ramp can't be {}", table.ch[0][table.entries - 1]); + return std::unexpected("invalid table values"); + } + + Log::logger->log(Log::DEBUG, "readVCGT16: red channel: [{}, {}, ... {}, {}]", table.ch[0][0], table.ch[0][1], table.ch[0][table.entries - 2], table.ch[0][table.entries - 1]); + Log::logger->log(Log::DEBUG, "readVCGT16: green channel: [{}, {}, ... {}, {}]", table.ch[1][0], table.ch[1][1], table.ch[1][table.entries - 2], table.ch[1][table.entries - 1]); + Log::logger->log(Log::DEBUG, "readVCGT16: blue channel: [{}, {}, ... {}, {}]", table.ch[2][0], table.ch[2][1], table.ch[2][table.entries - 2], table.ch[2][table.entries - 1]); + + return table; +} + +struct CmsProfileDeleter { + void operator()(cmsHPROFILE p) const { + if (p) + cmsCloseProfile(p); + } +}; +struct CmsTransformDeleter { + void operator()(cmsHTRANSFORM t) const { + if (t) + cmsDeleteTransform(t); + } +}; + +using UniqueProfile = std::unique_ptr, CmsProfileDeleter>; +using UniqueTransform = std::unique_ptr, CmsTransformDeleter>; + +static UniqueProfile createLinearSRGBProfile() { + cmsCIExyYTRIPLE prim{}; + // sRGB / Rec.709 primaries + prim.Red.x = 0.6400; + prim.Red.y = 0.3300; + prim.Red.Y = 1.0; + prim.Green.x = 0.3000; + prim.Green.y = 0.6000; + prim.Green.Y = 1.0; + prim.Blue.x = 0.1500; + prim.Blue.y = 0.0600; + prim.Blue.Y = 1.0; + + cmsCIExyY wp{}; + wp.x = 0.3127; + wp.y = 0.3290; + wp.Y = 1.0; // D65 + + cmsToneCurve* lin = cmsBuildGamma(nullptr, 1.0); + cmsToneCurve* curves[3] = {lin, lin, lin}; + + cmsHPROFILE p = cmsCreateRGBProfile(&wp, &prim, curves); + + cmsFreeToneCurve(lin); + return UniqueProfile{p}; +} + +static std::expected buildIcc3DLut(cmsHPROFILE profile, SImageDescription& image) { + UniqueProfile src = createLinearSRGBProfile(); + if (!src) + return std::unexpected("Failed to create linear sRGB profile"); + + // Rendering intent: RELATIVE_COLORIMETRIC is common for displays; add BPC to be safe. + const int intent = INTENT_RELATIVE_COLORIMETRIC; + const cmsUInt32Number flags = cmsFLAGS_BLACKPOINTCOMPENSATION | cmsFLAGS_HIGHRESPRECALC; // good quality precalc in LCMS + + // float->float transform (linear input, encoded output in dst device space) + UniqueTransform xform{cmsCreateTransform(src.get(), TYPE_RGB_FLT, profile, TYPE_RGB_FLT, intent, flags)}; + if (!xform) + return std::unexpected("Failed to create ICC transform"); + + Log::logger->log(Log::DEBUG, "Building a {}³ 3D LUT", image.icc.lutSize); + + image.icc.present = true; + image.icc.lutDataPacked.resize(image.icc.lutSize * image.icc.lutSize * image.icc.lutSize * 3); + + auto idx = [&image](int r, int g, int b) -> size_t { + // + return ((size_t)b * image.icc.lutSize * image.icc.lutSize + (size_t)g * image.icc.lutSize + (size_t)r) * 3; + }; + + for (size_t bz = 0; bz < image.icc.lutSize; ++bz) { + for (size_t gy = 0; gy < image.icc.lutSize; ++gy) { + for (size_t rx = 0; rx < image.icc.lutSize; ++rx) { + float in[3] = { + rx / float(image.icc.lutSize - 1), + gy / float(image.icc.lutSize - 1), + bz / float(image.icc.lutSize - 1), + }; + float outRGB[3]; + cmsDoTransform(xform.get(), in, outRGB, 1); + + outRGB[0] = std::clamp(outRGB[0], 0.F, 1.F); + outRGB[1] = std::clamp(outRGB[1], 0.F, 1.F); + outRGB[2] = std::clamp(outRGB[2], 0.F, 1.F); + + const size_t o = idx(rx, gy, bz); + image.icc.lutDataPacked[o + 0] = outRGB[0]; + image.icc.lutDataPacked[o + 1] = outRGB[1]; + image.icc.lutDataPacked[o + 2] = outRGB[2]; + } + } + } + + Log::logger->log(Log::DEBUG, "3D LUT constructed, size {}", image.icc.lutDataPacked.size()); + + // upload + image.icc.lutTexture = g_pHyprRenderer->createTexture(image.icc.lutDataPacked, image.icc.lutSize); + + return {}; +} + +std::expected SImageDescription::fromICC(const std::filesystem::path& file) { + static auto PVCGTENABLED = CConfigValue("render:icc_vcgt_enabled"); + + std::error_code ec; + if (!std::filesystem::exists(file, ec) || ec) + return std::unexpected("Invalid file"); + + SImageDescription image; + image.rawICC = readBinary(file); + + if (image.rawICC.empty()) + return std::unexpected("Failed to read file"); + + cmsHPROFILE prof = cmsOpenProfileFromFile(file.string().c_str(), "r"); + if (!prof) + return std::unexpected("CMS failed to open icc file"); + + CScopeGuard x([&prof] { cmsCloseProfile(prof); }); + + // only handle RGB (typical display profiles) + if (cmsGetColorSpace(prof) != cmsSigRgbData) + return std::unexpected("Only RGB display profiles are supported"); + + Log::logger->log(Log::DEBUG, "============= Begin ICC load ============="); + Log::logger->log(Log::DEBUG, "ICC size: {} bytes", image.rawICC.size()); + + if (const auto RET = buildIcc3DLut(prof, image); !RET) + return std::unexpected(RET.error()); + + if (*PVCGTENABLED) { + auto vcgtRes = readVCGT16(prof); + if (!vcgtRes) + return std::unexpected(vcgtRes.error()); + + image.icc.vcgt = *vcgtRes; + + if (!*vcgtRes) + Log::logger->log(Log::DEBUG, "ICC profile has no VCGT data"); + } else + Log::logger->log(Log::DEBUG, "Skipping VCGT load, disabled by config"); + + Log::logger->log(Log::DEBUG, "============= End ICC load ============="); + + return image; +} \ No newline at end of file diff --git a/src/helpers/env/Env.cpp b/src/helpers/env/Env.cpp new file mode 100644 index 000000000..606d5f728 --- /dev/null +++ b/src/helpers/env/Env.cpp @@ -0,0 +1,19 @@ +#include "Env.hpp" + +#include +#include + +bool Env::envEnabled(const std::string& env) { + auto ret = getenv(env.c_str()); + if (!ret) + return false; + + const std::string_view sv = ret; + + return !sv.empty() && sv != "0"; +} + +bool Env::isTrace() { + static bool TRACE = envEnabled("HYPRLAND_TRACE"); + return TRACE; +} diff --git a/src/helpers/env/Env.hpp b/src/helpers/env/Env.hpp new file mode 100644 index 000000000..030fe7365 --- /dev/null +++ b/src/helpers/env/Env.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include + +namespace Env { + bool envEnabled(const std::string& env); + bool isTrace(); +} diff --git a/src/helpers/fs/FsUtils.cpp b/src/helpers/fs/FsUtils.cpp index 0bc2e6857..60af7d44b 100644 --- a/src/helpers/fs/FsUtils.cpp +++ b/src/helpers/fs/FsUtils.cpp @@ -1,8 +1,9 @@ #include "FsUtils.hpp" -#include "../../debug/Log.hpp" +#include "../../debug/log/Logger.hpp" #include #include +#include #include #include @@ -17,7 +18,7 @@ std::optional NFsUtils::getDataHome() { const auto HOME = getenv("HOME"); if (!HOME) { - Debug::log(ERR, "FsUtils::getDataHome: can't get data home: no $HOME or $XDG_DATA_HOME"); + Log::logger->log(Log::ERR, "FsUtils::getDataHome: can't get data home: no $HOME or $XDG_DATA_HOME"); return std::nullopt; } @@ -27,26 +28,26 @@ std::optional NFsUtils::getDataHome() { std::error_code ec; if (!std::filesystem::exists(dataRoot, ec) || ec) { - Debug::log(ERR, "FsUtils::getDataHome: can't get data home: inaccessible / missing"); + Log::logger->log(Log::ERR, "FsUtils::getDataHome: can't get data home: inaccessible / missing"); return std::nullopt; } dataRoot += "hyprland/"; if (!std::filesystem::exists(dataRoot, ec) || ec) { - Debug::log(LOG, "FsUtils::getDataHome: no hyprland data home, creating."); + Log::logger->log(Log::DEBUG, "FsUtils::getDataHome: no hyprland data home, creating."); std::filesystem::create_directory(dataRoot, ec); if (ec) { - Debug::log(ERR, "FsUtils::getDataHome: can't create new data home for hyprland"); + Log::logger->log(Log::ERR, "FsUtils::getDataHome: can't create new data home for hyprland"); return std::nullopt; } std::filesystem::permissions(dataRoot, std::filesystem::perms::owner_read | std::filesystem::perms::owner_write | std::filesystem::perms::owner_exec, ec); if (ec) - Debug::log(WARN, "FsUtils::getDataHome: couldn't set perms on hyprland data store. Proceeding anyways."); + Log::logger->log(Log::WARN, "FsUtils::getDataHome: couldn't set perms on hyprland data store. Proceeding anyways."); } if (!std::filesystem::exists(dataRoot, ec) || ec) { - Debug::log(ERR, "FsUtils::getDataHome: no hyprland data home, failed to create."); + Log::logger->log(Log::ERR, "FsUtils::getDataHome: no hyprland data home, failed to create."); return std::nullopt; } @@ -69,7 +70,7 @@ std::optional NFsUtils::readFileAsString(const std::string& path) { bool NFsUtils::writeToFile(const std::string& path, const std::string& content) { std::ofstream of(path, std::ios::trunc); if (!of.good()) { - Debug::log(ERR, "CVersionKeeperManager: couldn't open an ofstream for writing the version file."); + Log::logger->log(Log::ERR, "CVersionKeeperManager: couldn't open an ofstream for writing the version file."); return false; } diff --git a/src/helpers/math/Direction.cpp b/src/helpers/math/Direction.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/src/helpers/math/Direction.hpp b/src/helpers/math/Direction.hpp new file mode 100644 index 000000000..9905db4f8 --- /dev/null +++ b/src/helpers/math/Direction.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +namespace Math { + enum eDirection : int8_t { + DIRECTION_DEFAULT = -1, + DIRECTION_UP, + DIRECTION_RIGHT, + DIRECTION_DOWN, + DIRECTION_LEFT + }; + + inline eDirection fromChar(char x) { + switch (x) { + case 'r': return DIRECTION_RIGHT; + case 'l': return DIRECTION_LEFT; + case 't': + case 'u': return DIRECTION_UP; + case 'b': + case 'd': return DIRECTION_DOWN; + default: return DIRECTION_DEFAULT; + } + } + + inline const char* toString(eDirection d) { + switch (d) { + case DIRECTION_UP: return "up"; + case DIRECTION_DOWN: return "down"; + case DIRECTION_LEFT: return "left"; + case DIRECTION_RIGHT: return "right"; + default: return "default"; + } + } +}; \ No newline at end of file diff --git a/src/helpers/math/Expression.cpp b/src/helpers/math/Expression.cpp index fb28628dc..3c0bee919 100644 --- a/src/helpers/math/Expression.cpp +++ b/src/helpers/math/Expression.cpp @@ -1,6 +1,6 @@ #include "Expression.hpp" #include "muParser.h" -#include "../../debug/Log.hpp" +#include "../../debug/log/Logger.hpp" using namespace Math; @@ -16,7 +16,7 @@ std::optional CExpression::compute(const std::string& expr) { try { m_parser->SetExpr(expr); return m_parser->Eval(); - } catch (mu::Parser::exception_type& e) { Debug::log(ERR, "CExpression::compute: mu threw: {}", e.GetMsg()); } + } catch (mu::Parser::exception_type& e) { Log::logger->log(Log::ERR, "CExpression::compute: mu threw: {}", e.GetMsg()); } return std::nullopt; } diff --git a/src/helpers/math/Math.cpp b/src/helpers/math/Math.cpp index f927701cc..d10997b53 100644 --- a/src/helpers/math/Math.cpp +++ b/src/helpers/math/Math.cpp @@ -1,24 +1,86 @@ #include "Math.hpp" #include "../memory/Memory.hpp" +#include "../../macros.hpp" -Hyprutils::Math::eTransform wlTransformToHyprutils(wl_output_transform t) { +#include +#include + +using namespace Math; + +// FIXME: expose in hu +static std::unordered_map transforms = { + {HYPRUTILS_TRANSFORM_NORMAL, std::array{1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, + {HYPRUTILS_TRANSFORM_90, std::array{0.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, + {HYPRUTILS_TRANSFORM_180, std::array{-1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, + {HYPRUTILS_TRANSFORM_270, std::array{0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, + {HYPRUTILS_TRANSFORM_FLIPPED, std::array{-1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, + {HYPRUTILS_TRANSFORM_FLIPPED_90, std::array{0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, + {HYPRUTILS_TRANSFORM_FLIPPED_180, std::array{1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, + {HYPRUTILS_TRANSFORM_FLIPPED_270, std::array{0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, +}; + +eTransform Math::wlTransformToHyprutils(wl_output_transform t) { switch (t) { - case WL_OUTPUT_TRANSFORM_NORMAL: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_NORMAL; - case WL_OUTPUT_TRANSFORM_180: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_180; - case WL_OUTPUT_TRANSFORM_90: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_90; - case WL_OUTPUT_TRANSFORM_270: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_270; - case WL_OUTPUT_TRANSFORM_FLIPPED: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED; - case WL_OUTPUT_TRANSFORM_FLIPPED_180: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED_180; - case WL_OUTPUT_TRANSFORM_FLIPPED_270: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED_270; - case WL_OUTPUT_TRANSFORM_FLIPPED_90: return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_FLIPPED_90; + case WL_OUTPUT_TRANSFORM_NORMAL: return eTransform::HYPRUTILS_TRANSFORM_NORMAL; + case WL_OUTPUT_TRANSFORM_180: return eTransform::HYPRUTILS_TRANSFORM_180; + case WL_OUTPUT_TRANSFORM_90: return eTransform::HYPRUTILS_TRANSFORM_90; + case WL_OUTPUT_TRANSFORM_270: return eTransform::HYPRUTILS_TRANSFORM_270; + case WL_OUTPUT_TRANSFORM_FLIPPED: return eTransform::HYPRUTILS_TRANSFORM_FLIPPED; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: return eTransform::HYPRUTILS_TRANSFORM_FLIPPED_180; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: return eTransform::HYPRUTILS_TRANSFORM_FLIPPED_270; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: return eTransform::HYPRUTILS_TRANSFORM_FLIPPED_90; default: break; } - return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_NORMAL; + return eTransform::HYPRUTILS_TRANSFORM_NORMAL; } -wl_output_transform invertTransform(wl_output_transform tr) { +wl_output_transform Math::invertTransform(wl_output_transform tr) { if ((tr & WL_OUTPUT_TRANSFORM_90) && !(tr & WL_OUTPUT_TRANSFORM_FLIPPED)) tr = sc(tr ^ sc(WL_OUTPUT_TRANSFORM_180)); return tr; } + +static bool matEq(const Mat3x3& a, const Mat3x3& b) { + for (size_t i = 0; i < 9; ++i) { + const float Δ = std::fabs(a.getMatrix()[i] - b.getMatrix()[i]); + if (Δ > 1e-6) // eps + return false; + } + return true; +} + +static eTransform composeInternal(eTransform a, eTransform b) { + const auto& A = transforms.at(a); + const auto& B = transforms.at(b); + const auto RESULT = Mat3x3{A}.multiply(B); + + for (const auto& [t, M] : transforms) { + if (matEq(M, RESULT)) + return t; + } + + return eTransform::HYPRUTILS_TRANSFORM_NORMAL; +} + +eTransform Math::composeTransform(eTransform a, eTransform b) { + static std::array, 8> lookup; + static bool once = true; + + if (once) { + once = false; + + // bake the composition table + static_assert(HYPRUTILS_TRANSFORM_FLIPPED_270 == 7); + for (size_t i = 0; i <= HYPRUTILS_TRANSFORM_FLIPPED_270 /* 7 */; ++i) { + for (size_t j = 0; j <= HYPRUTILS_TRANSFORM_FLIPPED_270 /* 7 */; ++j) { + lookup[i][j] = composeInternal(sc(i), sc(j)); + } + } + } + + RASSERT(a >= HYPRUTILS_TRANSFORM_NORMAL && a <= HYPRUTILS_TRANSFORM_FLIPPED_270, "Invalid transform a in composeTransform"); + RASSERT(b >= HYPRUTILS_TRANSFORM_NORMAL && b <= HYPRUTILS_TRANSFORM_FLIPPED_270, "Invalid transform b in composeTransform"); + + return lookup[a][b]; +} diff --git a/src/helpers/math/Math.hpp b/src/helpers/math/Math.hpp index 367d81907..cc181434c 100644 --- a/src/helpers/math/Math.hpp +++ b/src/helpers/math/Math.hpp @@ -9,5 +9,10 @@ // NOLINTNEXTLINE using namespace Hyprutils::Math; -eTransform wlTransformToHyprutils(wl_output_transform t); -wl_output_transform invertTransform(wl_output_transform tr); +namespace Math { + constexpr const Vector2D VECTOR2D_MAX = {std::numeric_limits::max(), std::numeric_limits::max()}; + + eTransform wlTransformToHyprutils(wl_output_transform t); + wl_output_transform invertTransform(wl_output_transform tr); + eTransform composeTransform(eTransform a, eTransform b); +} \ No newline at end of file diff --git a/src/helpers/sync/SyncReleaser.cpp b/src/helpers/sync/SyncReleaser.cpp index 66d7667f6..fbc585d0b 100644 --- a/src/helpers/sync/SyncReleaser.cpp +++ b/src/helpers/sync/SyncReleaser.cpp @@ -25,7 +25,7 @@ CSyncReleaser::CSyncReleaser(SP timeline, uint64_t point) : m_tim CSyncReleaser::~CSyncReleaser() { if (!m_timeline) { - Debug::log(ERR, "CSyncReleaser destructing without a timeline"); + Log::logger->log(Log::ERR, "CSyncReleaser destructing without a timeline"); return; } diff --git a/src/helpers/sync/SyncTimeline.cpp b/src/helpers/sync/SyncTimeline.cpp index 9fe2e406d..5a233e48e 100644 --- a/src/helpers/sync/SyncTimeline.cpp +++ b/src/helpers/sync/SyncTimeline.cpp @@ -16,7 +16,7 @@ SP CSyncTimeline::create(int drmFD_) { timeline->m_self = timeline; if (drmSyncobjCreate(drmFD_, 0, &timeline->m_handle)) { - Debug::log(ERR, "CSyncTimeline: failed to create a drm syncobj??"); + Log::logger->log(Log::ERR, "CSyncTimeline: failed to create a drm syncobj??"); return nullptr; } @@ -33,7 +33,7 @@ SP CSyncTimeline::create(int drmFD_, CFileDescriptor&& drmSyncobj timeline->m_self = timeline; if (drmSyncobjFDToHandle(drmFD_, timeline->m_syncobjFD.get(), &timeline->m_handle)) { - Debug::log(ERR, "CSyncTimeline: failed to create a drm syncobj from fd??"); + Log::logger->log(Log::ERR, "CSyncTimeline: failed to create a drm syncobj from fd??"); return nullptr; } @@ -57,7 +57,7 @@ std::optional CSyncTimeline::check(uint64_t point, uint32_t flags) { uint32_t signaled = 0; int ret = drmSyncobjTimelineWait(m_drmFD, &m_handle, &point, 1, 0, flags, &signaled); if (ret != 0 && ret != -ETIME_ERR) { - Debug::log(ERR, "CSyncTimeline::check: drmSyncobjTimelineWait failed"); + Log::logger->log(Log::ERR, "CSyncTimeline::check: drmSyncobjTimelineWait failed"); return std::nullopt; } @@ -68,12 +68,12 @@ bool CSyncTimeline::addWaiter(std::function&& waiter, uint64_t point, ui auto eventFd = CFileDescriptor(eventfd(0, EFD_CLOEXEC)); if (!eventFd.isValid()) { - Debug::log(ERR, "CSyncTimeline::addWaiter: failed to acquire an eventfd"); + Log::logger->log(Log::ERR, "CSyncTimeline::addWaiter: failed to acquire an eventfd"); return false; } if (drmSyncobjEventfd(m_drmFD, m_handle, point, eventFd.get(), flags)) { - Debug::log(ERR, "CSyncTimeline::addWaiter: drmSyncobjEventfd failed"); + Log::logger->log(Log::ERR, "CSyncTimeline::addWaiter: drmSyncobjEventfd failed"); return false; } @@ -87,18 +87,18 @@ CFileDescriptor CSyncTimeline::exportAsSyncFileFD(uint64_t src) { uint32_t syncHandle = 0; if (drmSyncobjCreate(m_drmFD, 0, &syncHandle)) { - Debug::log(ERR, "exportAsSyncFileFD: drmSyncobjCreate failed"); + Log::logger->log(Log::ERR, "exportAsSyncFileFD: drmSyncobjCreate failed"); return {}; } if (drmSyncobjTransfer(m_drmFD, syncHandle, 0, m_handle, src, 0)) { - Debug::log(ERR, "exportAsSyncFileFD: drmSyncobjTransfer failed"); + Log::logger->log(Log::ERR, "exportAsSyncFileFD: drmSyncobjTransfer failed"); drmSyncobjDestroy(m_drmFD, syncHandle); return {}; } if (drmSyncobjExportSyncFile(m_drmFD, syncHandle, &sync)) { - Debug::log(ERR, "exportAsSyncFileFD: drmSyncobjExportSyncFile failed"); + Log::logger->log(Log::ERR, "exportAsSyncFileFD: drmSyncobjExportSyncFile failed"); drmSyncobjDestroy(m_drmFD, syncHandle); return {}; } @@ -111,18 +111,18 @@ bool CSyncTimeline::importFromSyncFileFD(uint64_t dst, CFileDescriptor& fd) { uint32_t syncHandle = 0; if (drmSyncobjCreate(m_drmFD, 0, &syncHandle)) { - Debug::log(ERR, "importFromSyncFileFD: drmSyncobjCreate failed"); + Log::logger->log(Log::ERR, "importFromSyncFileFD: drmSyncobjCreate failed"); return false; } if (drmSyncobjImportSyncFile(m_drmFD, syncHandle, fd.get())) { - Debug::log(ERR, "importFromSyncFileFD: drmSyncobjImportSyncFile failed"); + Log::logger->log(Log::ERR, "importFromSyncFileFD: drmSyncobjImportSyncFile failed"); drmSyncobjDestroy(m_drmFD, syncHandle); return false; } if (drmSyncobjTransfer(m_drmFD, m_handle, dst, syncHandle, 0, 0)) { - Debug::log(ERR, "importFromSyncFileFD: drmSyncobjTransfer failed"); + Log::logger->log(Log::ERR, "importFromSyncFileFD: drmSyncobjTransfer failed"); drmSyncobjDestroy(m_drmFD, syncHandle); return false; } @@ -133,12 +133,12 @@ bool CSyncTimeline::importFromSyncFileFD(uint64_t dst, CFileDescriptor& fd) { bool CSyncTimeline::transfer(SP from, uint64_t fromPoint, uint64_t toPoint) { if (m_drmFD != from->m_drmFD) { - Debug::log(ERR, "CSyncTimeline::transfer: cannot transfer timelines between gpus, {} -> {}", from->m_drmFD, m_drmFD); + Log::logger->log(Log::ERR, "CSyncTimeline::transfer: cannot transfer timelines between gpus, {} -> {}", from->m_drmFD, m_drmFD); return false; } if (drmSyncobjTransfer(m_drmFD, m_handle, toPoint, from->m_handle, fromPoint, 0)) { - Debug::log(ERR, "CSyncTimeline::transfer: drmSyncobjTransfer failed"); + Log::logger->log(Log::ERR, "CSyncTimeline::transfer: drmSyncobjTransfer failed"); return false; } @@ -147,5 +147,5 @@ bool CSyncTimeline::transfer(SP from, uint64_t fromPoint, uint64_ void CSyncTimeline::signal(uint64_t point) { if (drmSyncobjTimelineSignal(m_drmFD, &m_handle, &point, 1)) - Debug::log(ERR, "CSyncTimeline::signal: drmSyncobjTimelineSignal failed"); + Log::logger->log(Log::ERR, "CSyncTimeline::signal: drmSyncobjTimelineSignal failed"); } diff --git a/src/helpers/time/Time.cpp b/src/helpers/time/Time.cpp index 791f5ea11..f454b7848 100644 --- a/src/helpers/time/Time.cpp +++ b/src/helpers/time/Time.cpp @@ -5,7 +5,6 @@ using s_ns = std::pair; -// HAS to be a > b static s_ns timediff(const s_ns& a, const s_ns& b) { s_ns d; @@ -13,7 +12,7 @@ static s_ns timediff(const s_ns& a, const s_ns& b) { if (a.second >= b.second) d.second = a.second - b.second; else { - d.second = b.second - a.second; + d.second = (TIMESPEC_NSEC_PER_SEC + a.second) - b.second; d.first -= 1; } @@ -46,9 +45,9 @@ uint64_t Time::millis(const steady_tp& tp) { } s_ns Time::secNsec(const steady_tp& tp) { - const uint64_t sec = chr::duration_cast(tp.time_since_epoch()).count(); - const chr::steady_clock::duration nsecdur = tp - chr::steady_clock::time_point(chr::seconds(sec)); - return std::make_pair<>(sec, chr::duration_cast(nsecdur).count()); + const uint64_t sec = chr::duration_cast(tp.time_since_epoch()).count(); + const auto nsecdur = tp - chr::steady_clock::time_point(chr::seconds(sec)); + return {sec, chr::duration_cast(nsecdur).count()}; } uint64_t Time::millis(const system_tp& tp) { @@ -56,9 +55,9 @@ uint64_t Time::millis(const system_tp& tp) { } s_ns Time::secNsec(const system_tp& tp) { - const uint64_t sec = chr::duration_cast(tp.time_since_epoch()).count(); - const chr::steady_clock::duration nsecdur = tp - chr::system_clock::time_point(chr::seconds(sec)); - return std::make_pair<>(sec, chr::duration_cast(nsecdur).count()); + const uint64_t sec = chr::duration_cast(tp.time_since_epoch()).count(); + const auto nsecdur = tp - chr::system_clock::time_point(chr::seconds(sec)); + return {sec, chr::duration_cast(nsecdur).count()}; } // TODO: this is a mess, but C++ doesn't define what steady_clock is. @@ -69,12 +68,12 @@ s_ns Time::secNsec(const system_tp& tp) { // In general, this may shift the time around by a couple hundred ns. Doesn't matter, realistically. Time::steady_tp Time::fromTimespec(const timespec* ts) { - struct timespec mono, real; + timespec mono{}, real{}; clock_gettime(CLOCK_MONOTONIC, &mono); clock_gettime(CLOCK_REALTIME, &real); - Time::steady_tp now = Time::steadyNow(); - Time::system_tp nowSys = Time::systemNow(); - s_ns stdSteady, stdReal; + auto now = Time::steadyNow(); + auto nowSys = Time::systemNow(); + s_ns stdSteady, stdReal; stdSteady = Time::secNsec(now); stdReal = Time::secNsec(nowSys); @@ -84,7 +83,7 @@ Time::steady_tp Time::fromTimespec(const timespec* ts) { if (real.tv_nsec >= mono.tv_nsec) diff.second = real.tv_nsec - mono.tv_nsec; else { - diff.second = mono.tv_nsec - real.tv_nsec; + diff.second = TIMESPEC_NSEC_PER_SEC + real.tv_nsec - mono.tv_nsec; diff.first -= 1; } @@ -104,7 +103,7 @@ Time::steady_tp Time::fromTimespec(const timespec* ts) { } struct timespec Time::toTimespec(const steady_tp& tp) { - struct timespec mono, real; + timespec mono{}, real{}; clock_gettime(CLOCK_MONOTONIC, &mono); clock_gettime(CLOCK_REALTIME, &real); Time::steady_tp now = Time::steadyNow(); @@ -119,7 +118,7 @@ struct timespec Time::toTimespec(const steady_tp& tp) { if (real.tv_nsec >= mono.tv_nsec) diff.second = real.tv_nsec - mono.tv_nsec; else { - diff.second = mono.tv_nsec - real.tv_nsec; + diff.second = TIMESPEC_NSEC_PER_SEC + real.tv_nsec - mono.tv_nsec; diff.first -= 1; } @@ -137,3 +136,10 @@ struct timespec Time::toTimespec(const steady_tp& tp) { auto sum = timeadd(tpTime, diffFinal); return timespec{.tv_sec = sum.first, .tv_nsec = sum.second}; } + +Time::steady_dur Time::till(const timespec& ts) { + timespec mono{}; + clock_gettime(CLOCK_MONOTONIC, &mono); + const auto delay = (ts.tv_sec - mono.tv_sec) * 1000000000 + (ts.tv_nsec - mono.tv_nsec); + return std::chrono::nanoseconds(delay); +} diff --git a/src/helpers/time/Time.hpp b/src/helpers/time/Time.hpp index eb3b57715..ce99982b7 100644 --- a/src/helpers/time/Time.hpp +++ b/src/helpers/time/Time.hpp @@ -17,6 +17,7 @@ namespace Time { steady_tp fromTimespec(const timespec*); struct timespec toTimespec(const steady_tp& tp); + steady_dur till(const timespec& ts); uint64_t millis(const steady_tp& tp); uint64_t millis(const system_tp& tp); diff --git a/src/hyprerror/HyprError.cpp b/src/hyprerror/HyprError.cpp deleted file mode 100644 index 3446fc4bd..000000000 --- a/src/hyprerror/HyprError.cpp +++ /dev/null @@ -1,228 +0,0 @@ -#include -#include "HyprError.hpp" -#include "../Compositor.hpp" -#include "../config/ConfigValue.hpp" -#include "../config/ConfigManager.hpp" -#include "../render/pass/TexPassElement.hpp" -#include "../managers/animation/AnimationManager.hpp" -#include "../render/Renderer.hpp" -#include "../managers/HookSystemManager.hpp" -#include "../desktop/state/FocusState.hpp" - -#include -using namespace Hyprutils::Animation; - -CHyprError::CHyprError() { - g_pAnimationManager->createAnimation(0.f, m_fadeOpacity, g_pConfigManager->getAnimationPropertyConfig("fadeIn"), AVARDAMAGE_NONE); - - static auto P = g_pHookSystem->hookDynamic("focusedMon", [&](void* self, SCallbackInfo& info, std::any param) { - if (!m_isCreated) - return; - - g_pHyprRenderer->damageMonitor(Desktop::focusState()->monitor()); - m_monitorChanged = true; - }); - - 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) { - m_queued = message; - m_queuedColor = color; -} - -void CHyprError::createQueued() { - if (m_isCreated) - m_texture->destroyTexture(); - - m_fadeOpacity->setConfig(g_pConfigManager->getAnimationPropertyConfig("fadeIn")); - - m_fadeOpacity->setValueAndWarp(0.f); - *m_fadeOpacity = 1.f; - - const auto PMONITOR = g_pCompositor->m_monitors.front(); - - const auto SCALE = PMONITOR->m_scale; - - const auto FONTSIZE = std::clamp(sc(10.f * ((PMONITOR->m_pixelSize.x * SCALE) / 1920.f)), 8, 40); - - const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y); - - const auto CAIRO = cairo_create(CAIROSURFACE); - - // clear the pixmap - cairo_save(CAIRO); - cairo_set_operator(CAIRO, CAIRO_OPERATOR_CLEAR); - cairo_paint(CAIRO); - cairo_restore(CAIRO); - - const auto LINECOUNT = Hyprlang::INT{1} + std::ranges::count(m_queued, '\n'); - static auto LINELIMIT = CConfigValue("debug:error_limit"); - static auto BAR_POSITION = CConfigValue("debug:error_position"); - - const bool TOPBAR = *BAR_POSITION == 0; - - const auto VISLINECOUNT = std::min(LINECOUNT, *LINELIMIT); - const auto EXTRALINES = (VISLINECOUNT < LINECOUNT) ? 1 : 0; - - const double DEGREES = M_PI / 180.0; - - const double PAD = 10 * SCALE; - - const double WIDTH = PMONITOR->m_pixelSize.x - PAD * 2; - const double HEIGHT = (FONTSIZE + 2 * (FONTSIZE / 10.0)) * (VISLINECOUNT + EXTRALINES) + 3; - const double RADIUS = PAD > HEIGHT / 2 ? HEIGHT / 2 - 1 : PAD; - const double X = PAD; - const double Y = TOPBAR ? PAD : PMONITOR->m_pixelSize.y - HEIGHT - PAD; - - m_damageBox = {0, 0, sc(PMONITOR->m_pixelSize.x), sc(HEIGHT) + sc(PAD) * 2}; - - cairo_new_sub_path(CAIRO); - cairo_arc(CAIRO, X + WIDTH - RADIUS, Y + RADIUS, RADIUS, -90 * DEGREES, 0 * DEGREES); - cairo_arc(CAIRO, X + WIDTH - RADIUS, Y + HEIGHT - RADIUS, RADIUS, 0 * DEGREES, 90 * DEGREES); - cairo_arc(CAIRO, X + RADIUS, Y + HEIGHT - RADIUS, RADIUS, 90 * DEGREES, 180 * DEGREES); - cairo_arc(CAIRO, X + RADIUS, Y + RADIUS, RADIUS, 180 * DEGREES, 270 * DEGREES); - cairo_close_path(CAIRO); - - cairo_set_source_rgba(CAIRO, 0.06, 0.06, 0.06, 1.0); - cairo_fill_preserve(CAIRO); - cairo_set_source_rgba(CAIRO, m_queuedColor.r, m_queuedColor.g, m_queuedColor.b, m_queuedColor.a); - cairo_set_line_width(CAIRO, 2); - cairo_stroke(CAIRO); - - // draw the text with a common font - const CHyprColor textColor = CHyprColor(0.9, 0.9, 0.9, 1.0); - cairo_set_source_rgba(CAIRO, textColor.r, textColor.g, textColor.b, textColor.a); - - static auto fontFamily = CConfigValue("misc:font_family"); - PangoLayout* layoutText = pango_cairo_create_layout(CAIRO); - PangoFontDescription* pangoFD = pango_font_description_new(); - - pango_font_description_set_family(pangoFD, (*fontFamily).c_str()); - pango_font_description_set_absolute_size(pangoFD, FONTSIZE * PANGO_SCALE); - 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); - - float yoffset = TOPBAR ? 0 : Y - PAD; - int renderedcnt = 0; - while (!m_queued.empty() && renderedcnt < VISLINECOUNT) { - std::string current = m_queued.substr(0, m_queued.find('\n')); - if (const auto NEWLPOS = m_queued.find('\n'); NEWLPOS != std::string::npos) - m_queued = m_queued.substr(NEWLPOS + 1); - else - m_queued = ""; - cairo_move_to(CAIRO, PAD + 1 + RADIUS, yoffset + PAD + 1); - pango_layout_set_text(layoutText, current.c_str(), -1); - pango_cairo_show_layout(CAIRO, layoutText); - yoffset += FONTSIZE + (FONTSIZE / 10.f); - renderedcnt++; - } - if (VISLINECOUNT < LINECOUNT) { - std::string moreString = std::format("({} more...)", LINECOUNT - VISLINECOUNT); - cairo_move_to(CAIRO, PAD + 1 + RADIUS, yoffset + PAD + 1); - pango_layout_set_text(layoutText, moreString.c_str(), -1); - pango_cairo_show_layout(CAIRO, layoutText); - } - m_queued = ""; - - m_lastHeight = yoffset + PAD + 1 - (TOPBAR ? 0 : Y - PAD); - - pango_font_description_free(pangoFD); - g_object_unref(layoutText); - - cairo_surface_flush(CAIROSURFACE); - - // copy the data to an OpenGL texture we have - const auto DATA = cairo_image_surface_get_data(CAIROSURFACE); - m_texture->allocate(); - m_texture->bind(); - m_texture->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); - m_texture->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); - m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); - m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA); - - // delete cairo - cairo_destroy(CAIRO); - cairo_surface_destroy(CAIROSURFACE); - - m_isCreated = true; - m_queued = ""; - m_queuedColor = CHyprColor(); - - g_pHyprRenderer->damageMonitor(PMONITOR); - - g_pHyprRenderer->arrangeLayersForMonitor(PMONITOR->m_id); -} - -void CHyprError::draw() { - if (!m_isCreated || !m_queued.empty()) { - if (!m_queued.empty()) - createQueued(); - return; - } - - if (m_queuedDestroy) { - if (!m_fadeOpacity->isBeingAnimated()) { - if (m_fadeOpacity->value() == 0.f) { - m_queuedDestroy = false; - m_texture->destroyTexture(); - m_isCreated = false; - m_queued = ""; - - for (auto& m : g_pCompositor->m_monitors) { - g_pHyprRenderer->arrangeLayersForMonitor(m->m_id); - } - - return; - } else { - m_fadeOpacity->setConfig(g_pConfigManager->getAnimationPropertyConfig("fadeOut")); - *m_fadeOpacity = 0.f; - } - } - } - - const auto PMONITOR = g_pHyprOpenGL->m_renderData.pMonitor; - - CBox monbox = {0, 0, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y}; - - m_damageBox.x = sc(PMONITOR->m_position.x); - m_damageBox.y = sc(PMONITOR->m_position.y); - - if (m_fadeOpacity->isBeingAnimated() || m_monitorChanged) - g_pHyprRenderer->damageBox(m_damageBox); - - m_monitorChanged = false; - - CTexPassElement::SRenderData data; - data.tex = m_texture; - data.box = monbox; - data.a = m_fadeOpacity->value(); - - g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(data))); -} - -void CHyprError::destroy() { - if (m_isCreated) - m_queuedDestroy = true; - else - m_queued = ""; -} - -bool CHyprError::active() { - return m_isCreated; -} - -float CHyprError::height() { - return m_lastHeight; -} diff --git a/src/hyprerror/HyprError.hpp b/src/hyprerror/HyprError.hpp deleted file mode 100644 index f4bc43d88..000000000 --- a/src/hyprerror/HyprError.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include "../defines.hpp" -#include "../render/Texture.hpp" -#include "../helpers/AnimatedVariable.hpp" - -#include - -class CHyprError { - public: - CHyprError(); - ~CHyprError() = default; - - void queueCreate(std::string message, const CHyprColor& color); - void draw(); - void destroy(); - - bool active(); - float height(); // logical - - private: - void createQueued(); - std::string m_queued = ""; - CHyprColor m_queuedColor; - bool m_queuedDestroy = false; - bool m_isCreated = false; - SP m_texture; - PHLANIMVAR m_fadeOpacity; - CBox m_damageBox = {0, 0, 0, 0}; - float m_lastHeight = 0.F; - - bool m_monitorChanged = false; -}; - -inline UP g_pHyprError; // This is a full-screen error. Treat it with respect, and there can only be one at a time. diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp index e777f81a8..92280e760 100644 --- a/src/i18n/Engine.cpp +++ b/src/i18n/Engine.cpp @@ -60,6 +60,153 @@ 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?"); + huEngine->registerEntry("da_DK", TXT_KEY_ANR_OPTION_TERMINATE, "Luk"); + huEngine->registerEntry("da_DK", TXT_KEY_ANR_OPTION_WAIT, "Vent"); + huEngine->registerEntry("da_DK", TXT_KEY_ANR_PROP_UNKNOWN, "(ukendt)"); + + huEngine->registerEntry("da_DK", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "En applikation {app} forespørger en ukendt rettighed."); + huEngine->registerEntry("da_DK", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "En applikation {app} forsøger at optage din skærm.\n\nVil du tillade dette?"); + huEngine->registerEntry("da_DK", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "En applikation {app} forsøger at indlæse et plugin: {plugin}.\n\nVil du tillade dette?"); + huEngine->registerEntry("da_DK", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Et nyt tastatur er fundet: {keyboard}.\n\nVil du tillade den at fungere?"); + huEngine->registerEntry("da_DK", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(ukendt)"); + huEngine->registerEntry("da_DK", TXT_KEY_PERMISSION_TITLE, "Anmodning om tilladelse"); + huEngine->registerEntry("da_DK", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Tip: Du kan indstille vedvarende regler for disse i Hyprland-konfigurationsfilen."); + huEngine->registerEntry("da_DK", TXT_KEY_PERMISSION_ALLOW, "Tillad"); + huEngine->registerEntry("da_DK", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Tillad og husk"); + huEngine->registerEntry("da_DK", TXT_KEY_PERMISSION_ALLOW_ONCE, "Tillad én gang"); + huEngine->registerEntry("da_DK", TXT_KEY_PERMISSION_DENY, "Nægt"); + huEngine->registerEntry("da_DK", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Ukendt applikation (wayland client ID {wayland_id})"); + + huEngine->registerEntry( + "da_DK", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Dit XDG_CURRENT_DESKTOP miljø ser ud til at være administreret externt, og den nuværende værdi er {value}.\nDette kan forårsage problemer, medmindre det er bevidst."); + huEngine->registerEntry("da_DK", TXT_KEY_NOTIF_NO_GUIUTILS, + "Dit system har ikke hyprland-guiutils installeret. Dette er en runtime-afhængighed for nogle dialoger. Overvej at installere den."); + huEngine->registerEntry("da_DK", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland kunne ikke indlæse {count} essentiale aktiver, skyd skylden på din distributions pakker for et dårligt stykke arbejde af pakningen!"; + return "Hyprland kunne ikke indlæse {count} essentiale aktiver, skyd skylden på din distributions pakker for et dårligt stykke arbejde af pakningen!"; + }); + huEngine->registerEntry("da_DK", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Dit skærmlayout har en ukorrekt opsætning. Skærm {name} overlapper med andre skærm(e) i layoutet.\nLæs venligst wiki'en (Monitors page) for " + "mere. Dette vil skabe problemer."); + huEngine->registerEntry("da_DK", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Skærm {name} kunne ikke indlæse nogen af de ønskede tilstande, vender tilbage til tilstand {mode}."); + huEngine->registerEntry("da_DK", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Ugyldig skalering sendt til skærm {name}: {scale}, bruger foreslået skalering: {fixed_scale}"); + huEngine->registerEntry("da_DK", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Kunne ikke indlæse plugin {name}: {error}"); + huEngine->registerEntry("da_DK", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Genindlæsning af CM-shader mislykkedes, går tilbage til rgba/rgbx."); + huEngine->registerEntry("da_DK", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Skærm {name}: wide color gamut er aktiveret men skærmen er ikke i 10-bit tilstand."); + + // el_GR (Greek) + huEngine->registerEntry("el_GR", TXT_KEY_ANR_TITLE, "Η εφαρμογή δεν αποκρίνεται"); + huEngine->registerEntry("el_GR", TXT_KEY_ANR_CONTENT, "Η εφαρμογή {title} - {class} δεν αποκρίνεται.\nΤι θέλετε να κάνετε;"); + huEngine->registerEntry("el_GR", TXT_KEY_ANR_OPTION_TERMINATE, "Τερματισμός"); + huEngine->registerEntry("el_GR", TXT_KEY_ANR_OPTION_WAIT, "Αναμονή"); + huEngine->registerEntry("el_GR", TXT_KEY_ANR_PROP_UNKNOWN, "(άγνωστο)"); + + huEngine->registerEntry("el_GR", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Μια εφαρμογή {app} ζητά μια άγνωστη άδεια."); + huEngine->registerEntry("el_GR", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Μια εφαρμογή {app} προσπαθεί να καταγράψει την οθόνη σας.\n\nΘέλετε να το επιτρέψετε;"); + huEngine->registerEntry("el_GR", TXT_KEY_PERMISSION_REQUEST_CURSOR_POS, + "Μια εφαρμογή {app} προσπαθεί να καταγράψει τη θέση του δρομέα σας.\n\nΘέλετε να το επιτρέψετε;"); + huEngine->registerEntry("el_GR", TXT_KEY_PERMISSION_REQUEST_PLUGIN, + "Μια εφαρμογή {app} προσπαθεί να φορτώσει ένα πρόσθετο: {plugin}.\n\nΘέλετε να το επιτρέψετε;"); + huEngine->registerEntry("el_GR", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Εντοπίστηκε νέο πληκτρολόγιο: {keyboard}.\n\nΘέλετε να επιτρέψετε τη λειτουργία του;"); + huEngine->registerEntry("el_GR", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(άγνωστο)"); + huEngine->registerEntry("el_GR", TXT_KEY_PERMISSION_TITLE, "Αίτημα άδειας"); + huEngine->registerEntry("el_GR", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Συμβουλή: μπορείτε να ορίσετε μόνιμους κανόνες γι' αυτά στο αρχείο ρυθμίσεων του Hyprland."); + huEngine->registerEntry("el_GR", TXT_KEY_PERMISSION_ALLOW, "Αποδοχή"); + huEngine->registerEntry("el_GR", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Αποδοχή και απομνημόνευση"); + huEngine->registerEntry("el_GR", TXT_KEY_PERMISSION_ALLOW_ONCE, "Αποδοχή μία φορά"); + huEngine->registerEntry("el_GR", TXT_KEY_PERMISSION_DENY, "Απόρριψη"); + huEngine->registerEntry("el_GR", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Άγνωστη εφαρμογή (αναγνωριστικό wayland πελάτη {wayland_id})"); + + huEngine->registerEntry("el_GR", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Η μεταβλητή περιβάλλοντος XDG_CURRENT_DESKTOP φαίνεται να διαχειρίζεται εξωτερικά, με τρέχουσα τιμή: {value}.\nΑυτό μπορεί να προκαλέσει προβλήματα " + "αν δεν είναι σκόπιμο."); + huEngine->registerEntry("el_GR", TXT_KEY_NOTIF_NO_GUIUTILS, + "Το σύστημά σας δεν έχει εγκατεστημένο το hyprland-guiutils, το οποίο χρησιμοποιείται για ορισμένα παράθυρα διαλόγου. Σκεφτείτε να το εγκαταστήσετε."); + huEngine->registerEntry("el_GR", 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("el_GR", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Η διάταξη των οθονών σας είναι εσφαλμένη. Η οθόνη {name} επικαλύπτεται με άλλη(ες) οθόνη(ες) στη διάταξη.\nΔείτε το wiki (σελίδα Monitors) για " + "περισσότερα. Αυτό θα προκαλέσει προβλήματα."); + huEngine->registerEntry("el_GR", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Η οθόνη {name} απέτυχε να ορίσει οποιαδήποτε ζητούμενη λειτουργία, επιστροφή στη λειτουργία {mode}."); + huEngine->registerEntry("el_GR", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Μη έγκυρη κλίμακα για την οθόνη {name}: {scale}, χρησιμοποιείται η προτεινόμενη κλίμακα: {fixed_scale}"); + huEngine->registerEntry("el_GR", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Αποτυχία φόρτωσης πρόσθετου {name}: {error}"); + huEngine->registerEntry("el_GR", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Η επαναφόρτωση του CM shader απέτυχε, επιστροφή σε rgba/rgbx."); + huEngine->registerEntry("el_GR", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, + "Οθόνη {name}: η ευρεία γκάμα χρωμάτων είναι ενεργοποιημένη αλλά η οθόνη δεν βρίσκεται σε λειτουργία 10-bit."); + huEngine->registerEntry("el_GR", TXT_KEY_NOTIF_NO_WATCHDOG, + "Το Hyprland εκκινήθηκε χωρίς το start-hyprland. Αυτό δεν συνιστάται εκτός αν βρίσκεστε σε περιβάλλον αποσφαλμάτωσης."); + + huEngine->registerEntry("el_GR", TXT_KEY_SAFE_MODE_TITLE, "Ασφαλής λειτουργία"); + huEngine->registerEntry("el_GR", TXT_KEY_SAFE_MODE_DESCRIPTION, + "Το Hyprland εκκινήθηκε σε ασφαλή λειτουργία, που σημαίνει ότι η τελευταία σας συνεδρία κατέρρευσε.\nΗ ασφαλής λειτουργία αποτρέπει τη φόρτωση " + "των ρυθμίσεών σας. Μπορείτε να αντιμετωπίσετε προβλήματα σε αυτό το περιβάλλον ή να φορτώσετε τις ρυθμίσεις σας με το παρακάτω κουμπί.\nΙσχύουν " + "οι προεπιλεγμένες συντομεύσεις: SUPER+Q για kitty, SUPER+R για βασικό εκκινητή, SUPER+M για έξοδο.\nΗ επανεκκίνηση του Hyprland θα γίνει σε " + "κανονική λειτουργία."); + huEngine->registerEntry("el_GR", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, "Φόρτωση ρυθμίσεων"); + huEngine->registerEntry("el_GR", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, "Άνοιγμα φακέλου αναφορών κατάρρευσης"); + huEngine->registerEntry("el_GR", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, "Εντάξει, κλείσιμο"); + // en_US (English) huEngine->registerEntry("en_US", TXT_KEY_ANR_TITLE, "Application Not Responding"); huEngine->registerEntry("en_US", TXT_KEY_ANR_CONTENT, "An application {title} - {class} is not responding.\nWhat do you want to do with it?"); @@ -69,6 +216,8 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "An application {app} is requesting an unknown permission."); huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "An application {app} is trying to capture your screen.\n\nDo you want to allow it to?"); + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_CURSOR_POS, + "An application {app} is trying to capture your cursor position.\n\nDo you want to allow it to?"); huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "An application {app} is trying to load a plugin: {plugin}.\n\nDo you want to allow it to?"); huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "A new keyboard has been detected: {keyboard}.\n\nDo you want to allow it to operate?"); huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(unknown)"); @@ -99,6 +248,18 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("en_US", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Failed to load plugin {name}: {error}"); huEngine->registerEntry("en_US", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM shader reload failed, falling back to rgba/rgbx."); huEngine->registerEntry("en_US", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: wide color gamut is enabled but the display is not in 10-bit mode."); + huEngine->registerEntry("en_US", TXT_KEY_NOTIF_NO_WATCHDOG, + "Hyprland was started without start-hyprland. This is highly not recommended unless you are in a debugging environment."); + + huEngine->registerEntry("en_US", TXT_KEY_SAFE_MODE_TITLE, "Safe Mode"); + huEngine->registerEntry("en_US", TXT_KEY_SAFE_MODE_DESCRIPTION, + "Hyprland has been launched in safe mode, which means your last session crashed.\nSafe mode prevents your config from being loaded. You can " + "troubleshoot in this environment, or load your config with the button below.\nDefault keybinds apply: SUPER+Q for kitty, SUPER+R for a basic runner, " + "SUPER+M to exit.\nRestarting " + "Hyprland will launch in normal mode again."); + huEngine->registerEntry("en_US", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, "Load config"); + huEngine->registerEntry("en_US", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, "Open crash report directory"); + huEngine->registerEntry("en_US", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, "Ok, close this"); // as_IN (Assamese) huEngine->registerEntry("as_IN", TXT_KEY_ANR_TITLE, "এপ্লিকেচনে উত্তৰ দিয়া নাই"); @@ -369,6 +530,46 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("fa_IR", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "مانیتور {name}: گسترهٔ رنگ وسیع فعال است اما نمایشگر در حالت ۱۰ بیتی نیست."); + // fi_FI (Finnish) + huEngine->registerEntry("fi_FI", TXT_KEY_ANR_TITLE, "Sovellus ei vastaa"); + huEngine->registerEntry("fi_FI", TXT_KEY_ANR_CONTENT, "Sovellus {title} - {class} ei vastaa.\nMitä haluat tehdä sille?"); + huEngine->registerEntry("fi_FI", TXT_KEY_ANR_OPTION_TERMINATE, "Lopeta"); + huEngine->registerEntry("fi_FI", TXT_KEY_ANR_OPTION_WAIT, "Odota"); + huEngine->registerEntry("fi_FI", TXT_KEY_ANR_PROP_UNKNOWN, "(tuntematon)"); + + huEngine->registerEntry("fi_FI", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Sovellus {app} pyytää tuntematonta käyttöoikeutta."); + huEngine->registerEntry("fi_FI", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Sovellus {app} yrittää nauhoittaa näyttöäsi.\n\nHaluatko sallia nauhoituksen?"); + huEngine->registerEntry("fi_FI", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Sovellus {app} yrittää ladata laajennusta: {plugin}.\n\nHaluatko sallia latauksen?"); + huEngine->registerEntry("fi_FI", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Uusi näppäimistö havaittu: {keyboard}.\n\nHaluatko sallia sen toiminnan?"); + huEngine->registerEntry("fi_FI", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(tuntematon)"); + huEngine->registerEntry("fi_FI", TXT_KEY_PERMISSION_TITLE, "Käyttöoikeuspyyntö"); + huEngine->registerEntry("fi_FI", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Vihje: voit asettaa nämä säännöt pysyvästi Hyprland konfiguraatio tiedostossa."); + huEngine->registerEntry("fi_FI", TXT_KEY_PERMISSION_ALLOW, "Salli"); + huEngine->registerEntry("fi_FI", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Salli ja muista"); + huEngine->registerEntry("fi_FI", TXT_KEY_PERMISSION_ALLOW_ONCE, "Salli kerran"); + huEngine->registerEntry("fi_FI", TXT_KEY_PERMISSION_DENY, "Kiellä"); + huEngine->registerEntry("fi_FI", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Tuntematon sovellus (wayland client ID {wayland_id})"); + + huEngine->registerEntry("fi_FI", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "XDG_CURRENT_DESKTOP ympäristösi näyttäisi olevan ulkoisesti hallittu, ja sen nykyinen arvo on {value}.\nTämä voi aiheuttaa ongelmia, jos sitä ei ole " + "tehty tarkoituksella."); + huEngine->registerEntry("fi_FI", TXT_KEY_NOTIF_NO_GUIUTILS, + "Paketti hyprland-guiutils ei ole asennettuna järjestelmääsi. Jotkin dialogit tarvitsevat sitä. Harkitse sen asentamista."); + huEngine->registerEntry("fi_FI", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland epäonnistui olennaisen resurssin ({count}) latauksessa. Tämä johtuu todennäköisesti jakelusi virheellisestä pakkauksesta."; + return "Hyprland epäonnistui olennaisten resurssien ({count}) latauksessa. Tämä johtuu todennäköisesti jakelusi virheellisestä pakkauksesta."; + }); + huEngine->registerEntry( + "fi_FI", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Näyttöjesi asettelu on virheellinen. Näyttö {name} on muiden näyttöjen päällä.\nLisätietoja löydät wikistä (Monitors sivu). Tämä tulee aiheuttamaan ongelmia."); + huEngine->registerEntry("fi_FI", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Näyttö {name} epäonnistui pyydetyn tilan asettamisessa, palataan tilaan {mode}."); + huEngine->registerEntry("fi_FI", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Näytölle {name} asetettu skaalaus: {scale} on virheellinen, asetetaan suositeltu skaalaus: {fixed_scale}."); + huEngine->registerEntry("fi_FI", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Laajennuksen {name} lataus epäonnistui: {error}"); + huEngine->registerEntry("fi_FI", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM varjostimen uudelleenlataus epäonnistui, palataan takaisin rgba/rgbx tilaan."); + huEngine->registerEntry("fi_FI", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Näyttö {name}: laaja väriskaala on otettu käyttöön, mutta näyttö ei ole 10-bit tilassa."); + // fr_FR (French) huEngine->registerEntry("fr_FR", TXT_KEY_ANR_TITLE, "L'application ne répond plus"); huEngine->registerEntry("fr_FR", TXT_KEY_ANR_CONTENT, "L'application {title} - {class} ne répond plus.\nQue voulez-vous faire?"); @@ -466,6 +667,47 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("hi_IN", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM शेडर रीलोड विफल हुआ, rgba/rgbx पर वापस जा रहा है।"); huEngine->registerEntry("hi_IN", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "मॉनिटर {name}: वाइड कलर गैम सक्षम है लेकिन डिस्प्ले 10-बिट मोड में नहीं है।"); + // id_ID (Indonesia) + huEngine->registerEntry("id_ID", TXT_KEY_ANR_TITLE, "Aplikasi Tidak Merespon"); + huEngine->registerEntry("id_ID", TXT_KEY_ANR_CONTENT, "Aplikasi {title} - {class} tidak merespon.\nApa yang ingin Anda lakukan?"); + huEngine->registerEntry("id_ID", TXT_KEY_ANR_OPTION_TERMINATE, "Hentikan"); + huEngine->registerEntry("id_ID", TXT_KEY_ANR_OPTION_WAIT, "Tunggu"); + huEngine->registerEntry("id_ID", TXT_KEY_ANR_PROP_UNKNOWN, "(tidak diketahui)"); + + huEngine->registerEntry("id_ID", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Aplikasi {app} meminta izin yang tidak dikenali."); + huEngine->registerEntry("id_ID", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Aplikasi {app} mencoba merekam layar Anda.\n\nApakah Anda mengizinkannya?"); + huEngine->registerEntry("id_ID", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Aplikasi {app} mencoba memuat plugin: {plugin}.\n\nApakah Anda mengizinkannya?"); + huEngine->registerEntry("id_ID", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Keyboard baru terdeteksi: {keyboard}.\n\nApakah Anda mengizinkannya beroperasi?"); + huEngine->registerEntry("id_ID", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(tidak diketahui)"); + huEngine->registerEntry("id_ID", TXT_KEY_PERMISSION_TITLE, "Permintaan Izin"); + huEngine->registerEntry("id_ID", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Petunjuk: Anda dapat mengatur rule ini secara permanen di file konfigurasi Hyprland."); + huEngine->registerEntry("id_ID", TXT_KEY_PERMISSION_ALLOW, "Izinkan"); + huEngine->registerEntry("id_ID", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Izinkan dan Ingat"); + huEngine->registerEntry("id_ID", TXT_KEY_PERMISSION_ALLOW_ONCE, "Izinkan Sekali"); + huEngine->registerEntry("id_ID", TXT_KEY_PERMISSION_DENY, "Tolak"); + huEngine->registerEntry("id_ID", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Aplikasi tidak dikenal (ID klien wayland {wayland_id})"); + + huEngine->registerEntry("id_ID", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Variabel environment XDG_CURRENT_DESKTOP Anda tampaknya dikelola secara eksternal, nilainya saat ini: {value}.\nHal ini dapat menyebabkan " + "masalah, kecuali jika disengaja."); + huEngine->registerEntry( + "id_ID", TXT_KEY_NOTIF_NO_GUIUTILS, + "hyprland-guiutils belum terpasang di Sistem Anda. Paket tersebut merupakan dependensi runtime untuk beberapa dialog. Mohon untuk menginstalnya."); + huEngine->registerEntry("id_ID", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland gagal memuat {count} aset penting. Salahkan pengelola paket distro Anda karena pengemasannya buruk!"; + return "Hyprland gagal memuat {count} aset penting. Salahkan pengelola paket distro Anda karena pengemasannya buruk!"; + }); + huEngine->registerEntry("id_ID", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Susunan monitor Anda tidak benar. Monitor {name} tertumpuk dengan monitor lain.\nSilakan lihat wiki (halaman Monitors) untuk " + "detailnya. Hal ini pasti akan menimbulkan masalah."); + huEngine->registerEntry("id_ID", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Monitor {name} gagal menerapkan mode yang diminta, kembali ke mode {mode}."); + huEngine->registerEntry("id_ID", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Skala tidak valid diberikan ke monitor {name}: {scale}, skala yang disarankan: {fixed_scale}"); + huEngine->registerEntry("id_ID", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Gagal memuat plugin {name}: {error}"); + huEngine->registerEntry("id_ID", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Gagal memuat ulang shader CM, kembali ke rgba/rgbx."); + huEngine->registerEntry("id_ID", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: wide color gamut aktif tetapi layar tidak dalam mode 10-bit."); + // hr_HR (Croatian) huEngine->registerEntry("hr_HR", TXT_KEY_ANR_TITLE, "Aplikacija ne reagira"); huEngine->registerEntry("hr_HR", TXT_KEY_ANR_CONTENT, "Aplikacija {title} - {class} ne reagira.\nŠto želiš napraviti s njom?"); @@ -546,38 +788,49 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Schermo {name}: la gamma di colori ampia è abilitata ma lo schermo non è in modalità 10-bit."); // ja_JP (Japanese) - huEngine->registerEntry("ja_JP", TXT_KEY_ANR_TITLE, "アプリは応答しません"); - huEngine->registerEntry("ja_JP", TXT_KEY_ANR_CONTENT, "アプリ {title} ー {class}は応答しません。\n何をしたいですか?"); + huEngine->registerEntry("ja_JP", TXT_KEY_ANR_TITLE, "アプリが応答しません"); + huEngine->registerEntry("ja_JP", TXT_KEY_ANR_CONTENT, "アプリ {title} - {class} が応答しません。\nどうしますか?"); huEngine->registerEntry("ja_JP", TXT_KEY_ANR_OPTION_TERMINATE, "強制終了"); huEngine->registerEntry("ja_JP", TXT_KEY_ANR_OPTION_WAIT, "待機"); huEngine->registerEntry("ja_JP", TXT_KEY_ANR_PROP_UNKNOWN, "(不明)"); - huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "アプリ{app}は不明な許可を要求します。"); - huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "アプリ{app}は画面へのアクセスを要求します。\n\n許可したいですか?"); - huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "アプリ{app}は以下のプラグインをロード許可を要求します:{plugin}。\n\n許可したいですか?"); - huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "新しいキーボードを見つけた:{keyboard}。\n\n稼働を許可したいですか?"); + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "アプリ {app} が権限を求めています。"); + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "アプリ {app} が画面をキャプチャしようとしています。\n\n許可しますか?"); + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "アプリ {app} がプラグイン {plugin} をロードしようとしています。\n\n許可しますか?"); + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "新しいキーボード {keyboard} が接続されました。\n\n使用を許可しますか?"); huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(不明)"); - huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_TITLE, "許可要求"); - huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "ヒント:Hyprlandのコンフィグで通常の許可や却下を設定できます。"); + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_TITLE, "権限の要求"); + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "ヒント:永続的なルールを Hyprland の設定ファイルに記述できます。"); huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_ALLOW, "許可"); - huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "保存して許可"); - huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_ALLOW_ONCE, "一度許可"); + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "許可して保存"); + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_ALLOW_ONCE, "今回だけ許可"); huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_DENY, "却下"); - huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "不明なアプリ (waylandクライアントID {wayland_id})"); + huEngine->registerEntry("ja_JP", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "不明なアプリ(wayland クライアント ID {wayland_id})"); huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, - "エンバイアロンメント変数「XDG_CURRENT_DESKTOP」は外部から「{value}」に設定しました。\n意図的ではなければ、問題は発生可能性があります。"); - huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_NO_GUIUTILS, "システムにhyprland-guiutilsはインストールしていません。このパッケージをインストールしてください。"); + "環境変数 XDG_CURRENT_DESKTOP は外部から {value} に設定されています。\n意図的なものでなければ、何らかの問題を起こすかもしれません。"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_NO_GUIUTILS, "hyprland-guiutils がありません。このパッケージをインストールしてください。"); huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_FAILED_ASSETS, - "{count}つの根本的なアセットをロードできませんでした。これはパッケージャーのせいだから、パッケージャーに文句してください。"); - huEngine->registerEntry( - "ja_JP", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, - "画面の位置設定は誤用です。画面{name}は他の画面の区域と重ね合わせます。\nウィキのモニターページで詳細を確認してください。これは絶対に問題になります。"); - huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "画面{name}は設定したモードを正常に受け入れませんでした。{mode}を使いました。"); - huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "画面{name}のスケールは無効:{scale}、代わりにおすすめのスケール{fixed_scale}を使いました。"); - huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "プラグイン{name}のロード失敗: {error}"); - huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CMシェーダーのリロード失敗、rgba/rgbxを使いました。"); - huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "画面{name}:広い色域は設定していますけど、画面は10ビットモードに設定されていません。"); + "{count} 個の必要なアセットをロードできません。ディストリビューションのパッケージ作成者にこの問題を報告してください。"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "モニタのレイアウトが正しく設定されていません。モニタ {name} の表示領域が他のモニタと重複しています。\n詳細は Wiki の Monitor " + "の項目を参照してください。これは絶対に問題を起こします。"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "モニタ {name} のモード設定に失敗したため、モード {mode} を使用します。"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "モニタ {name} のスケール設定が正しくないため、代わりにスケール {fixed_scale} を使用します。"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "プラグイン {name} のロードで、エラー {error} が発生しました。"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM シェーダのリロードに失敗したため、rgba/rgbx を使用します。"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "広色域が有効なモニタ {name} を使用していますが、画面表示の設定は 10 ビットになっていません。"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_NO_WATCHDOG, "start-hyprland なしで Hyprland を実行しています。これは、デバッグ目的以外ではおすすめしません。"); + + huEngine->registerEntry("ja_JP", TXT_KEY_SAFE_MODE_TITLE, "セーフモード"); + huEngine->registerEntry("ja_JP", TXT_KEY_SAFE_MODE_DESCRIPTION, + "前回のセッションがクラッシュしました。Hyprland " + "は設定ファイルをロードしない、セーフモードで動作しています。\n問題を解決するか、もしくは下のボタンで設定ファイルをロードしてください。" + "\nデフォルトのキーバインドは、SUPER+Q が kitty、SUPER+R が簡素なランチャー、SUPER+M が Hyprland の終了です。" + "\nHyprland を再起動することで、ノーマルモードで動作します。"); + huEngine->registerEntry("ja_JP", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, "設定ファイルをロード"); + huEngine->registerEntry("ja_JP", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, "クラッシュレポートフォルダを開く"); + huEngine->registerEntry("ja_JP", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, "了解(このウィンドウを閉じる)"); // lv_LV (Latvian) huEngine->registerEntry("lv_LV", TXT_KEY_ANR_TITLE, "Lietotne nereaģē"); @@ -824,6 +1077,58 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("nl_NL", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Het opnieuw laden van de CM-shader is mislukt. Er wordt teruggevallen op rgba/rgbx."); huEngine->registerEntry("nl_NL", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: breed kleurbereik is ingeschakeld maar het scherm staat niet in 10-bitmodus."); + // pa_IN (Punjabi) + huEngine->registerEntry("pa_IN", TXT_KEY_ANR_TITLE, "ਐਪਲੀਕੇਸ਼ਨ ਜਵਾਬ ਨਹੀਂ ਦੇ ਰਹੀ"); + huEngine->registerEntry("pa_IN", TXT_KEY_ANR_CONTENT, "ਇੱਕ ਐਪਲੀਕੇਸ਼ਨ {title} - {class} ਜਵਾਬ ਨਹੀਂ ਦੇ ਰਹੀ ਹੈ।\nਤੁਸੀਂ ਇਸ ਨਾਲ ਕੀ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?"); + huEngine->registerEntry("pa_IN", TXT_KEY_ANR_OPTION_TERMINATE, "ਬੰਦ ਕਰੋ"); + huEngine->registerEntry("pa_IN", TXT_KEY_ANR_OPTION_WAIT, "ਉਡੀਕ ਕਰੋ"); + huEngine->registerEntry("pa_IN", TXT_KEY_ANR_PROP_UNKNOWN, "(ਅਗਿਆਤ)"); + + huEngine->registerEntry("pa_IN", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "ਇੱਕ ਐਪਲੀਕੇਸ਼ਨ {app} ਅਗਿਆਤ ਇਜਾਜ਼ਤ ਦੀ ਬੇਨਤੀ ਕਰ ਰਹੀ ਹੈ।"); + huEngine->registerEntry("pa_IN", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "ਇੱਕ ਐਪਲੀਕੇਸ਼ਨ {app} ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ ਨੂੰ ਰਿਕਾਰਡ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰ ਰਹੀ ਹੈ।\n\nਕੀ ਤੁਸੀਂ ਇਸਦੀ ਇਜਾਜ਼ਤ ਦੇਣਾ ਚਾਹੁੰਦੇ ਹੋ?"); + huEngine->registerEntry("pa_IN", TXT_KEY_PERMISSION_REQUEST_CURSOR_POS, "ਇੱਕ ਐਪਲੀਕੇਸ਼ਨ {app} ਤੁਹਾਡੇ ਕਰਸਰ ਦੀ ਸਥਿਤੀ ਪੜ੍ਹਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰ ਰਹੀ ਹੈ।\n\nਕੀ ਤੁਸੀਂ ਇਸਦੀ ਇਜਾਜ਼ਤ ਦੇਣਾ ਚਾਹੁੰਦੇ ਹੋ?"); + huEngine->registerEntry("pa_IN", TXT_KEY_PERMISSION_REQUEST_PLUGIN, + "ਇੱਕ ਐਪਲੀਕੇਸ਼ਨ {app} ਇੱਕ ਪਲੱਗਇਨ ਲੋਡ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰ ਰਹੀ ਹੈ: {plugin}।\n\nਕੀ ਤੁਸੀਂ ਇਸਦੀ ਇਜਾਜ਼ਤ ਦੇਣਾ ਚਾਹੁੰਦੇ ਹੋ?"); + huEngine->registerEntry("pa_IN", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "ਇੱਕ ਨਵਾਂ ਕੀਬੋਰਡ ਲੱਭਿਆ ਗਿਆ ਹੈ: {keyboard}।\n\nਕੀ ਤੁਸੀਂ ਇਸਨੂੰ ਕੰਮ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਦੇਣਾ ਚਾਹੁੰਦੇ ਹੋ?"); + huEngine->registerEntry("pa_IN", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(ਅਗਿਆਤ)"); + huEngine->registerEntry("pa_IN", TXT_KEY_PERMISSION_TITLE, "ਇਜਾਜ਼ਤ ਦੀ ਬੇਨਤੀ"); + huEngine->registerEntry("pa_IN", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "ਸੁਝਾਅ: ਤੁਸੀਂ ਆਪਣੀ Hyprland ਕੌਂਫਿਗਰੇਸ਼ਨ ਫਾਈਲ ਵਿੱਚ ਇਹਨਾਂ ਲਈ ਪੱਕੇ ਨਿਯਮ ਸੈੱਟ ਕਰ ਸਕਦੇ ਹੋ।"); + huEngine->registerEntry("pa_IN", TXT_KEY_PERMISSION_ALLOW, "ਇਜਾਜ਼ਤ ਦਿਓ"); + huEngine->registerEntry("pa_IN", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "ਇਜਾਜ਼ਤ ਦਿਓ ਅਤੇ ਯਾਦ ਰੱਖੋ"); + huEngine->registerEntry("pa_IN", TXT_KEY_PERMISSION_ALLOW_ONCE, "ਇੱਕ ਵਾਰ ਇਜਾਜ਼ਤ ਦਿਓ"); + huEngine->registerEntry("pa_IN", TXT_KEY_PERMISSION_DENY, "ਇਨਕਾਰ ਕਰੋ"); + huEngine->registerEntry("pa_IN", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "ਅਗਿਆਤ ਐਪਲੀਕੇਸ਼ਨ (wayland ਕਲਾਇੰਟ ID {wayland_id})"); + + huEngine->registerEntry("pa_IN", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "ਤੁਹਾਡਾ XDG_CURRENT_DESKTOP ਵਾਤਾਵਰਣ ਵੇਰੀਏਬਲ ਬਾਹਰੀ ਤੌਰ 'ਤੇ ਪ੍ਰਬੰਧਿਤ ਜਾਪਦਾ ਹੈ, ਮੌਜੂਦਾ ਮੁੱਲ: {value}।\nਜਦੋਂ ਤੱਕ ਇਹ ਜਾਣਬੁੱਝ ਕੇ ਨਾ " + "ਕੀਤਾ ਗਿਆ ਹੋਵੇ, ਇਹ ਸਮੱਸਿਆਵਾਂ ਪੈਦਾ ਕਰ ਸਕਦਾ ਹੈ।"); + huEngine->registerEntry("pa_IN", TXT_KEY_NOTIF_NO_GUIUTILS, "ਤੁਹਾਡੇ ਸਿਸਟਮ ਵਿੱਚ hyprland-guiutils ਇੰਸਟਾਲ ਨਹੀਂ ਹੈ। ਇਹ ਕੁਝ ਡਾਇਲਾਗਸ ਲਈ ਜ਼ਰੂਰੀ ਹੈ। ਕਿਰਪਾ ਕਰਕੇ ਇਸਨੂੰ ਇੰਸਟਾਲ ਕਰਨ ਬਾਰੇ ਵਿਚਾਰ ਕਰੋ।"); + huEngine->registerEntry("pa_IN", 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("pa_IN", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "ਤੁਹਾਡਾ ਮਾਨੀਟਰ ਲੇਆਉਟ ਗਲਤ ਤਰੀਕੇ ਨਾਲ ਕੌਂਫਿਗਰ ਕੀਤਾ ਗਿਆ ਹੈ। ਮਾਨੀਟਰ {name} ਲੇਆਉਟ ਵਿੱਚ ਦੂਜੇ ਮਾਨੀਟਰਾਂ ਨਾਲ ਓਵਰਲੈਪ ਕਰ ਰਿਹਾ ਹੈ।\nਹੋਰ " + "ਜਾਣਕਾਰੀ ਲਈ ਵਿਕੀ (Monitors page) ਦੇਖੋ। ਇਸ ਨਾਲ ਸਮੱਸਿਆਵਾਂ ਪੈਦਾ ਹੋਣਗੀਆਂ।"); + huEngine->registerEntry("pa_IN", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "ਮਾਨੀਟਰ {name} ਕਿਸੇ ਵੀ ਬੇਨਤੀ ਕੀਤੇ ਮੋਡ ਨੂੰ ਸੈੱਟ ਕਰਨ ਵਿੱਚ ਅਸਫਲ ਰਿਹਾ, ਵਾਪਸ ਮੋਡ {mode} 'ਤੇ ਜਾ ਰਿਹਾ ਹੈ।"); + huEngine->registerEntry("pa_IN", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "ਮਾਨੀਟਰ {name} ਨੂੰ ਅਵੈਧ ਸਕੇਲ ਭੇਜਿਆ ਗਿਆ: {scale}, ਪ੍ਰਸਤਾਵਿਤ ਸਕੇਲ ਦੀ ਵਰਤੋਂ ਕਰ ਰਿਹਾ ਹੈ: {fixed_scale}"); + huEngine->registerEntry("pa_IN", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "ਪਲੱਗਇਨ {name} ਲੋਡ ਕਰਨ ਵਿੱਚ ਅਸਫਲ: {error}"); + huEngine->registerEntry("pa_IN", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM ਸ਼ੇਡਰ ਮੁੜ-ਲੋਡ ਕਰਨ ਵਿੱਚ ਅਸਫਲ, rgba/rgbx 'ਤੇ ਵਾਪਸ ਜਾ ਰਿਹਾ ਹੈ।"); + huEngine->registerEntry("pa_IN", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "ਮਾਨੀਟਰ {name}: ਵਾਈਡ ਕਲਰ ਗੇਮਟ ਚਾਲੂ ਹੈ ਪਰ ਡਿਸਪਲੇ 10-ਬਿੱਟ ਮੋਡ ਵਿੱਚ ਨਹੀਂ ਹੈ।"); + huEngine->registerEntry("pa_IN", TXT_KEY_NOTIF_NO_WATCHDOG, "Hyprland ਨੂੰ start-hyprland ਤੋਂ ਬਿਨਾਂ ਸ਼ੁਰੂ ਕੀਤਾ ਗਿਆ ਸੀ। ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਡੀਬੱਗਿੰਗ ਵਾਤਾਵਰਣ ਵਿੱਚ ਨਹੀਂ ਹੋ, ਇਸਦੀ ਸਿਫਾਰਸ਼ ਨਹੀਂ ਕੀਤੀ ਜਾਂਦੀ।"); + + huEngine->registerEntry("pa_IN", TXT_KEY_SAFE_MODE_TITLE, "ਸੁਰੱਖਿਅਤ ਮੋਡ (Safe Mode)"); + huEngine->registerEntry("pa_IN", TXT_KEY_SAFE_MODE_DESCRIPTION, + "Hyprland ਸੁਰੱਖਿਅਤ ਮੋਡ ਵਿੱਚ ਸ਼ੁਰੂ ਕੀਤਾ ਗਿਆ ਹੈ, ਜਿਸਦਾ ਮਤਲਬ ਹੈ ਕਿ ਤੁਹਾਡਾ ਪਿਛਲਾ ਸੈਸ਼ਨ ਕ੍ਰੈਸ਼ ਹੋ ਗਿਆ ਸੀ।\nਸੁਰੱਖਿਅਤ ਮੋਡ ਤੁਹਾਡੀ " + "ਕੌਂਫਿਗਰੇਸ਼ਨ ਨੂੰ ਲੋਡ ਹੋਣ ਤੋਂ ਰੋਕਦਾ ਹੈ। ਤੁਸੀਂ ਇਸ ਵਾਤਾਵਰਣ ਵਿੱਚ ਸਮੱਸਿਆ ਦਾ ਨਿਪਟਾਰਾ ਕਰ ਸਕਦੇ ਹੋ, ਜਾਂ ਹੇਠਾਂ ਦਿੱਤੇ ਬਟਨ ਨਾਲ ਆਪਣੀ " + "ਕੌਂਫਿਗਰੇਸ਼ਨ ਲੋਡ ਕਰ ਸਕਦੇ ਹੋ।\nਡਿਫੌਲਟ ਕੀਬਾਈਂਡ ਲਾਗੂ ਹੁੰਦੇ ਹਨ: kitty ਲਈ SUPER+Q, ਬੇਸਿਕ ਰਨਰ ਲਈ SUPER+R, ਬਾਹਰ ਨਿਕਲਣ ਲਈ SUPER+M।\n" + "Hyprland ਨੂੰ ਦੁਬਾਰਾ ਸ਼ੁਰੂ ਕਰਨ ਨਾਲ ਇਹ ਮੁੜ ਆਮ ਮੋਡ ਵਿੱਚ ਚੱਲੇਗਾ।"); + huEngine->registerEntry("pa_IN", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, "ਕੌਂਫਿਗ ਲੋਡ ਕਰੋ"); + huEngine->registerEntry("pa_IN", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, "ਕ੍ਰੈਸ਼ ਰਿਪੋਰਟ ਡਾਇਰੈਕਟਰੀ ਖੋਲ੍ਹੋ"); + huEngine->registerEntry("pa_IN", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, "ਸਮਝ ਆ ਗਿਆ, ਇਸਨੂੰ ਬੰਦ ਕਰੋ"); + // pl_PL (Polish) huEngine->registerEntry("pl_PL", TXT_KEY_ANR_TITLE, "Aplikacja Nie Odpowiada"); huEngine->registerEntry("pl_PL", TXT_KEY_ANR_CONTENT, "Aplikacja {title} - {class} nie odpowiada.\nCo chcesz z nią zrobić?"); @@ -862,6 +1167,18 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Nie udało się załadować plugin'a {name}: {error}"); huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Nie udało się przeładować shader'a CM, użyto rgba/rgbx."); huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: skonfigurowano szeroką głębię barw, ale monitor nie jest w trybie 10-bit."); + huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_NO_WATCHDOG, + "Hyprland został uruchomiony bez start-hyprland. Nie jest to zalecane, chyba, że jest to środowisko do debugowania."); + + huEngine->registerEntry("pl_PL", TXT_KEY_SAFE_MODE_TITLE, "Tryb Bezpieczny"); + huEngine->registerEntry("pl_PL", TXT_KEY_SAFE_MODE_DESCRIPTION, + "Hyprland został uruchomiony w trybie bezpiecznym, co oznacza, że twoja ostatnia sesja uległa awarii.\nTryb bezpieczny zapobiega ładowaniu twojej " + "konfiguracji. Możesz próbować rozwiązać" + "problem w tym środowisku, lub załadować swoją konfigurację przyciskiem poniżej.\nDomyślne skróty klawiszowe są dostępne: SUPER+Q uruchamia kitty, " + "SUPER+R otwiera podstawowy launcher, SUPER+M zamyka Hyprland.\nUruchomienie ponowne Hyprland'a uruchomi go w trybie normalnym."); + huEngine->registerEntry("pl_PL", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, "Załaduj konfigurację"); + huEngine->registerEntry("pl_PL", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, "Otwórz folder z raportami awarii"); + huEngine->registerEntry("pl_PL", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, "Ok, zamknij to okno"); // pt_PT (Portuguese Portugal) huEngine->registerEntry("pt_PT", TXT_KEY_ANR_TITLE, "A aplicação não está a responder"); @@ -935,6 +1252,65 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "无法加载插件 {name}:{error}"); huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "无法重新加载CM着色器,将使用rgba/rgbx兜底。"); huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "显示器 {name}:宽色域被启用了,但是显示器并不在10-bit模式。"); + huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_NO_WATCHDOG, "Hyprland 启动时未使用 start-hyprland。除非你处于调试环境,否则极度不推荐这样做。"); + + huEngine->registerEntry("zh_CN", TXT_KEY_SAFE_MODE_TITLE, "安全模式"); + huEngine->registerEntry("zh_CN", TXT_KEY_SAFE_MODE_DESCRIPTION, + "Hyprland " + "已在安全模式下启动,这意味着你上次会话崩溃了。\n安全模式会阻止加载你的配置。你可以在此环境中进行故障排除,或者使用下方按钮加载你的配置。\n默认快" + "捷键适用:SUPER+Q 打开 Kitty,SUPER+R 打开简易启动器,SUPER+M 退出。\n重新启动 " + "Hyprland 将再次进入正常模式。"); + huEngine->registerEntry("zh_CN", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, "加载配置"); + huEngine->registerEntry("zh_CN", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, "打开崩溃报告目录"); + huEngine->registerEntry("zh_CN", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, "好的,关闭窗口"); + + // zh_TW (Traditional Chinese) + huEngine->registerEntry("zh_TW", TXT_KEY_ANR_TITLE, "應用程式沒有回應"); + huEngine->registerEntry("zh_TW", TXT_KEY_ANR_CONTENT, "應用程式 {title} - {class} 沒有回應。\n您想要怎麼做?"); + huEngine->registerEntry("zh_TW", TXT_KEY_ANR_OPTION_TERMINATE, "強制結束"); + huEngine->registerEntry("zh_TW", TXT_KEY_ANR_OPTION_WAIT, "等待"); + huEngine->registerEntry("zh_TW", TXT_KEY_ANR_PROP_UNKNOWN, "(未知)"); + + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "應用程式 {app} 正在請求未知的權限。"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "應用程式 {app} 試圖擷取您的螢幕畫面。\n\n您是否允許?"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "應用程式 {app} 試圖載入外掛:{plugin}。\n\n您是否允許?"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "偵測到新鍵盤:{keyboard}。\n\n您是否允許它進行操作?"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(未知)"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_TITLE, "權限請求"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "提示:您可以在 Hyprland 設定檔中為此建立永久規則。"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_ALLOW, "允許"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "總是允許"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_ALLOW_ONCE, "僅允許一次"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_DENY, "拒絕"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "未知的應用程式 (Wayland 用戶端 ID {wayland_id})"); + + huEngine->registerEntry("zh_TW", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "您的 XDG_CURRENT_DESKTOP 環境變數似乎由外部管理,目前的值為 {value}。\n除非您有意為之,否則這可能會導致問題。"); + huEngine->registerEntry("zh_TW", TXT_KEY_NOTIF_NO_GUIUTILS, "您的系統未安裝 hyprland-guiutils。這是部分對話視窗的執行期依賴元件。建議您安裝它。"); + huEngine->registerEntry("zh_TW", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland 無法載入 {count} 個必要資源,去怪那個把發行版打包成這副德性的維護者!"; + return "Hyprland 無法載入 {count} 個必要資源,去怪那個把發行版打包成這副德性的維護者!"; + }); + huEngine->registerEntry("zh_TW", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "您的螢幕配置設定不正確。螢幕 {name} 與配置中的其他螢幕重疊了。\n請參閱 Wiki(螢幕頁面)以了解詳情。這絕對會導致問題。"); + huEngine->registerEntry("zh_TW", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "螢幕 {name} 無法設定為任何請求的模式,將改用模式 {mode}。"); + huEngine->registerEntry("zh_TW", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "傳遞給螢幕 {name} 的縮放比例無效:{scale},將使用建議的比例:{fixed_scale}"); + huEngine->registerEntry("zh_TW", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "無法載入外掛 {name}:{error}"); + huEngine->registerEntry("zh_TW", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM 著色器重新載入失敗,將退回使用 rgba/rgbx。"); + huEngine->registerEntry("zh_TW", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "螢幕 {name}:已啟用廣色域,但顯示器並非處於 10-bit 模式。"); + huEngine->registerEntry("zh_TW", TXT_KEY_NOTIF_NO_WATCHDOG, "Hyprland 啟動時未使用 start-hyprland wrapper。除非您處於除錯環境,否則極度不建議這麼做。"); + + huEngine->registerEntry("zh_TW", TXT_KEY_SAFE_MODE_TITLE, "安全模式"); + huEngine->registerEntry("zh_TW", TXT_KEY_SAFE_MODE_DESCRIPTION, + "Hyprland " + "已在安全模式下啟動,這代表您的上個工作階段當機。\n安全模式會阻止載入您的設定檔。您可以在此環境中進行故障排除,或使用下方按鈕載入您的設定。\n預設快" + "捷鍵適用:SUPER+Q 開啟 Kitty,SUPER+R 開啟簡易啟動器,SUPER+M 退出。\n重新啟動 " + "Hyprland 將再次進入正常模式。"); + huEngine->registerEntry("zh_TW", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, "載入設定檔"); + huEngine->registerEntry("zh_TW", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, "開啟當機報告目錄"); + huEngine->registerEntry("zh_TW", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, "好,關閉視窗"); // ar (Arabic - Modern Standard) huEngine->registerEntry("ar", TXT_KEY_ANR_TITLE, "التطبيق لا يستجيب"); @@ -959,30 +1335,88 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("ar", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, "يبدو أنّ متغيّر البيئة XDG_CURRENT_DESKTOP يُدار من خارج النظام، والقيمة الحالية هي {value}.\n" "قد يؤدي ذلك إلى مشكلات ما لم يكن مقصودًا."); - huEngine->registerEntry("ar", TXT_KEY_NOTIF_NO_GUIUTILS, "لا يحتوي نظامك على الحزمة hyprland-guiutils مثبتة. هذه حزمة مطلوبة أثناء التشغيل لبعض مربعات الحوار. يُنصَح بتثبيتها."); - huEngine->registerEntry("ar", 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("ar", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, "تم إعداد مخطط الشاشات لديك بشكل غير صحيح. الشاشة {name} تتداخل مع شاشة أو أكثر في المخطط.\n" "يرجى مراجعة صفحة الشاشات في الويكي لمزيد من التفاصيل. هذا سيسبب مشكلات."); - huEngine->registerEntry("ar", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "فشلت الشاشة {name} في ضبط أي من الأوضاع المطلوبة، وسيتم الرجوع إلى الوضع {mode}."); - huEngine->registerEntry("ar", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "تم تمرير قيمة تحجيم غير صالحة إلى الشاشة {name}: {scale}. سيتم استخدام قيمة التحجيم المقترحة: {fixed_scale}."); - huEngine->registerEntry("ar", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "فشل تحميل الإضافة {name}: {error}"); - huEngine->registerEntry("ar", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "فشلت إعادة تحميل نظام إدارة الألوان (CM). سيتم الرجوع إلى صيغة الألوان rgba/rgbx."); - huEngine->registerEntry("ar", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "الشاشة {name}: تم تفعيل نطاق الألوان الواسع، لكن العرض ليس في وضع 10 بت."); + huEngine->registerEntry("ar", TXT_KEY_SAFE_MODE_TITLE, "الوضع الآمن"); + huEngine->registerEntry("ar", TXT_KEY_SAFE_MODE_DESCRIPTION, + "شُغل Hyprland في الوضع الآمن، هذا يعني أن جلستك الأخيرة قد انهارت.\nالوضع الآمن يمنع تحميل إعداداتك، " + "يمكنك البحث عن وحل المشاكل في هذه البيئة، أو تحميل إعداداتك باستخدام الزر أدناه.\n اختصارات المفاتيح الافتراضية: الطرفية (Kitty) — SUPER+Q، مشغّل " + "الأوامر البسيط — SUPER+R، الخروج — SUPER+M.\n" + "إعادة تشغيل Hyprland سيشغله في الوضع العادي"); + huEngine->registerEntry("ar", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, "حمل ملف الإعدادات"); + 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Что вы хотите сделать?"); @@ -1021,6 +1455,17 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Не удалось загрузить плагин {name}: {error}"); huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Не удалось перезагрузить CM shader, используется rgba/rgbx."); huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Монитор {name}: расширенный цветовой охват включён, но дисплей не в 10-bit режиме."); + huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_NO_WATCHDOG, "Hyprland был запущен без start-hyprland. Это крайне не рекомендуется, если только вы не в отладочной среде."); + + huEngine->registerEntry("ru_RU", TXT_KEY_SAFE_MODE_TITLE, "Безопасный режим"); + huEngine->registerEntry( + "ru_RU", TXT_KEY_SAFE_MODE_DESCRIPTION, + "Hyprland запущен в безопасном режиме, это значит, что ваш прошлый сеанс завершился сбоем.\nБезопасный режим не загружает ваш конфиг. Вы можете " + "исправить проблему в этом окружении или загрузить конфиг кнопкой ниже.\nДействуют стандартные бинды: SUPER+Q запускает kitty, SUPER+R открывает лаунчер, " + "SUPER+M для выхода.\nПосле перезапуска Hyprland снова запустится в обычном режиме."); + huEngine->registerEntry("ru_RU", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, "Загрузить конфиг"); + huEngine->registerEntry("ru_RU", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, "Открыть каталог отчётов о сбоях"); + huEngine->registerEntry("ru_RU", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, "Ок, закрыть"); // sl_SI (Slovenian) huEngine->registerEntry("sl_SI", TXT_KEY_ANR_TITLE, "Program se ne odziva"); @@ -1181,6 +1626,55 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("tr_TR", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM shader yeniden yüklemesi başarısız, rgba/rgbx'e geri dönülüyor."); huEngine->registerEntry("tr_TR", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitör {name}: wide color gamut etkinleştirildi ama ekran 10-bit modunda değil."); + // tt_RU (Tatar) + huEngine->registerEntry("tt_RU", TXT_KEY_ANR_TITLE, "Программа җавап бирми"); + huEngine->registerEntry("tt_RU", TXT_KEY_ANR_CONTENT, "Программасы {title} - {class} җавап бирми.\nСез аның белән нәрсә эшләргә телисез?"); + huEngine->registerEntry("tt_RU", TXT_KEY_ANR_OPTION_TERMINATE, "Тәмам итү"); + huEngine->registerEntry("tt_RU", TXT_KEY_ANR_OPTION_WAIT, "Көтү"); + huEngine->registerEntry("tt_RU", TXT_KEY_ANR_PROP_UNKNOWN, "(билгесез)"); + + huEngine->registerEntry("tt_RU", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "{app} программасы билгесез рөхсәт сорый."); + huEngine->registerEntry("tt_RU", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "{app} программасы сезнең экранны яздырырга тели.\n\nРөхсәт бирәсезме?"); + huEngine->registerEntry("tt_RU", TXT_KEY_PERMISSION_REQUEST_CURSOR_POS, "{app} программасы курсор позициясен күзәтергә тели.\n\nРөхсәт бирәсезме?"); + huEngine->registerEntry("tt_RU", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "{app} программасы плагин йөкләргә тели: {plugin}.\n\nРөхсәт бирәсезме?"); + huEngine->registerEntry("tt_RU", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Яңа клавиатура табылды: {keyboard}.\n\nАның эшләргә рөхсәт бирәсезме?"); + huEngine->registerEntry("tt_RU", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(билгесез)"); + huEngine->registerEntry("tt_RU", TXT_KEY_PERMISSION_TITLE, "Рөхсәт сорау"); + huEngine->registerEntry("tt_RU", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Киңәш: сез Hyprland көйләү файлында даими кагыйдәләр куя аласыз."); + huEngine->registerEntry("tt_RU", TXT_KEY_PERMISSION_ALLOW, "Рөхсәт бирү"); + huEngine->registerEntry("tt_RU", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Рөхсәт бирү һәм истә калдыру"); + huEngine->registerEntry("tt_RU", TXT_KEY_PERMISSION_ALLOW_ONCE, "Бер тапкыр рөхсәт бирү"); + huEngine->registerEntry("tt_RU", TXT_KEY_PERMISSION_DENY, "Кире кагу"); + huEngine->registerEntry("tt_RU", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Билгесез программа (wayland client ID {wayland_id})"); + + huEngine->registerEntry("tt_RU", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Сезнең XDG_CURRENT_DESKTOP мохите тыштан идарә ителә, хәзерге кыйммәте: {value}.\n" + "Бу теләгән булмаса, проблемалар китереп чыгарырга мөмкин."); + huEngine->registerEntry("tt_RU", TXT_KEY_NOTIF_NO_GUIUTILS, + "Сезнең системада hyprland-guiutils урнаштырылмаган. Бу кайбер диалоглар өчен кирәкле вакыт бәйлелеге. Урнаштыруны карап чыгыгыз."); + huEngine->registerEntry("tt_RU", TXT_KEY_NOTIF_FAILED_ASSETS, "Hyprland {count} мөһим ресурсны йөкли алмады. Ул дистрибутивыгыз пакетлаучысының хатасы!"); + huEngine->registerEntry("tt_RU", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Сезнең мониторлар урнашуы дөрес түгел. {name} мониторы башка монитор белән өстәлә.\n" + "Зинһар, өстәмә мәгълүмат өчен викидагы (Monitors бит) мөрәҗәгать итегез. Бу һичшиксез проблемалар тудырачак."); + huEngine->registerEntry("tt_RU", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "{name} мониторы соралган режимнарны куя алмады, {mode} режимына кайта."); + huEngine->registerEntry("tt_RU", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, + "{name} мониторы өчен яраксыз масштаб билгеләнгән: {scale}. Тәкъдим ителгән масштаб кулланыла: {fixed_scale}"); + huEngine->registerEntry("tt_RU", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "{name} плагинны йөкләүдә хата: {error}"); + huEngine->registerEntry("tt_RU", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM шейдерын яңадан йөкләү уңышсыз булды, rgba/rgbx режимына кайтыла."); + huEngine->registerEntry("tt_RU", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Монитор {name}: киң төсләр диапазоны кушылган, ләкин дисплей 10-бит режимында түгел."); + huEngine->registerEntry("tt_RU", TXT_KEY_NOTIF_NO_WATCHDOG, "Hyprland start-hyprland ярдәмендә эшләтелмәгән. Бу, төзәтү мохитеннән тыш, бик тәкъдим ителми."); + + huEngine->registerEntry("tt_RU", TXT_KEY_SAFE_MODE_TITLE, "Куркынычсыз режим"); + huEngine->registerEntry( + "tt_RU", TXT_KEY_SAFE_MODE_DESCRIPTION, + "Hyprland куркынычсыз режимда эшләтелде, димәк сезнең соңгы сессия авария белән тәмамланган.\n" + "Куркынычсыз режим сезнең конфигурацияне йөкләүне тыя. Сез бу мохиттә проблемаларны тикшерә аласыз, яки түбәндәге төймә аша конфигурацияне йөкли аласыз.\n" + "Килешенгән төймә бәйләнешләре кулланыла: SUPER+Q kitty ачу, SUPER+R лаунчер ачу, SUPER+M чыгу.\n" + "Hyprland-ны яңадан эшләтү аны гадәти режимда ачачак."); + huEngine->registerEntry("tt_RU", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, "Конфигурацияне йөкләү"); + huEngine->registerEntry("tt_RU", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, "Авария хисаплары папкасын ачу"); + huEngine->registerEntry("tt_RU", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, "Аңлашылды, ябу"); + // uk_UA (Ukrainian) huEngine->registerEntry("uk_UA", TXT_KEY_ANR_TITLE, "Програма не відповідає"); huEngine->registerEntry("uk_UA", TXT_KEY_ANR_CONTENT, "Програма {title} - {class} не відповідає.\nЩо ви хочете з нею зробити?"); @@ -1223,6 +1717,53 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("uk_UA", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Не вдалося перезавантажити шейдер CM, повернення до rgba/rgbx."); huEngine->registerEntry("uk_UA", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Монітор {name}: широка кольорова гама увімкнена, але дисплей не працює в 10-бітному режимі."); + // vi_VN (Vietnamese) + huEngine->registerEntry("vi_VN", TXT_KEY_ANR_TITLE, "Ứng dụng không phản hồi"); + huEngine->registerEntry("vi_VN", TXT_KEY_ANR_CONTENT, "Ứng dụng {title} - {class} đang bị treo.\nBạn muốn xử lý thế nào?"); + huEngine->registerEntry("vi_VN", TXT_KEY_ANR_OPTION_TERMINATE, "Buộc dừng"); + huEngine->registerEntry("vi_VN", TXT_KEY_ANR_OPTION_WAIT, "Chờ"); + huEngine->registerEntry("vi_VN", TXT_KEY_ANR_PROP_UNKNOWN, "(không xác định)"); + + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Ứng dụng {app} đang yêu cầu một quyền không xác định."); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Ứng dụng {app} đang cố gắng ghi hình màn hình của bạn.\n\nBạn muốn cho phép không?"); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_REQUEST_CURSOR_POS, "Ứng dụng {app} đang cố gắng đọc vị trí chuột của bạn.\n\nBạn muốn cho phép không?"); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Ứng dụng {app} đang cố gắng tải plugin: {plugin}.\n\nBạn muốn cho phép không?"); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Phát hiện bàn phím mới: {keyboard}.\n\nBạn muốn cho phép bàn phím này hoạt động không?"); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(không xác định)"); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_TITLE, "Yêu cầu cấp quyền"); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Gợi ý: bạn có thể thiết lập các quyền này trong tệp cấu hình Hyprland."); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_ALLOW, "Cho phép"); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Cho phép và ghi nhớ"); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_ALLOW_ONCE, "Chỉ một lần"); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_DENY, "Từ chối"); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Ứng dụng không xác định (wayland client ID {wayland_id})"); + + huEngine->registerEntry( + "vi_VN", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Biến môi trường XDG_CURRENT_DESKTOP dường như đang được thiết lập từ bên ngoài với giá trị là {value}.\nViệc này có thể gây ra lỗi trừ khi đó là chủ ý của bạn."); + huEngine->registerEntry("vi_VN", TXT_KEY_NOTIF_NO_GUIUTILS, "Hệ thống chưa cài hyprland-guiutils. Một số hộp thoại sẽ không hiển thị nếu thiếu nó."); + huEngine->registerEntry("vi_VN", TXT_KEY_NOTIF_FAILED_ASSETS, + "Hyprland không thể tải {count} tài nguyên quan trọng. Vui lòng báo lỗi cho người đóng gói (packager) của bản phân phối (distro) mà bạn dùng!"); + huEngine->registerEntry("vi_VN", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Bố cục màn hình không hợp lệ. Màn hình {name} đang bị đè lên các màn hình khác.\nVui lòng xem trang Monitors trên wiki để " + "khắc phục, nếu không chắc chắn sẽ có lỗi xảy ra."); + huEngine->registerEntry("vi_VN", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Màn hình {name} không thể áp dụng chế độ nào được yêu cầu, đang dùng tạm chế độ {mode}."); + huEngine->registerEntry("vi_VN", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Tỉ lệ {scale} cho màn hình {name} không hợp lệ, chuyển sang tỷ lệ gợi ý: {fixed_scale}"); + huEngine->registerEntry("vi_VN", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Lỗi tải plugin {name}: {error}"); + huEngine->registerEntry("vi_VN", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Tải lại CM shader thất bại, đang dùng tạm rgba/rgbx."); + huEngine->registerEntry("vi_VN", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Màn hình {name}: dải màu rộng (wide color gamut) khả dụng nhưng màn hình không ở chế độ 10-bit."); + huEngine->registerEntry("vi_VN", TXT_KEY_NOTIF_NO_WATCHDOG, + "Hyprland đã được khởi động mà không thông qua start-hyprland. Việc này không được khuyến khích trừ khi dùng cho mục đích gỡ lỗi."); + + huEngine->registerEntry("vi_VN", TXT_KEY_SAFE_MODE_TITLE, "Chế độ An toàn (Safe Mode)"); + huEngine->registerEntry("vi_VN", TXT_KEY_SAFE_MODE_DESCRIPTION, + "Phiên hoạt động trước đó đã bị sập (crash).\nHyprland hiện đang chạy ở chế độ an toàn và không tải tệp cấu hình của bạn. Bạn có thể " + "khắc phục sự cố trong môi trường này, hoặc bấm nút bên dưới để thử tải lại cấu hình.\nCác phím tắt mặc định: SUPER+Q (kitty), SUPER+R (runner), " + "SUPER+M (exit).\nHyprland sẽ về chế độ bình thường sau khi khởi động lại."); + huEngine->registerEntry("vi_VN", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, "Tải cấu hình"); + huEngine->registerEntry("vi_VN", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, "Mở thư mục báo cáo lỗi (crash report)"); + huEngine->registerEntry("vi_VN", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, "OK, đã hiểu"); + // cs_CZ (Czech) huEngine->registerEntry("cs_CZ", TXT_KEY_ANR_TITLE, "Aplikace Neodpovídá"); huEngine->registerEntry("cs_CZ", TXT_KEY_ANR_CONTENT, "Aplikace {title} - {class} neodpovídá.\nCo s ní chcete udělat?"); diff --git a/src/i18n/Engine.hpp b/src/i18n/Engine.hpp index d1182632a..79ec86f89 100644 --- a/src/i18n/Engine.hpp +++ b/src/i18n/Engine.hpp @@ -16,6 +16,7 @@ namespace I18n { TXT_KEY_PERMISSION_REQUEST_UNKNOWN, TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, + TXT_KEY_PERMISSION_REQUEST_CURSOR_POS, TXT_KEY_PERMISSION_REQUEST_PLUGIN, TXT_KEY_PERMISSION_REQUEST_KEYBOARD, TXT_KEY_PERMISSION_UNKNOWN_NAME, @@ -36,6 +37,13 @@ namespace I18n { TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, TXT_KEY_NOTIF_CM_RELOAD_FAILED, TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, + TXT_KEY_NOTIF_NO_WATCHDOG, + + TXT_KEY_SAFE_MODE_TITLE, + TXT_KEY_SAFE_MODE_DESCRIPTION, + TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, + TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, + TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, }; class CI18nEngine { @@ -47,4 +55,4 @@ namespace I18n { }; SP i18nEngine(); -}; \ No newline at end of file +}; diff --git a/src/init/initHelpers.cpp b/src/init/initHelpers.cpp index c27f625c5..5d4d0b259 100644 --- a/src/init/initHelpers.cpp +++ b/src/init/initHelpers.cpp @@ -1,3 +1,6 @@ +#include +#include + #include "initHelpers.hpp" bool NInit::isSudo() { @@ -10,20 +13,24 @@ void NInit::gainRealTime() { struct sched_param param; if (pthread_getschedparam(pthread_self(), &old_policy, ¶m)) { - Debug::log(WARN, "Failed to get old pthread scheduling priority"); + Log::logger->log(Log::WARN, "Failed to get old pthread scheduling priority"); return; } param.sched_priority = minPrio; if (pthread_setschedparam(pthread_self(), SCHED_RR, ¶m)) { - Debug::log(WARN, "Failed to change process scheduling strategy"); + Log::logger->log(Log::WARN, "Failed to change process scheduling strategy"); return; } + // NixOS-specific fix to prevent all children from inheriting + // CAP_SYS_NICE due to how the security wrapper works. + prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_LOWER, CAP_SYS_NICE, 0, 0); + pthread_atfork(nullptr, nullptr, []() { const struct sched_param param = {.sched_priority = 0}; if (pthread_setschedparam(pthread_self(), SCHED_OTHER, ¶m)) - Debug::log(WARN, "Failed to reset process scheduling strategy"); + Log::logger->log(Log::WARN, "Failed to reset process scheduling strategy"); }); } \ No newline at end of file diff --git a/src/layout/DwindleLayout.cpp b/src/layout/DwindleLayout.cpp deleted file mode 100644 index cf833fb53..000000000 --- a/src/layout/DwindleLayout.cpp +++ /dev/null @@ -1,1185 +0,0 @@ -#include "DwindleLayout.hpp" -#include "../Compositor.hpp" -#include "../config/ConfigValue.hpp" -#include "../config/ConfigManager.hpp" -#include "../render/decorations/CHyprGroupBarDecoration.hpp" -#include "../render/Renderer.hpp" -#include "../managers/input/InputManager.hpp" -#include "../managers/LayoutManager.hpp" -#include "../managers/EventManager.hpp" -#include "../desktop/state/FocusState.hpp" -#include "xwayland/XWayland.hpp" - -void SDwindleNodeData::recalcSizePosRecursive(bool force, bool horizontalOverride, bool verticalOverride) { - if (children[0]) { - static auto PSMARTSPLIT = CConfigValue("dwindle:smart_split"); - static auto PPRESERVESPLIT = CConfigValue("dwindle:preserve_split"); - static auto PFLMULT = CConfigValue("dwindle:split_width_multiplier"); - - if (*PPRESERVESPLIT == 0 && *PSMARTSPLIT == 0) - splitTop = box.h * *PFLMULT > box.w; - - if (verticalOverride) - splitTop = true; - else if (horizontalOverride) - splitTop = false; - - const auto SPLITSIDE = !splitTop; - - if (SPLITSIDE) { - // split left/right - const float FIRSTSIZE = box.w / 2.0 * splitRatio; - children[0]->box = CBox{box.x, box.y, FIRSTSIZE, box.h}.noNegativeSize(); - children[1]->box = CBox{box.x + FIRSTSIZE, box.y, box.w - FIRSTSIZE, box.h}.noNegativeSize(); - } else { - // split top/bottom - const float FIRSTSIZE = box.h / 2.0 * splitRatio; - children[0]->box = CBox{box.x, box.y, box.w, FIRSTSIZE}.noNegativeSize(); - children[1]->box = CBox{box.x, box.y + FIRSTSIZE, box.w, box.h - FIRSTSIZE}.noNegativeSize(); - } - - children[0]->recalcSizePosRecursive(force); - children[1]->recalcSizePosRecursive(force); - } else { - layout->applyNodeDataToWindow(this, force); - } -} - -int CHyprDwindleLayout::getNodesOnWorkspace(const WORKSPACEID& id) { - int no = 0; - for (auto const& n : m_dwindleNodesData) { - if (n.workspaceID == id && n.valid) - ++no; - } - return no; -} - -SDwindleNodeData* CHyprDwindleLayout::getFirstNodeOnWorkspace(const WORKSPACEID& id) { - for (auto& n : m_dwindleNodesData) { - if (n.workspaceID == id && validMapped(n.pWindow)) - return &n; - } - return nullptr; -} - -SDwindleNodeData* CHyprDwindleLayout::getClosestNodeOnWorkspace(const WORKSPACEID& id, const Vector2D& point) { - SDwindleNodeData* 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; -} - -SDwindleNodeData* CHyprDwindleLayout::getNodeFromWindow(PHLWINDOW pWindow) { - for (auto& n : m_dwindleNodesData) { - if (n.pWindow.lock() == pWindow && !n.isNode) - return &n; - } - - return nullptr; -} - -SDwindleNodeData* CHyprDwindleLayout::getMasterNodeOnWorkspace(const WORKSPACEID& id) { - for (auto& n : m_dwindleNodesData) { - if (!n.pParent && n.workspaceID == id) - return &n; - } - return nullptr; -} - -void CHyprDwindleLayout::applyNodeDataToWindow(SDwindleNodeData* pNode, bool force) { - // Don't set nodes, only windows. - if (pNode->isNode) - return; - - PHLMONITOR PMONITOR = nullptr; - - if (g_pCompositor->isWorkspaceSpecial(pNode->workspaceID)) { - for (auto const& m : g_pCompositor->m_monitors) { - if (m->activeSpecialWorkspaceID() == pNode->workspaceID) { - PMONITOR = m; - break; - } - } - } else if (const auto WS = g_pCompositor->getWorkspaceByID(pNode->workspaceID); WS) - PMONITOR = WS->m_monitor.lock(); - - if (!PMONITOR) { - Debug::log(ERR, "Orphaned Node {}!!", pNode); - return; - } - - // for gaps outer - const bool DISPLAYLEFT = STICKS(pNode->box.x, PMONITOR->m_position.x + PMONITOR->m_reservedTopLeft.x); - const bool DISPLAYRIGHT = STICKS(pNode->box.x + pNode->box.w, PMONITOR->m_position.x + PMONITOR->m_size.x - PMONITOR->m_reservedBottomRight.x); - const bool DISPLAYTOP = STICKS(pNode->box.y, PMONITOR->m_position.y + PMONITOR->m_reservedTopLeft.y); - const bool DISPLAYBOTTOM = STICKS(pNode->box.y + pNode->box.h, PMONITOR->m_position.y + PMONITOR->m_size.y - PMONITOR->m_reservedBottomRight.y); - - 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)) { - Debug::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"); - static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); - auto* const PGAPSIN = sc((PGAPSINDATA.ptr())->getData()); - auto* const PGAPSOUT = sc((PGAPSOUTDATA.ptr())->getData()); - - auto gapsIn = WORKSPACERULE.gapsIn.value_or(*PGAPSIN); - auto gapsOut = WORKSPACERULE.gapsOut.value_or(*PGAPSOUT); - 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 = PMONITOR->m_size - PMONITOR->m_reservedTopLeft - PMONITOR->m_reservedBottomRight; - - 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 ? gapsOut.m_left : gapsIn.m_left), sc(DISPLAYTOP ? gapsOut.m_top : gapsIn.m_top)); - - const auto GAPOFFSETBOTTOMRIGHT = Vector2D(sc(DISPLAYRIGHT ? gapsOut.m_right : gapsIn.m_right), sc(DISPLAYBOTTOM ? gapsOut.m_bottom : 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 = PMONITOR->m_size - PMONITOR->m_reservedTopLeft - PMONITOR->m_reservedBottomRight - - Vector2D{(double)(gapsOut.m_left + gapsOut.m_right), (double)(gapsOut.m_top + gapsOut.m_bottom)} - 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, PMONITOR->m_position.x + PMONITOR->m_reservedTopLeft.x + gapsOut.m_left + borderSize, - PMONITOR->m_size.x + PMONITOR->m_position.x - PMONITOR->m_reservedBottomRight.x - gapsOut.m_right - calcSize.x - borderSize); - calcPos.y = std::clamp(calcPos.y, PMONITOR->m_position.y + PMONITOR->m_reservedTopLeft.y + gapsOut.m_top + borderSize, - PMONITOR->m_size.y + PMONITOR->m_position.y - PMONITOR->m_reservedBottomRight.y - gapsOut.m_bottom - 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; - - m_dwindleNodesData.emplace_back(); - const auto PNODE = &m_dwindleNodesData.back(); - - 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; - - SDwindleNodeData* 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, RESERVED_EXTENTS | INPUT_EXTENTS | 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, RESERVED_EXTENTS | INPUT_EXTENTS)); - } - - if (!OPENINGON && g_pCompositor->isPointOnReservedArea(MOUSECOORDS, PMONITOR)) - OPENINGON = getClosestNodeOnWorkspace(PNODE->workspaceID, MOUSECOORDS); - - } else - OPENINGON = getFirstNodeOnWorkspace(pWindow->workspaceID()); - - Debug::log(LOG, "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->requestedMaxSize(); MAXSIZE.x < PREDSIZEMAX.x || MAXSIZE.y < PREDSIZEMAX.y) { - // we can't continue. make it floating. - pWindow->m_isFloating = true; - m_dwindleNodesData.remove(*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->box = CBox{PMONITOR->m_position + PMONITOR->m_reservedTopLeft, PMONITOR->m_size - PMONITOR->m_reservedTopLeft - PMONITOR->m_reservedBottomRight}; - - applyNodeDataToWindow(PNODE); - - return; - } - - // get the node under our cursor - - m_dwindleNodesData.emplace_back(); - const auto NEWPARENT = &m_dwindleNodesData.back(); - - // 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); - - 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) { - Debug::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) { - Debug::log(LOG, "Removing last node (dwindle)"); - m_dwindleNodesData.remove(*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(); - - m_dwindleNodesData.remove(*PPARENT); - m_dwindleNodesData.remove(*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) { - SDwindleNodeData fakeNode; - fakeNode.pWindow = PFULLWINDOW; - fakeNode.box = {PMONITOR->m_position + PMONITOR->m_reservedTopLeft, PMONITOR->m_size - PMONITOR->m_reservedTopLeft - PMONITOR->m_reservedBottomRight}; - fakeNode.workspaceID = pWorkspace->m_id; - PFULLWINDOW->m_position = fakeNode.box.pos(); - PFULLWINDOW->m_size = fakeNode.box.size(); - fakeNode.ignoreFullscreenChecks = true; - - applyNodeDataToWindow(&fakeNode); - } - - // if has fullscreen, don't calculate the rest - return; - } - - const auto TOPNODE = getMasterNodeOnWorkspace(pWorkspace->m_id); - - if (TOPNODE) { - TOPNODE->box = {PMONITOR->m_position + PMONITOR->m_reservedTopLeft, PMONITOR->m_size - PMONITOR->m_reservedTopLeft - PMONITOR->m_reservedBottomRight}; - 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 bool DISPLAYLEFT = STICKS(PWINDOW->m_position.x, PMONITOR->m_position.x + PMONITOR->m_reservedTopLeft.x); - const bool DISPLAYRIGHT = STICKS(PWINDOW->m_position.x + PWINDOW->m_size.x, PMONITOR->m_position.x + PMONITOR->m_size.x - PMONITOR->m_reservedBottomRight.x); - const bool DISPLAYTOP = STICKS(PWINDOW->m_position.y, PMONITOR->m_position.y + PMONITOR->m_reservedTopLeft.y); - const bool DISPLAYBOTTOM = STICKS(PWINDOW->m_position.y + PWINDOW->m_size.y, PMONITOR->m_position.y + PMONITOR->m_size.y - PMONITOR->m_reservedBottomRight.y); - - 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 - SDwindleNodeData* PVOUTER = nullptr; - SDwindleNodeData* PVINNER = nullptr; - SDwindleNodeData* PHOUTER = nullptr; - SDwindleNodeData* 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) { - 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 - - SDwindleNodeData fakeNode; - fakeNode.pWindow = pWindow; - fakeNode.box = {PMONITOR->m_position + PMONITOR->m_reservedTopLeft, PMONITOR->m_size - PMONITOR->m_reservedTopLeft - PMONITOR->m_reservedBottomRight}; - fakeNode.workspaceID = pWindow->workspaceID(); - pWindow->m_position = fakeNode.box.pos(); - pWindow->m_size = fakeNode.box.size(); - fakeNode.ignoreFullscreenChecks = true; - - 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()) { - Debug::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; - while (pRoot->pParent) { - pAncestor = pRoot; - pRoot = pRoot->pParent; - } - - auto& pSwap = pRoot->children[0] == pAncestor ? pRoot->children[1] : pRoot->children[0]; - std::swap(pNode, pSwap); - std::swap(pNode->pParent, pSwap->pParent); - - // [stable] in that the focused window occupies same side of screen - if (stable) - std::swap(pRoot->children[0], pRoot->children[1]); - - // if the workspace is visible, recalculate layout - if (pWindow->m_workspace && pWindow->m_workspace->isVisible()) - pRoot->recalcSizePosRecursive(); -} - -void CHyprDwindleLayout::replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to) { - const auto PNODE = getNodeFromWindow(from); - - if (!PNODE) - return; - - PNODE->pWindow = to; - - applyNodeDataToWindow(PNODE, true); -} - -std::string CHyprDwindleLayout::getLayoutName() { - return "dwindle"; -} - -void CHyprDwindleLayout::onEnable() { - for (auto const& w : g_pCompositor->m_windows) { - if (w->m_isFloating || !w->m_isMapped || w->isHidden()) - continue; - - onWindowCreatedTiling(w); - } -} - -void CHyprDwindleLayout::onDisable() { - m_dwindleNodesData.clear(); -} - -Vector2D CHyprDwindleLayout::predictSizeForNewWindowTiled() { - if (!Desktop::focusState()->monitor()) - return {}; - - // get window candidate - PHLWINDOW candidate = Desktop::focusState()->window(); - - if (!candidate) - candidate = Desktop::focusState()->monitor()->m_activeWorkspace->getFirstWindow(); - - // create a fake node - SDwindleNodeData node; - - if (!candidate) - return Desktop::focusState()->monitor()->m_size; - else { - const auto PNODE = getNodeFromWindow(candidate); - - if (!PNODE) - return {}; - - node = *PNODE; - node.pWindow.reset(); - - CBox box = PNODE->box; - - static auto PFLMULT = CConfigValue("dwindle:split_width_multiplier"); - - bool splitTop = box.h * *PFLMULT > box.w; - - const auto SPLITSIDE = !splitTop; - - if (SPLITSIDE) - node.box = {{}, {box.w / 2.0, box.h}}; - else - node.box = {{}, {box.w, box.h / 2.0}}; - - // TODO: make this better and more accurate - - return node.box.size(); - } - - return {}; -} diff --git a/src/layout/DwindleLayout.hpp b/src/layout/DwindleLayout.hpp deleted file mode 100644 index 23f19956a..000000000 --- a/src/layout/DwindleLayout.hpp +++ /dev/null @@ -1,108 +0,0 @@ -#pragma once - -#include "IHyprLayout.hpp" -#include "../desktop/DesktopTypes.hpp" - -#include -#include -#include -#include -#include - -class CHyprDwindleLayout; -enum eFullscreenMode : int8_t; - -struct SDwindleNodeData { - SDwindleNodeData* pParent = nullptr; - bool isNode = false; - - PHLWINDOWREF pWindow; - - std::array children = {nullptr, nullptr}; - - 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); - 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::list 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(SDwindleNodeData*, bool force = false); - void calculateWorkspace(const PHLWORKSPACE& pWorkspace); - SDwindleNodeData* getNodeFromWindow(PHLWINDOW); - SDwindleNodeData* getFirstNodeOnWorkspace(const WORKSPACEID&); - SDwindleNodeData* getClosestNodeOnWorkspace(const WORKSPACEID&, const Vector2D&); - SDwindleNodeData* getMasterNodeOnWorkspace(const WORKSPACEID&); - - void toggleSplit(PHLWINDOW); - void swapSplit(PHLWINDOW); - void moveToRoot(PHLWINDOW, bool stable = true); - - eDirection m_overrideDirection = DIRECTION_DEFAULT; - - friend struct SDwindleNodeData; -}; - -template -struct std::formatter : std::formatter { - template - auto format(const SDwindleNodeData* 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->box.pos(), node->box.size()); - if (!node->isNode && !node->pWindow.expired()) - std::format_to(out, ", window: {:x}", node->pWindow.lock()); - return std::format_to(out, "]"); - } -}; diff --git a/src/layout/IHyprLayout.cpp b/src/layout/IHyprLayout.cpp deleted file mode 100644 index fb47938fc..000000000 --- a/src/layout/IHyprLayout.cpp +++ /dev/null @@ -1,1041 +0,0 @@ -#include "IHyprLayout.hpp" -#include "../defines.hpp" -#include "../Compositor.hpp" -#include "../render/decorations/CHyprGroupBarDecoration.hpp" -#include "../config/ConfigValue.hpp" -#include "../config/ConfigManager.hpp" -#include "../desktop/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()) { - Debug::log(LOG, "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) { - Debug::log(ERR, "{:m} has an invalid monitor in onWindowCreatedFloating!!!", pWindow); - return; - } - - if (desiredGeometry.width <= 5 || desiredGeometry.height <= 5) { - const auto PWINDOWSURFACE = pWindow->m_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)) { - Debug::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, RESERVED_EXTENTS | INPUT_EXTENTS | 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, RESERVED_EXTENTS | INPUT_EXTENTS | 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(RESERVED_EXTENTS | 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(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; - - SRange monX = {MON->m_position.x + MON->m_reservedTopLeft.x + GAPSOUT->m_left, MON->m_position.x + MON->m_size.x - MON->m_reservedBottomRight.x - GAPSOUT->m_right}; - SRange monY = {MON->m_position.y + MON->m_reservedTopLeft.y + GAPSOUT->m_top, MON->m_position.y + MON->m_size.y - MON->m_reservedBottomRight.y - GAPSOUT->m_bottom}; - - if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && - ((MON->m_reservedTopLeft.x > 0 && canSnap(sourceX.start, monX.start, GAPSIZE)) || - canSnap(sourceX.start, (monX.start -= MON->m_reservedTopLeft.x + EXTENTDIFF->topLeft.x), GAPSIZE))) { - SNAP(sourceX.start, sourceX.end, monX.start); - snaps |= SNAP_LEFT; - } - if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && - ((MON->m_reservedBottomRight.x > 0 && canSnap(sourceX.end, monX.end, GAPSIZE)) || - canSnap(sourceX.end, (monX.end += MON->m_reservedBottomRight.x + EXTENTDIFF->bottomRight.x), GAPSIZE))) { - SNAP(sourceX.end, sourceX.start, monX.end); - snaps |= SNAP_RIGHT; - } - if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && - ((MON->m_reservedTopLeft.y > 0 && canSnap(sourceY.start, monY.start, GAPSIZE)) || - canSnap(sourceY.start, (monY.start -= MON->m_reservedTopLeft.y + EXTENTDIFF->topLeft.y), GAPSIZE))) { - SNAP(sourceY.start, sourceY.end, monY.start); - snaps |= SNAP_UP; - } - if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && - ((MON->m_reservedBottomRight.y > 0 && canSnap(sourceY.end, monY.end, GAPSIZE)) || - canSnap(sourceY.end, (monY.end += MON->m_reservedBottomRight.y + 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->requestedMinSize().clamp(DRAGGINGWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D(MIN_WINDOW_SIZE, MIN_WINDOW_SIZE))); - Vector2D MAXSIZE; - if (DRAGGINGWINDOW->m_ruleApplicator->maxSize().hasValue()) - MAXSIZE = DRAGGINGWINDOW->requestedMaxSize().clamp({}, DRAGGINGWINDOW->m_ruleApplicator->maxSize().value()); - else - MAXSIZE = DRAGGINGWINDOW->requestedMaxSize().clamp({}, Vector2D(std::numeric_limits::max(), std::numeric_limits::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()) { - Debug::log(LOG, "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(RESERVED_EXTENTS | INPUT_EXTENTS); - CBox targetBoxMonLocal = tb.value_or(w->getWindowMainSurfaceBox()).translate(-PMONITOR->m_position).addExtents(EXTENTS); - const auto MONITOR_LOCAL_BOX = PMONITOR->logicalBoxMinusExtents().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) { - Debug::log(LOG, "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(), RESERVED_EXTENTS | INPUT_EXTENTS | 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(), RESERVED_EXTENTS | INPUT_EXTENTS | 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()) { - Debug::log(LOG, "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) { - Debug::log(LOG, "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))) { - Debug::log(LOG, "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->requestedMinSize().clamp(DRAGGINGWINDOW->m_ruleApplicator->minSize().valueOr(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; -} diff --git a/src/layout/IHyprLayout.hpp b/src/layout/IHyprLayout.hpp deleted file mode 100644 index d97a2ba8e..000000000 --- a/src/layout/IHyprLayout.hpp +++ /dev/null @@ -1,243 +0,0 @@ -#pragma once - -#include "../defines.hpp" -#include "../managers/input/InputManager.hpp" -#include - -class CWindow; -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); - - private: - int m_mouseMoveEventCount; - Vector2D m_beginDragXY; - Vector2D m_lastDragXY; - Vector2D m_beginDragPositionXY; - Vector2D m_beginDragSizeXY; - Vector2D m_draggingWindowOriginalFloatSize; - eRectCorner m_grabbedCorner = CORNER_TOPLEFT; - - PHLWINDOWREF m_lastTiledWindow; -}; diff --git a/src/layout/LayoutManager.cpp b/src/layout/LayoutManager.cpp new file mode 100644 index 000000000..1c7384266 --- /dev/null +++ b/src/layout/LayoutManager.cpp @@ -0,0 +1,349 @@ +#include "LayoutManager.hpp" + +#include "space/Space.hpp" +#include "target/Target.hpp" + +#include "../helpers/Monitor.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) { + if (target->space()) + 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; + } + + if (!target->space()) + 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 = Config::CCssGapData{0, 0, 0, 0}; + + const SnapFn SNAP = (MODE == MBIND_MOVE) ? snapMove : snapResize; + int snaps = 0; + + struct SRange { + double start = 0; + double end = 0; + }; + const auto EXTENTS = DRAGGINGWINDOW->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS); + SRange sourceX = {sourcePos.x - EXTENTS.topLeft.x, sourcePos.x + sourceSize.x + EXTENTS.bottomRight.x}; + SRange sourceY = {sourcePos.y - EXTENTS.topLeft.y, sourcePos.y + sourceSize.y + EXTENTS.bottomRight.y}; + + if (*SNAPWINDOWGAP) { + const double GAPSIZE = *SNAPWINDOWGAP; + const auto WSID = DRAGGINGWINDOW->workspaceID(); + const bool HASFULLSCREEN = DRAGGINGWINDOW->m_workspace && DRAGGINGWINDOW->m_workspace->m_hasFullscreenWindow; + + const auto* GAPSIN = *SNAPRESPECTGAPS ? sc(PGAPSIN.ptr()->getData()) : &GAPSNONE; + const double GAPSX = GAPSIN->m_left + GAPSIN->m_right; + const double GAPSY = GAPSIN->m_top + GAPSIN->m_bottom; + + for (auto& other : g_pCompositor->m_windows) { + if ((HASFULLSCREEN && !other->m_createdOverFullscreen) || other == DRAGGINGWINDOW || other->workspaceID() != WSID || !other->m_isMapped || other->m_fadingOut || + other->isX11OverrideRedirect()) + continue; + + const CBox SURF = other->getWindowBoxUnified(Desktop::View::RESERVED_EXTENTS); + const SRange SURFBX = {SURF.x - GAPSX, SURF.x + SURF.w + GAPSX}; + const SRange SURFBY = {SURF.y - GAPSY, SURF.y + SURF.h + GAPSY}; + + // only snap windows if their ranges overlap in the opposite axis + if (sourceY.start <= SURFBY.end && SURFBY.start <= sourceY.end) { + if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && canSnap(sourceX.start, SURFBX.end, GAPSIZE)) { + SNAP(sourceX.start, sourceX.end, SURFBX.end); + snaps |= SNAP_LEFT; + } else if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && canSnap(sourceX.end, SURFBX.start, GAPSIZE)) { + SNAP(sourceX.end, sourceX.start, SURFBX.start); + snaps |= SNAP_RIGHT; + } + } + if (sourceX.start <= SURFBX.end && SURFBX.start <= sourceX.end) { + if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && canSnap(sourceY.start, SURFBY.end, GAPSIZE)) { + SNAP(sourceY.start, sourceY.end, SURFBY.end); + snaps |= SNAP_UP; + } else if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && canSnap(sourceY.end, SURFBY.start, GAPSIZE)) { + SNAP(sourceY.end, sourceY.start, SURFBY.start); + snaps |= SNAP_DOWN; + } + } + + // corner snapping + if (sourceX.start == SURFBX.end || SURFBX.start == sourceX.end) { + const SRange SURFY = {SURFBY.start + GAPSY, SURFBY.end - GAPSY}; + if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && !(snaps & SNAP_UP) && canSnap(sourceY.start, SURFY.start, GAPSIZE)) { + SNAP(sourceY.start, sourceY.end, SURFY.start); + snaps |= SNAP_UP; + } else if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && !(snaps & SNAP_DOWN) && canSnap(sourceY.end, SURFY.end, GAPSIZE)) { + SNAP(sourceY.end, sourceY.start, SURFY.end); + snaps |= SNAP_DOWN; + } + } + if (sourceY.start == SURFBY.end || SURFBY.start == sourceY.end) { + const SRange SURFX = {SURFBX.start + GAPSX, SURFBX.end - GAPSX}; + if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && !(snaps & SNAP_LEFT) && canSnap(sourceX.start, SURFX.start, GAPSIZE)) { + SNAP(sourceX.start, sourceX.end, SURFX.start); + snaps |= SNAP_LEFT; + } else if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && !(snaps & SNAP_RIGHT) && canSnap(sourceX.end, SURFX.end, GAPSIZE)) { + SNAP(sourceX.end, sourceX.start, SURFX.end); + snaps |= SNAP_RIGHT; + } + } + } + } + + if (*SNAPMONITORGAP) { + const double GAPSIZE = *SNAPMONITORGAP; + const auto EXTENTNONE = SBoxExtents{{0, 0}, {0, 0}}; + const auto* EXTENTDIFF = *SNAPBORDEROVERLAP ? &EXTENTS : &EXTENTNONE; + const auto MON = DRAGGINGWINDOW->m_monitor.lock(); + + const auto* GAPSOUT = *SNAPRESPECTGAPS ? sc(PGAPSOUT.ptr()->getData()) : &GAPSNONE; + const auto WORK_AREA = Desktop::CReservedArea{GAPSOUT->m_top, GAPSOUT->m_right, GAPSOUT->m_bottom, GAPSOUT->m_left}.apply(MON->logicalBoxMinusReserved()); + + SRange monX = {WORK_AREA.x, WORK_AREA.x + WORK_AREA.w}; + SRange monY = {WORK_AREA.y, WORK_AREA.y + WORK_AREA.h}; + + const bool HAS_LEFT = MON->m_reservedArea.left() > 0; + const bool HAS_TOP = MON->m_reservedArea.top() > 0; + const bool HAS_BOTTOM = MON->m_reservedArea.bottom() > 0; + const bool HAS_RIGHT = MON->m_reservedArea.right() > 0; + + if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && + ((HAS_LEFT && canSnap(sourceX.start, monX.start, GAPSIZE)) || canSnap(sourceX.start, (monX.start -= MON->m_reservedArea.left() + EXTENTDIFF->topLeft.x), GAPSIZE))) { + SNAP(sourceX.start, sourceX.end, monX.start); + snaps |= SNAP_LEFT; + } + if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && + ((HAS_RIGHT && canSnap(sourceX.end, monX.end, GAPSIZE)) || canSnap(sourceX.end, (monX.end += MON->m_reservedArea.right() + EXTENTDIFF->bottomRight.x), GAPSIZE))) { + SNAP(sourceX.end, sourceX.start, monX.end); + snaps |= SNAP_RIGHT; + } + if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && + ((HAS_TOP && canSnap(sourceY.start, monY.start, GAPSIZE)) || canSnap(sourceY.start, (monY.start -= MON->m_reservedArea.top() + EXTENTDIFF->topLeft.y), GAPSIZE))) { + SNAP(sourceY.start, sourceY.end, monY.start); + snaps |= SNAP_UP; + } + if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && + ((HAS_BOTTOM && canSnap(sourceY.end, monY.end, GAPSIZE)) || canSnap(sourceY.end, (monY.end += MON->m_reservedArea.bottom() + EXTENTDIFF->bottomRight.y), GAPSIZE))) { + SNAP(sourceY.end, sourceY.start, monY.end); + snaps |= SNAP_DOWN; + } + } + + // remove extents from main surface + sourceX = {sourceX.start + EXTENTS.topLeft.x, sourceX.end - EXTENTS.bottomRight.x}; + sourceY = {sourceY.start + EXTENTS.topLeft.y, sourceY.end - EXTENTS.bottomRight.y}; + + if (MODE == MBIND_RESIZE_FORCE_RATIO) { + if ((CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && snaps & SNAP_LEFT) || (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && snaps & SNAP_RIGHT)) { + const double SIZEY = (sourceX.end - sourceX.start) * (BEGINSIZE.y / BEGINSIZE.x); + if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT)) + sourceY.start = sourceY.end - SIZEY; + else + sourceY.end = sourceY.start + SIZEY; + } else if ((CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && snaps & SNAP_UP) || (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && snaps & SNAP_DOWN)) { + const double SIZEX = (sourceY.end - sourceY.start) * (BEGINSIZE.x / BEGINSIZE.y); + if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT)) + sourceX.start = sourceX.end - SIZEX; + else + sourceX.end = sourceX.start + SIZEX; + } + } + + sourcePos = {sourceX.start, sourceY.start}; + sourceSize = {sourceX.end - sourceX.start, sourceY.end - sourceY.start}; +} + +void CLayoutManager::recalculateMonitor(PHLMONITOR m) { + if (m->m_activeSpecialWorkspace) + m->m_activeSpecialWorkspace->m_space->recalculate(); + if (m->m_activeWorkspace) + m->m_activeWorkspace->m_space->recalculate(); +} + +void CLayoutManager::invalidateMonitorGeometries(PHLMONITOR m) { + for (const auto& ws : g_pCompositor->getWorkspaces()) { + if (ws && ws->m_monitor == m) { + ws->m_space->recheckWorkArea(); + ws->m_space->recalculate(); + } + } +} diff --git a/src/layout/LayoutManager.hpp b/src/layout/LayoutManager.hpp new file mode 100644 index 000000000..e99911d51 --- /dev/null +++ b/src/layout/LayoutManager.hpp @@ -0,0 +1,87 @@ +#pragma once + +#include "../helpers/memory/Memory.hpp" +#include "../helpers/math/Math.hpp" +#include "../managers/input/InputManager.hpp" + +#include "supplementary/DragController.hpp" + +#include +#include + +enum eFullscreenMode : int8_t; + +namespace Layout { + class ITarget; + class CSpace; + + enum eRectCorner : uint8_t { + CORNER_NONE = 0, + CORNER_TOPLEFT = (1 << 0), + CORNER_TOPRIGHT = (1 << 1), + CORNER_BOTTOMRIGHT = (1 << 2), + CORNER_BOTTOMLEFT = (1 << 3), + }; + + inline eRectCorner cornerFromBox(const CBox& box, const Vector2D& pos) { + const auto CENTER = box.middle(); + + if (pos.x < CENTER.x) + return pos.y < CENTER.y ? CORNER_TOPLEFT : CORNER_BOTTOMLEFT; + return pos.y < CENTER.y ? CORNER_TOPRIGHT : CORNER_BOTTOMRIGHT; + } + + enum eSnapEdge : uint8_t { + SNAP_INVALID = 0, + SNAP_UP = (1 << 0), + SNAP_DOWN = (1 << 1), + SNAP_LEFT = (1 << 2), + SNAP_RIGHT = (1 << 3), + }; + + class CLayoutManager { + public: + CLayoutManager(); + ~CLayoutManager() = default; + + void newTarget(SP target, SP space); + void removeTarget(SP target); + + void changeFloatingMode(SP target); + + void beginDragTarget(SP target, eMouseBindMode mode); + void moveMouse(const Vector2D& mousePos); + void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + void moveTarget(const Vector2D& Δ, SP target); + void setTargetGeom(const CBox& box, SP target); // floats only + void endDragTarget(); + + std::expected layoutMsg(const std::string_view& sv); + + void fullscreenRequestForTarget(SP target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode); + + void switchTargets(SP a, SP b, bool preserveFocus = true); + + void moveInDirection(SP target, const std::string& direction, bool silent = false); + + SP getNextCandidate(SP space, SP from); + + bool isReachable(SP target); + + void bringTargetToTop(SP target); + + std::optional predictSizeForNewTiledTarget(); + + void performSnap(Vector2D& sourcePos, Vector2D& sourceSize, SP target, eMouseBindMode mode, int corner, const Vector2D& beginSize); + + void invalidateMonitorGeometries(PHLMONITOR); + void recalculateMonitor(PHLMONITOR); + + const UP& dragController(); + + private: + UP m_dragStateController = makeUnique(); + }; +} + +inline UP g_layoutManager; \ No newline at end of file diff --git a/src/layout/MasterLayout.cpp b/src/layout/MasterLayout.cpp deleted file mode 100644 index d0b82343a..000000000 --- a/src/layout/MasterLayout.cpp +++ /dev/null @@ -1,1527 +0,0 @@ -#include "MasterLayout.hpp" -#include "../Compositor.hpp" -#include "../render/decorations/CHyprGroupBarDecoration.hpp" -#include "config/ConfigDataValues.hpp" -#include -#include "../config/ConfigValue.hpp" -#include "../config/ConfigManager.hpp" -#include "../render/Renderer.hpp" -#include "../managers/input/InputManager.hpp" -#include "../managers/LayoutManager.hpp" -#include "../managers/EventManager.hpp" -#include "../desktop/state/FocusState.hpp" -#include "xwayland/XWayland.hpp" - -SMasterNodeData* CHyprMasterLayout::getNodeFromWindow(PHLWINDOW pWindow) { - for (auto& nd : m_masterNodesData) { - if (nd.pWindow.lock() == pWindow) - return &nd; - } - - return nullptr; -} - -int CHyprMasterLayout::getNodesOnWorkspace(const WORKSPACEID& ws) { - int no = 0; - for (auto const& n : m_masterNodesData) { - if (n.workspaceID == ws) - no++; - } - - return no; -} - -int CHyprMasterLayout::getMastersOnWorkspace(const WORKSPACEID& ws) { - int no = 0; - for (auto const& n : m_masterNodesData) { - if (n.workspaceID == ws && n.isMaster) - no++; - } - - return no; -} - -SMasterWorkspaceData* CHyprMasterLayout::getMasterWorkspaceData(const WORKSPACEID& ws) { - for (auto& n : m_masterWorkspacesData) { - if (n.workspaceID == ws) - return &n; - } - - //create on the fly if it doesn't exist yet - const auto PWORKSPACEDATA = &m_masterWorkspacesData.emplace_back(); - PWORKSPACEDATA->workspaceID = ws; - static auto PORIENTATION = CConfigValue("master:orientation"); - - if (*PORIENTATION == "top") - PWORKSPACEDATA->orientation = ORIENTATION_TOP; - else if (*PORIENTATION == "right") - PWORKSPACEDATA->orientation = ORIENTATION_RIGHT; - else if (*PORIENTATION == "bottom") - PWORKSPACEDATA->orientation = ORIENTATION_BOTTOM; - else if (*PORIENTATION == "center") - PWORKSPACEDATA->orientation = ORIENTATION_CENTER; - else - PWORKSPACEDATA->orientation = ORIENTATION_LEFT; - - return PWORKSPACEDATA; -} - -std::string CHyprMasterLayout::getLayoutName() { - return "Master"; -} - -SMasterNodeData* CHyprMasterLayout::getMasterNodeOnWorkspace(const WORKSPACEID& ws) { - for (auto& n : m_masterNodesData) { - if (n.isMaster && n.workspaceID == ws) - return &n; - } - - return nullptr; -} - -void CHyprMasterLayout::onWindowCreatedTiling(PHLWINDOW pWindow, eDirection direction) { - if (pWindow->m_isFloating) - return; - - static auto PNEWONACTIVE = CConfigValue("master:new_on_active"); - static auto PNEWONTOP = CConfigValue("master:new_on_top"); - static auto PNEWSTATUS = CConfigValue("master:new_status"); - - const auto PMONITOR = pWindow->m_monitor.lock(); - - const bool BNEWBEFOREACTIVE = *PNEWONACTIVE == "before"; - const bool BNEWISMASTER = *PNEWSTATUS == "master"; - - const auto PNODE = [&]() { - if (*PNEWONACTIVE != "none" && !BNEWISMASTER) { - const auto pLastNode = getNodeFromWindow(Desktop::focusState()->window()); - if (pLastNode && !(pLastNode->isMaster && (getMastersOnWorkspace(pWindow->workspaceID()) == 1 || *PNEWSTATUS == "slave"))) { - auto it = std::ranges::find(m_masterNodesData, *pLastNode); - if (!BNEWBEFOREACTIVE) - ++it; - return &(*m_masterNodesData.emplace(it)); - } - } - return *PNEWONTOP ? &m_masterNodesData.emplace_front() : &m_masterNodesData.emplace_back(); - }(); - - PNODE->workspaceID = pWindow->workspaceID(); - PNODE->pWindow = pWindow; - - const auto WINDOWSONWORKSPACE = getNodesOnWorkspace(PNODE->workspaceID); - static auto PMFACT = CConfigValue("master:mfact"); - float lastSplitPercent = *PMFACT; - - auto OPENINGON = isWindowTiled(Desktop::focusState()->window()) && Desktop::focusState()->window()->m_workspace == pWindow->m_workspace ? - getNodeFromWindow(Desktop::focusState()->window()) : - getMasterNodeOnWorkspace(pWindow->workspaceID()); - - const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); - static auto PDROPATCURSOR = CConfigValue("master:drop_at_cursor"); - eOrientation orientation = getDynamicOrientation(pWindow->m_workspace); - const auto NODEIT = std::ranges::find(m_masterNodesData, *PNODE); - - bool forceDropAsMaster = false; - // if dragging window to move, drop it at the cursor position instead of bottom/top of stack - if (*PDROPATCURSOR && g_pInputManager->m_dragMode == MBIND_MOVE) { - if (WINDOWSONWORKSPACE > 2) { - for (auto it = m_masterNodesData.begin(); it != m_masterNodesData.end(); ++it) { - if (it->workspaceID != pWindow->workspaceID()) - continue; - const CBox box = it->pWindow->getWindowIdealBoundingBoxIgnoreReserved(); - if (box.containsPoint(MOUSECOORDS)) { - switch (orientation) { - case ORIENTATION_LEFT: - case ORIENTATION_RIGHT: - if (MOUSECOORDS.y > it->pWindow->middle().y) - ++it; - break; - case ORIENTATION_TOP: - case ORIENTATION_BOTTOM: - if (MOUSECOORDS.x > it->pWindow->middle().x) - ++it; - break; - case ORIENTATION_CENTER: break; - default: UNREACHABLE(); - } - m_masterNodesData.splice(it, m_masterNodesData, NODEIT); - break; - } - } - } else if (WINDOWSONWORKSPACE == 2) { - // when dropping as the second tiled window in the workspace, - // make it the master only if the cursor is on the master side of the screen - for (auto const& nd : m_masterNodesData) { - if (nd.isMaster && nd.workspaceID == PNODE->workspaceID) { - switch (orientation) { - case ORIENTATION_LEFT: - case ORIENTATION_CENTER: - if (MOUSECOORDS.x < nd.pWindow->middle().x) - forceDropAsMaster = true; - break; - case ORIENTATION_RIGHT: - if (MOUSECOORDS.x > nd.pWindow->middle().x) - forceDropAsMaster = true; - break; - case ORIENTATION_TOP: - if (MOUSECOORDS.y < nd.pWindow->middle().y) - forceDropAsMaster = true; - break; - case ORIENTATION_BOTTOM: - if (MOUSECOORDS.y > nd.pWindow->middle().y) - forceDropAsMaster = true; - break; - default: UNREACHABLE(); - } - break; - } - } - } - } - - if ((BNEWISMASTER && g_pInputManager->m_dragMode != MBIND_MOVE) // - || WINDOWSONWORKSPACE == 1 // - || (WINDOWSONWORKSPACE > 2 && !pWindow->m_firstMap && OPENINGON && OPENINGON->isMaster) // - || forceDropAsMaster // - || (*PNEWSTATUS == "inherit" && OPENINGON && OPENINGON->isMaster && g_pInputManager->m_dragMode != MBIND_MOVE)) { - - if (BNEWBEFOREACTIVE) { - for (auto& nd : m_masterNodesData | std::views::reverse) { - if (nd.isMaster && nd.workspaceID == PNODE->workspaceID) { - nd.isMaster = false; - lastSplitPercent = nd.percMaster; - break; - } - } - } else { - for (auto& nd : m_masterNodesData) { - if (nd.isMaster && nd.workspaceID == PNODE->workspaceID) { - nd.isMaster = false; - lastSplitPercent = nd.percMaster; - break; - } - } - } - - PNODE->isMaster = true; - PNODE->percMaster = lastSplitPercent; - - // first, check if it isn't too big. - if (const auto MAXSIZE = pWindow->requestedMaxSize(); 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->requestedMaxSize(); - 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; - fakeNode.position = PMONITOR->m_position + PMONITOR->m_reservedTopLeft; - fakeNode.size = PMONITOR->m_size - PMONITOR->m_reservedTopLeft - PMONITOR->m_reservedBottomRight; - 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 WSSIZE = PMONITOR->m_size - PMONITOR->m_reservedTopLeft - PMONITOR->m_reservedBottomRight; - const auto WSPOS = PMONITOR->m_position + PMONITOR->m_reservedTopLeft; - - 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) ? WSSIZE.x : WSSIZE.y; - 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 = WSSIZE.x * PMASTERNODE->percMaster; - float nextX = 0; - - if (orientation == ORIENTATION_RIGHT) - nextX = WSSIZE.x - WIDTH; - else if (orientation == ORIENTATION_CENTER) - nextX = (WSSIZE.x - WIDTH) / 2; - - PMASTERNODE->size = Vector2D(WIDTH, WSSIZE.y); - PMASTERNODE->position = WSPOS + Vector2D(nextX, 0.0); - } else { - PMASTERNODE->size = WSSIZE; - PMASTERNODE->position = WSPOS; - } - - applyNodeDataToWindow(PMASTERNODE); - return; - } else if (orientation == ORIENTATION_TOP || orientation == ORIENTATION_BOTTOM) { - const float HEIGHT = STACKWINDOWS != 0 ? WSSIZE.y * PMASTERNODE->percMaster : WSSIZE.y; - float widthLeft = WSSIZE.x; - int mastersLeft = MASTERS; - float nextX = 0; - float nextY = 0; - - if (orientation == ORIENTATION_BOTTOM) - nextY = WSSIZE.y - 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 *= WSSIZE.x / masterAccumulatedSize; - WIDTH = masterAverageSize * nd.percSize; - } - - nd.size = Vector2D(WIDTH, HEIGHT); - nd.position = WSPOS + Vector2D(nextX, nextY); - applyNodeDataToWindow(&nd); - - mastersLeft--; - widthLeft -= WIDTH; - nextX += WIDTH; - } - } else { // orientation left, right or center - float WIDTH = *PIGNORERESERVED && centerMasterWindow ? PMONITOR->m_size.x : WSSIZE.x; - float heightLeft = WSSIZE.y; - int mastersLeft = MASTERS; - float nextX = 0; - float nextY = 0; - - if (STACKWINDOWS > 0 || centerMasterWindow) - WIDTH *= PMASTERNODE->percMaster; - - if (orientation == ORIENTATION_RIGHT) { - nextX = WSSIZE.x - WIDTH; - } else if (centerMasterWindow) { - nextX = ((*PIGNORERESERVED && centerMasterWindow ? PMONITOR->m_size.x : WSSIZE.x) - 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 *= WSSIZE.y / masterAccumulatedSize; - HEIGHT = masterAverageSize * nd.percSize; - } - - nd.size = Vector2D(WIDTH, HEIGHT); - nd.position = (*PIGNORERESERVED && centerMasterWindow ? PMONITOR->m_position : WSPOS) + 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 = WSSIZE.y - PMASTERNODE->size.y; - float widthLeft = WSSIZE.x; - 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 *= WSSIZE.x / slaveAccumulatedSize; - WIDTH = slaveAverageSize * nd.percSize; - } - - nd.size = Vector2D(WIDTH, HEIGHT); - nd.position = WSPOS + Vector2D(nextX, nextY); - applyNodeDataToWindow(&nd); - - slavesLeft--; - widthLeft -= WIDTH; - nextX += WIDTH; - } - } else if (orientation == ORIENTATION_LEFT || orientation == ORIENTATION_RIGHT) { - const float WIDTH = WSSIZE.x - PMASTERNODE->size.x; - float heightLeft = WSSIZE.y; - 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 *= WSSIZE.y / slaveAccumulatedSize; - HEIGHT = slaveAverageSize * nd.percSize; - } - - nd.size = Vector2D(WIDTH, HEIGHT); - nd.position = WSPOS + Vector2D(nextX, nextY); - applyNodeDataToWindow(&nd); - - slavesLeft--; - heightLeft -= HEIGHT; - nextY += HEIGHT; - } - } else { // slaves for centered master window(s) - const float WIDTH = ((*PIGNORERESERVED ? PMONITOR->m_size.x : WSSIZE.x) - PMASTERNODE->size.x) / 2.0; - float heightLeft = 0; - float heightLeftL = WSSIZE.y; - float heightLeftR = WSSIZE.y; - 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 = WSSIZE.y / slavesLeftL; - const float slaveAverageHeightR = WSSIZE.y / 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_reservedTopLeft.x : 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 *= WSSIZE.y / slaveAccumulatedHeightR; - HEIGHT = slaveAverageHeightR * nd.percSize; - } else { - nd.percSize *= WSSIZE.y / slaveAccumulatedHeightL; - HEIGHT = slaveAverageHeightL * nd.percSize; - } - } - - nd.size = Vector2D(*PIGNORERESERVED ? (WIDTH - (onRight ? PMONITOR->m_reservedBottomRight.x : PMONITOR->m_reservedTopLeft.x)) : WIDTH, HEIGHT); - nd.position = WSPOS + 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; - - if (g_pCompositor->isWorkspaceSpecial(pNode->workspaceID)) { - for (auto const& m : g_pCompositor->m_monitors) { - if (m->activeSpecialWorkspaceID() == pNode->workspaceID) { - PMONITOR = m; - break; - } - } - } else - PMONITOR = g_pCompositor->getWorkspaceByID(pNode->workspaceID)->m_monitor.lock(); - - if (!PMONITOR) { - Debug::log(ERR, "Orphaned Node {}!!", pNode); - return; - } - - // for gaps outer - const bool DISPLAYLEFT = STICKS(pNode->position.x, PMONITOR->m_position.x + PMONITOR->m_reservedTopLeft.x); - const bool DISPLAYRIGHT = STICKS(pNode->position.x + pNode->size.x, PMONITOR->m_position.x + PMONITOR->m_size.x - PMONITOR->m_reservedBottomRight.x); - const bool DISPLAYTOP = STICKS(pNode->position.y, PMONITOR->m_position.y + PMONITOR->m_reservedTopLeft.y); - const bool DISPLAYBOTTOM = STICKS(pNode->position.y + pNode->size.y, PMONITOR->m_position.y + PMONITOR->m_size.y - PMONITOR->m_reservedBottomRight.y); - - 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"); - static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); - auto* PGAPSIN = sc((PGAPSINDATA.ptr())->getData()); - auto* PGAPSOUT = sc((PGAPSOUTDATA.ptr())->getData()); - - auto gapsIn = WORKSPACERULE.gapsIn.value_or(*PGAPSIN); - auto gapsOut = WORKSPACERULE.gapsOut.value_or(*PGAPSOUT); - - if (!validMapped(PWINDOW)) { - Debug::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 ? gapsOut.m_left : gapsIn.m_left), sc(DISPLAYTOP ? gapsOut.m_top : gapsIn.m_top)); - - const auto OFFSETBOTTOMRIGHT = Vector2D(sc(DISPLAYRIGHT ? gapsOut.m_right : gapsIn.m_right), sc(DISPLAYBOTTOM ? gapsOut.m_bottom : 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 = PMONITOR->m_size - PMONITOR->m_reservedTopLeft - PMONITOR->m_reservedBottomRight - - Vector2D{(double)(gapsOut.m_left + gapsOut.m_right), (double)(gapsOut.m_top + gapsOut.m_bottom)} - 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, PMONITOR->m_position.x + PMONITOR->m_reservedTopLeft.x + gapsOut.m_left + borderSize, - PMONITOR->m_size.x + PMONITOR->m_position.x - PMONITOR->m_reservedBottomRight.x - gapsOut.m_right - calcSize.x - borderSize); - calcPos.y = std::clamp(calcPos.y, PMONITOR->m_position.y + PMONITOR->m_reservedTopLeft.y + gapsOut.m_top + borderSize, - PMONITOR->m_size.y + PMONITOR->m_position.y - PMONITOR->m_reservedBottomRight.y - gapsOut.m_bottom - 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 bool DISPLAYBOTTOM = STICKS(PWINDOW->m_position.y + PWINDOW->m_size.y, PMONITOR->m_position.y + PMONITOR->m_size.y - PMONITOR->m_reservedBottomRight.y); - const bool DISPLAYRIGHT = STICKS(PWINDOW->m_position.x + PWINDOW->m_size.x, PMONITOR->m_position.x + PMONITOR->m_size.x - PMONITOR->m_reservedBottomRight.x); - const bool DISPLAYTOP = STICKS(PWINDOW->m_position.y, PMONITOR->m_position.y + PMONITOR->m_reservedTopLeft.y); - const bool DISPLAYLEFT = STICKS(PWINDOW->m_position.x, PMONITOR->m_position.x + PMONITOR->m_reservedTopLeft.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; - const auto WSSIZE = PMONITOR->m_size - PMONITOR->m_reservedTopLeft - PMONITOR->m_reservedBottomRight; - - auto nodesInSameColumn = PNODE->isMaster ? MASTERS : STACKWINDOWS; - if (orientation == ORIENTATION_CENTER && !PNODE->isMaster) - nodesInSameColumn = DISPLAYRIGHT ? (nodesInSameColumn + 1) / 2 : nodesInSameColumn / 2; - - const auto SIZE = isStackVertical ? WSSIZE.y / nodesInSameColumn : WSSIZE.x / 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 ? WSSIZE.y : WSSIZE.x; - 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; - fakeNode.pWindow = pWindow; - fakeNode.position = PMONITOR->m_position + PMONITOR->m_reservedTopLeft; - fakeNode.size = PMONITOR->m_size - PMONITOR->m_reservedTopLeft - PMONITOR->m_reservedBottomRight; - 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()) { - Debug::log(ERR, "layoutmsg called without params"); - return 0; - } - - auto command = vars[0]; - - // swapwithmaster - // first message argument can have the following values: - // * master - keep the focus at the new master - // * child - keep the focus at the new child - // * auto (default) - swap the focus (keep the focus of the previously selected window) - // * ignoremaster - ignore if master is focused - if (command == "swapwithmaster") { - const auto PWINDOW = header.pWindow; - - if (!PWINDOW) - return 0; - - if (!isWindowTiled(PWINDOW)) - return 0; - - const auto PMASTER = getMasterNodeOnWorkspace(PWINDOW->workspaceID()); - - if (!PMASTER) - return 0; - - const auto NEWCHILD = PMASTER->pWindow.lock(); - - const bool IGNORE_IF_MASTER = vars.size() >= 2 && std::ranges::any_of(vars, [](const auto& e) { return e == "ignoremaster"; }); - - if (PMASTER->pWindow.lock() != PWINDOW) { - const auto& NEWMASTER = PWINDOW; - const bool newFocusToChild = vars.size() >= 2 && vars[1] == "child"; - switchWindows(NEWMASTER, NEWCHILD); - const auto NEWFOCUS = newFocusToChild ? NEWCHILD : NEWMASTER; - switchToWindow(NEWFOCUS); - } else if (!IGNORE_IF_MASTER) { - for (auto const& n : m_masterNodesData) { - if (n.workspaceID == PMASTER->workspaceID && !n.isMaster) { - const auto NEWMASTER = n.pWindow.lock(); - switchWindows(NEWMASTER, NEWCHILD); - const bool newFocusToMaster = vars.size() >= 2 && vars[1] == "master"; - const auto NEWFOCUS = newFocusToMaster ? NEWMASTER : NEWCHILD; - switchToWindow(NEWFOCUS); - break; - } - } - } - - return 0; - } - // focusmaster - // first message argument can have the following values: - // * master - keep the focus at the new master, even if it was focused before - // * previous - focus window which was previously switched from using `focusmaster previous` command, otherwise fallback to `auto` - // * auto (default) - swap the focus with the first child, if the current focus was master, otherwise focus master - else if (command == "focusmaster") { - const auto PWINDOW = header.pWindow; - - if (!PWINDOW) - return 0; - - const auto PMASTER = getMasterNodeOnWorkspace(PWINDOW->workspaceID()); - - if (!PMASTER) - return 0; - - const auto& ARG = vars[1]; // returns empty string if out of bounds - - if (PMASTER->pWindow.lock() != PWINDOW) { - switchToWindow(PMASTER->pWindow.lock()); - // save previously focused window (only for `previous` mode) - if (ARG == "previous") - getMasterWorkspaceData(PWINDOW->workspaceID())->focusMasterPrev = PWINDOW; - return 0; - } - - const auto focusAuto = [&]() { - // focus first non-master window - for (auto const& n : m_masterNodesData) { - if (n.workspaceID == PMASTER->workspaceID && !n.isMaster) { - switchToWindow(n.pWindow.lock()); - break; - } - } - }; - - if (ARG == "master") - return 0; - // switch to previously saved window - else if (ARG == "previous") { - const auto PREVWINDOW = getMasterWorkspaceData(PWINDOW->workspaceID())->focusMasterPrev.lock(); - const bool VALID = validMapped(PREVWINDOW) && PWINDOW->workspaceID() == PREVWINDOW->workspaceID() && PWINDOW != PREVWINDOW; - VALID ? switchToWindow(PREVWINDOW) : focusAuto(); - } else - focusAuto(); - } else if (command == "cyclenext") { - const auto PWINDOW = header.pWindow; - - if (!PWINDOW) - return 0; - - const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop"; - const auto PNEXTWINDOW = getNextWindow(PWINDOW, true, !NOLOOP); - switchToWindow(PNEXTWINDOW); - } else if (command == "cycleprev") { - const auto PWINDOW = header.pWindow; - - if (!PWINDOW) - return 0; - - const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop"; - const auto PPREVWINDOW = getNextWindow(PWINDOW, false, !NOLOOP); - switchToWindow(PPREVWINDOW); - } else if (command == "swapnext") { - if (!validMapped(header.pWindow)) - return 0; - - if (header.pWindow->m_isFloating) { - g_pKeybindManager->m_dispatchers["swapnext"](""); - return 0; - } - - const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop"; - const auto PWINDOWTOSWAPWITH = getNextWindow(header.pWindow, true, !NOLOOP); - - if (PWINDOWTOSWAPWITH) { - g_pCompositor->setWindowFullscreenInternal(header.pWindow, FSMODE_NONE); - switchWindows(header.pWindow, PWINDOWTOSWAPWITH); - switchToWindow(header.pWindow); - } - } else if (command == "swapprev") { - if (!validMapped(header.pWindow)) - return 0; - - if (header.pWindow->m_isFloating) { - g_pKeybindManager->m_dispatchers["swapnext"]("prev"); - return 0; - } - - const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop"; - const auto PWINDOWTOSWAPWITH = getNextWindow(header.pWindow, false, !NOLOOP); - - if (PWINDOWTOSWAPWITH) { - g_pCompositor->setWindowFullscreenClient(header.pWindow, FSMODE_NONE); - switchWindows(header.pWindow, PWINDOWTOSWAPWITH); - switchToWindow(header.pWindow); - } - } else if (command == "addmaster") { - if (!validMapped(header.pWindow)) - return 0; - - if (header.pWindow->m_isFloating) - return 0; - - const auto PNODE = getNodeFromWindow(header.pWindow); - - const auto WINDOWS = getNodesOnWorkspace(header.pWindow->workspaceID()); - const auto MASTERS = getMastersOnWorkspace(header.pWindow->workspaceID()); - static auto SMALLSPLIT = CConfigValue("master:allow_small_split"); - - if (MASTERS + 2 > WINDOWS && *SMALLSPLIT == 0) - return 0; - - g_pCompositor->setWindowFullscreenInternal(header.pWindow, FSMODE_NONE); - - if (!PNODE || PNODE->isMaster) { - // first non-master node - for (auto& n : m_masterNodesData) { - if (n.workspaceID == header.pWindow->workspaceID() && !n.isMaster) { - n.isMaster = true; - break; - } - } - } else { - PNODE->isMaster = true; - } - - recalculateMonitor(header.pWindow->monitorID()); - - } else if (command == "removemaster") { - - if (!validMapped(header.pWindow)) - return 0; - - if (header.pWindow->m_isFloating) - return 0; - - const auto PNODE = getNodeFromWindow(header.pWindow); - - const auto WINDOWS = getNodesOnWorkspace(header.pWindow->workspaceID()); - const auto MASTERS = getMastersOnWorkspace(header.pWindow->workspaceID()); - - if (WINDOWS < 2 || MASTERS < 2) - return 0; - - g_pCompositor->setWindowFullscreenInternal(header.pWindow, FSMODE_NONE); - - if (!PNODE || !PNODE->isMaster) { - // first non-master node - for (auto& nd : m_masterNodesData | std::views::reverse) { - if (nd.workspaceID == header.pWindow->workspaceID() && nd.isMaster) { - nd.isMaster = false; - break; - } - } - } else { - PNODE->isMaster = false; - } - - recalculateMonitor(header.pWindow->monitorID()); - } else if (command == "orientationleft" || command == "orientationright" || command == "orientationtop" || command == "orientationbottom" || command == "orientationcenter") { - const auto PWINDOW = header.pWindow; - - if (!PWINDOW) - return 0; - - g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); - - const auto PWORKSPACEDATA = getMasterWorkspaceData(PWINDOW->workspaceID()); - - if (command == "orientationleft") - PWORKSPACEDATA->orientation = ORIENTATION_LEFT; - else if (command == "orientationright") - PWORKSPACEDATA->orientation = ORIENTATION_RIGHT; - else if (command == "orientationtop") - PWORKSPACEDATA->orientation = ORIENTATION_TOP; - else if (command == "orientationbottom") - PWORKSPACEDATA->orientation = ORIENTATION_BOTTOM; - else if (command == "orientationcenter") - PWORKSPACEDATA->orientation = ORIENTATION_CENTER; - - recalculateMonitor(header.pWindow->monitorID()); - - } else if (command == "orientationnext") { - runOrientationCycle(header, nullptr, 1); - } else if (command == "orientationprev") { - runOrientationCycle(header, nullptr, -1); - } else if (command == "orientationcycle") { - runOrientationCycle(header, &vars, 1); - } else if (command == "mfact") { - g_pKeybindManager->m_dispatchers["splitratio"](vars[1] + " " + vars[2]); - } else if (command == "rollnext") { - const auto PWINDOW = header.pWindow; - const auto PNODE = getNodeFromWindow(PWINDOW); - - if (!PNODE) - return 0; - - const auto OLDMASTER = PNODE->isMaster ? PNODE : getMasterNodeOnWorkspace(PNODE->workspaceID); - if (!OLDMASTER) - return 0; - - const auto OLDMASTERIT = std::ranges::find(m_masterNodesData, *OLDMASTER); - - for (auto& nd : m_masterNodesData) { - if (nd.workspaceID == PNODE->workspaceID && !nd.isMaster) { - nd.isMaster = true; - const auto NEWMASTERIT = std::ranges::find(m_masterNodesData, nd); - m_masterNodesData.splice(OLDMASTERIT, m_masterNodesData, NEWMASTERIT); - switchToWindow(nd.pWindow.lock()); - OLDMASTER->isMaster = false; - m_masterNodesData.splice(m_masterNodesData.end(), m_masterNodesData, OLDMASTERIT); - break; - } - } - - recalculateMonitor(PWINDOW->monitorID()); - } else if (command == "rollprev") { - const auto PWINDOW = header.pWindow; - const auto PNODE = getNodeFromWindow(PWINDOW); - - if (!PNODE) - return 0; - - const auto OLDMASTER = PNODE->isMaster ? PNODE : getMasterNodeOnWorkspace(PNODE->workspaceID); - if (!OLDMASTER) - return 0; - - const auto OLDMASTERIT = std::ranges::find(m_masterNodesData, *OLDMASTER); - - for (auto& nd : m_masterNodesData | std::views::reverse) { - if (nd.workspaceID == PNODE->workspaceID && !nd.isMaster) { - nd.isMaster = true; - const auto NEWMASTERIT = std::ranges::find(m_masterNodesData, nd); - m_masterNodesData.splice(OLDMASTERIT, m_masterNodesData, NEWMASTERIT); - switchToWindow(nd.pWindow.lock()); - OLDMASTER->isMaster = false; - m_masterNodesData.splice(m_masterNodesData.begin(), m_masterNodesData, OLDMASTERIT); - break; - } - } - - recalculateMonitor(PWINDOW->monitorID()); - } - - return 0; -} - -// If vars is null, we use the default list -void CHyprMasterLayout::runOrientationCycle(SLayoutMessageHeader& header, CVarList* vars, int direction) { - std::vector cycle; - if (vars != nullptr) - buildOrientationCycleVectorFromVars(cycle, *vars); - - if (cycle.empty()) - buildOrientationCycleVectorFromEOperation(cycle); - - const auto PWINDOW = header.pWindow; - - if (!PWINDOW) - return; - - g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); - - const auto PWORKSPACEDATA = getMasterWorkspaceData(PWINDOW->workspaceID()); - - int nextOrPrev = 0; - for (size_t i = 0; i < cycle.size(); ++i) { - if (PWORKSPACEDATA->orientation == cycle[i]) { - nextOrPrev = i + direction; - break; - } - } - - if (nextOrPrev >= sc(cycle.size())) - nextOrPrev = nextOrPrev % sc(cycle.size()); - else if (nextOrPrev < 0) - nextOrPrev = cycle.size() + (nextOrPrev % sc(cycle.size())); - - PWORKSPACEDATA->orientation = cycle.at(nextOrPrev); - recalculateMonitor(header.pWindow->monitorID()); -} - -void CHyprMasterLayout::buildOrientationCycleVectorFromEOperation(std::vector& cycle) { - for (int i = 0; i <= ORIENTATION_CENTER; ++i) { - cycle.push_back(sc(i)); - } -} - -void CHyprMasterLayout::buildOrientationCycleVectorFromVars(std::vector& cycle, CVarList& vars) { - for (size_t i = 1; i < vars.size(); ++i) { - if (vars[i] == "top") { - cycle.push_back(ORIENTATION_TOP); - } else if (vars[i] == "right") { - cycle.push_back(ORIENTATION_RIGHT); - } else if (vars[i] == "bottom") { - cycle.push_back(ORIENTATION_BOTTOM); - } else if (vars[i] == "left") { - cycle.push_back(ORIENTATION_LEFT); - } else if (vars[i] == "center") { - cycle.push_back(ORIENTATION_CENTER); - } - } -} - -eOrientation CHyprMasterLayout::getDynamicOrientation(PHLWORKSPACE pWorkspace) { - const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(pWorkspace); - std::string orientationString; - if (WORKSPACERULE.layoutopts.contains("orientation")) - orientationString = WORKSPACERULE.layoutopts.at("orientation"); - - eOrientation orientation = getMasterWorkspaceData(pWorkspace->m_id)->orientation; - // override if workspace rule is set - if (!orientationString.empty()) { - if (orientationString == "top") - orientation = ORIENTATION_TOP; - else if (orientationString == "right") - orientation = ORIENTATION_RIGHT; - else if (orientationString == "bottom") - orientation = ORIENTATION_BOTTOM; - else if (orientationString == "center") - orientation = ORIENTATION_CENTER; - else - orientation = ORIENTATION_LEFT; - } - - return orientation; -} - -void CHyprMasterLayout::replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to) { - const auto PNODE = getNodeFromWindow(from); - - if (!PNODE) - return; - - PNODE->pWindow = to; - - applyNodeDataToWindow(PNODE); -} - -Vector2D CHyprMasterLayout::predictSizeForNewWindowTiled() { - static auto PNEWSTATUS = CConfigValue("master:new_status"); - - if (!Desktop::focusState()->monitor()) - return {}; - - const int NODES = getNodesOnWorkspace(Desktop::focusState()->monitor()->m_activeWorkspace->m_id); - - if (NODES <= 0) - return Desktop::focusState()->monitor()->m_size; - - const auto MASTER = getMasterNodeOnWorkspace(Desktop::focusState()->monitor()->m_activeWorkspace->m_id); - if (!MASTER) // wtf - return {}; - - if (*PNEWSTATUS == "master") { - return MASTER->size; - } else { - const auto SLAVES = NODES - getMastersOnWorkspace(Desktop::focusState()->monitor()->m_activeWorkspace->m_id); - - // TODO: make this better - return {Desktop::focusState()->monitor()->m_size.x - MASTER->size.x, Desktop::focusState()->monitor()->m_size.y / (SLAVES + 1)}; - } - - return {}; -} - -void CHyprMasterLayout::onEnable() { - for (auto const& w : g_pCompositor->m_windows) { - if (w->m_isFloating || !w->m_isMapped || w->isHidden()) - continue; - - onWindowCreatedTiling(w); - } -} - -void CHyprMasterLayout::onDisable() { - m_masterNodesData.clear(); -} diff --git a/src/layout/MasterLayout.hpp b/src/layout/MasterLayout.hpp deleted file mode 100644 index a59689167..000000000 --- a/src/layout/MasterLayout.hpp +++ /dev/null @@ -1,112 +0,0 @@ -#pragma once - -#include "IHyprLayout.hpp" -#include "../desktop/DesktopTypes.hpp" -#include "../helpers/varlist/VarList.hpp" -#include -#include -#include - -enum eFullscreenMode : int8_t; - -//orientation determines which side of the screen the master area resides -enum eOrientation : uint8_t { - ORIENTATION_LEFT = 0, - ORIENTATION_TOP, - ORIENTATION_RIGHT, - ORIENTATION_BOTTOM, - ORIENTATION_CENTER -}; - -struct SMasterNodeData { - bool isMaster = false; - float percMaster = 0.5f; - - PHLWINDOWREF pWindow; - - Vector2D position; - Vector2D size; - - float percSize = 1.f; // size multiplier for resizing children - - WORKSPACEID workspaceID = WORKSPACE_INVALID; - - bool ignoreFullscreenChecks = false; - - // - bool operator==(const SMasterNodeData& rhs) const { - return pWindow.lock() == rhs.pWindow.lock(); - } -}; - -struct SMasterWorkspaceData { - WORKSPACEID workspaceID = WORKSPACE_INVALID; - eOrientation orientation = ORIENTATION_LEFT; - // Previously focused non-master window when `focusmaster previous` command was issued - PHLWINDOWREF focusMasterPrev; - - // - bool operator==(const SMasterWorkspaceData& rhs) const { - return workspaceID == rhs.workspaceID; - } -}; - -class CHyprMasterLayout : public IHyprLayout { - public: - virtual void onWindowCreatedTiling(PHLWINDOW, eDirection direction = DIRECTION_DEFAULT); - virtual void onWindowRemovedTiling(PHLWINDOW); - virtual bool isWindowTiled(PHLWINDOW); - virtual void recalculateMonitor(const MONITORID&); - virtual void recalculateWindow(PHLWINDOW); - virtual void resizeActiveWindow(const Vector2D&, eRectCorner corner = CORNER_NONE, PHLWINDOW pWindow = nullptr); - virtual void fullscreenRequestForWindow(PHLWINDOW pWindow, const eFullscreenMode CURRENT_EFFECTIVE_MODE, const eFullscreenMode EFFECTIVE_MODE); - virtual std::any layoutMessage(SLayoutMessageHeader, std::string); - virtual SWindowRenderLayoutHints requestRenderHints(PHLWINDOW); - virtual void switchWindows(PHLWINDOW, PHLWINDOW); - virtual void moveWindowTo(PHLWINDOW, const std::string& dir, bool silent); - virtual void alterSplitRatio(PHLWINDOW, float, bool); - virtual std::string getLayoutName(); - virtual void replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to); - virtual Vector2D predictSizeForNewWindowTiled(); - - virtual void onEnable(); - virtual void onDisable(); - - private: - std::list m_masterNodesData; - std::vector m_masterWorkspacesData; - - bool m_forceWarps = false; - - void buildOrientationCycleVectorFromVars(std::vector& cycle, CVarList& vars); - void buildOrientationCycleVectorFromEOperation(std::vector& cycle); - void runOrientationCycle(SLayoutMessageHeader& header, CVarList* vars, int next); - eOrientation getDynamicOrientation(PHLWORKSPACE); - int getNodesOnWorkspace(const WORKSPACEID&); - void applyNodeDataToWindow(SMasterNodeData*); - SMasterNodeData* getNodeFromWindow(PHLWINDOW); - SMasterNodeData* getMasterNodeOnWorkspace(const WORKSPACEID&); - SMasterWorkspaceData* getMasterWorkspaceData(const WORKSPACEID&); - void calculateWorkspace(PHLWORKSPACE); - PHLWINDOW getNextWindow(PHLWINDOW, bool, bool); - int getMastersOnWorkspace(const WORKSPACEID&); - - friend struct SMasterNodeData; - friend struct SMasterWorkspaceData; -}; - -template -struct std::formatter : std::formatter { - template - auto format(const SMasterNodeData* const& node, FormatContext& ctx) const { - auto out = ctx.out(); - if (!node) - return std::format_to(out, "[Node nullptr]"); - std::format_to(out, "[Node {:x}: workspace: {}, pos: {:j2}, size: {:j2}", rc(node), node->workspaceID, node->position, node->size); - if (node->isMaster) - std::format_to(out, ", master"); - if (!node->pWindow.expired()) - std::format_to(out, ", window: {:x}", node->pWindow.lock()); - return std::format_to(out, "]"); - } -}; diff --git a/src/layout/algorithm/Algorithm.cpp b/src/layout/algorithm/Algorithm.cpp new file mode 100644 index 000000000..eb737d34a --- /dev/null +++ b/src/layout/algorithm/Algorithm.cpp @@ -0,0 +1,289 @@ +#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 "../../desktop/state/FocusState.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(); + if (!PWORKSPACE) + return; + + 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; + + const auto FOCUSED_WINDOW = Desktop::focusState()->window(); + const auto FOCUSED_TARGET = FOCUSED_WINDOW ? FOCUSED_WINDOW->layoutTarget() : nullptr; + + 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); + } + + if (FOCUSED_TARGET && FOCUSED_TARGET->space() == m_space && FOCUSED_TARGET->floating()) + Desktop::focusState()->fullWindowFocus(FOCUSED_WINDOW, Desktop::eFocusReason::FOCUS_REASON_DESKTOP_STATE_CHANGE); + + m_floating = std::move(algo); +} + +void CAlgorithm::updateTiledAlgo(UP&& algo) { + algo->m_parent = m_self; + + const auto FOCUSED_WINDOW = Desktop::focusState()->window(); + const auto FOCUSED_TARGET = FOCUSED_WINDOW ? FOCUSED_WINDOW->layoutTarget() : nullptr; + + 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); + } + + if (FOCUSED_TARGET && FOCUSED_TARGET->space() == m_space && !FOCUSED_TARGET->floating()) + Desktop::focusState()->fullWindowFocus(FOCUSED_WINDOW, Desktop::eFocusReason::FOCUS_REASON_DESKTOP_STATE_CHANGE); + + 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) { + static auto FOCUSONCLOSE = CConfigValue("input:focus_on_close"); + + if (old->floating() || *FOCUSONCLOSE == 2) { + // use window history to determine best target + for (const auto& w : Desktop::History::windowTracker()->fullHistory() | std::views::reverse) { + if (!w->m_workspace || w->m_workspace->m_space != m_space || !w->layoutTarget() || !w->layoutTarget()->space()) + continue; + + return w->layoutTarget(); + } + + // no history, fall back + } else { + // ask the layout + const auto CANDIDATE = m_tiled->getNextCandidate(old); + if (CANDIDATE) + return CANDIDATE; + + // no candidate, fall back + } + + // fallback: try to focus anything + if (!m_tiledTargets.empty()) + return m_tiledTargets.back().lock(); + if (!m_floatingTargets.empty()) + return m_floatingTargets.back().lock(); + + // god damn it, maybe empty? + return nullptr; +} + +void CAlgorithm::setTargetGeom(const CBox& box, SP target) { + if (!target->floating() || !std::ranges::contains(m_floatingTargets, target)) + return; + + m_floating->setTargetGeom(box, target); +} diff --git a/src/layout/algorithm/Algorithm.hpp b/src/layout/algorithm/Algorithm.hpp new file mode 100644 index 000000000..7df6c5c18 --- /dev/null +++ b/src/layout/algorithm/Algorithm.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include "../../helpers/math/Math.hpp" +#include "../../helpers/math/Direction.hpp" +#include "../../helpers/memory/Memory.hpp" + +#include "../LayoutManager.hpp" + +#include +#include + +namespace Layout { + class ITarget; + class IFloatingAlgorithm; + class ITiledAlgorithm; + class CSpace; + + class CAlgorithm { + public: + static SP create(UP&& tiled, UP&& floating, SP space); + ~CAlgorithm() = default; + + void addTarget(SP target); + void moveTarget(SP target, std::optional focalPoint = std::nullopt, bool reposition = false); + void removeTarget(SP target); + + void swapTargets(SP a, SP b); + void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + SP getNextCandidate(SP old); + + void setFloating(SP target, bool floating, bool reposition = false); + + std::expected layoutMsg(const std::string_view& sv); + std::optional predictSizeForNewTiledTarget(); + + void recalculate(); + void recenter(SP t); + + void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + void moveTarget(const Vector2D& Δ, SP target); + + void setTargetGeom(const CBox& box, SP target); // only for float + + void updateFloatingAlgo(UP&& algo); + void updateTiledAlgo(UP&& algo); + + const UP& tiledAlgo() const; + const UP& floatingAlgo() const; + + SP space() const; + + size_t tiledTargets() const; + size_t floatingTargets() const; + + private: + CAlgorithm(UP&& tiled, UP&& floating, SP space); + + UP m_tiled; + UP m_floating; + WP m_space; + WP m_self; + + std::vector> m_tiledTargets, m_floatingTargets; + }; +} \ No newline at end of file diff --git a/src/layout/algorithm/FloatingAlgorithm.cpp b/src/layout/algorithm/FloatingAlgorithm.cpp new file mode 100644 index 000000000..058887bf0 --- /dev/null +++ b/src/layout/algorithm/FloatingAlgorithm.cpp @@ -0,0 +1,18 @@ +#include "FloatingAlgorithm.hpp" +#include "Algorithm.hpp" +#include "../space/Space.hpp" + +using namespace Layout; + +void IFloatingAlgorithm::recalculate() { + ; +} + +void IFloatingAlgorithm::recenter(SP t) { + const auto LAST = t->lastFloatingSize(); + + if (LAST.x <= 5 || LAST.y <= 5) + return; + + t->setPositionGlobal({m_parent->space()->workArea().middle() - LAST / 2.F, LAST}); +} diff --git a/src/layout/algorithm/FloatingAlgorithm.hpp b/src/layout/algorithm/FloatingAlgorithm.hpp new file mode 100644 index 000000000..40e530343 --- /dev/null +++ b/src/layout/algorithm/FloatingAlgorithm.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "../../helpers/math/Math.hpp" +#include "../../helpers/memory/Memory.hpp" + +#include "ModeAlgorithm.hpp" + +namespace Layout { + + class ITarget; + class CAlgorithm; + + class IFloatingAlgorithm : public IModeAlgorithm { + public: + virtual ~IFloatingAlgorithm() = default; + + // a target is being moved by a delta + virtual void moveTarget(const Vector2D& Δ, SP target) = 0; + + // a target is moved to a pos x size + virtual void setTargetGeom(const CBox& geom, SP target) = 0; + + virtual void recenter(SP t); + + virtual void recalculate(); + + protected: + IFloatingAlgorithm() = default; + + WP m_parent; + + friend class Layout::CAlgorithm; + }; +} \ No newline at end of file diff --git a/src/layout/algorithm/ModeAlgorithm.cpp b/src/layout/algorithm/ModeAlgorithm.cpp new file mode 100644 index 000000000..2fea2b681 --- /dev/null +++ b/src/layout/algorithm/ModeAlgorithm.cpp @@ -0,0 +1,46 @@ +#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 getFullscreenBB = [&]() -> std::optional { + const auto PARENT = m_parent.lock(); + if (!PARENT) + return std::nullopt; + const auto SPACE = PARENT->space(); + if (!SPACE) + return std::nullopt; + const auto WS = SPACE->workspace(); + if (!WS || !WS->m_monitor) + return std::nullopt; + return WS->m_monitor->logicalBox(); + }; + + const auto WINDOWIDEALBB = t->fullscreenMode() != FSMODE_NONE ? getFullscreenBB().value_or(t->window()->getWindowIdealBoundingBoxIgnoreReserved()) : + t->window()->getWindowIdealBoundingBoxIgnoreReserved(); + + switch (dir) { + case Math::DIRECTION_UP: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x / 2.0, -1.0}; break; + case Math::DIRECTION_DOWN: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x / 2.0, WINDOWIDEALBB.size().y + 1.0}; break; + case Math::DIRECTION_LEFT: focalPoint = WINDOWIDEALBB.pos() + Vector2D{-1.0, WINDOWIDEALBB.size().y / 2.0}; break; + case Math::DIRECTION_RIGHT: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x + 1.0, WINDOWIDEALBB.size().y / 2.0}; break; + default: return std::nullopt; + } + + return focalPoint; +} diff --git a/src/layout/algorithm/ModeAlgorithm.hpp b/src/layout/algorithm/ModeAlgorithm.hpp new file mode 100644 index 000000000..0fedc3da2 --- /dev/null +++ b/src/layout/algorithm/ModeAlgorithm.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include "../../helpers/math/Math.hpp" +#include "../../helpers/math/Direction.hpp" +#include "../../helpers/memory/Memory.hpp" + +#include "../LayoutManager.hpp" + +#include + +namespace Layout { + + class ITarget; + class CAlgorithm; + + class IModeAlgorithm { + public: + virtual ~IModeAlgorithm() = default; + + // a completely new target + virtual void newTarget(SP target) = 0; + + // a target moved into the algorithm (from another) + virtual void movedTarget(SP target, std::optional focalPoint = std::nullopt) = 0; + + // a target removed + virtual void removeTarget(SP target) = 0; + + // a target is being resized by a delta. Corner none likely means not interactive + virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE) = 0; + + // recalculate layout + virtual void recalculate() = 0; + + // swap targets + virtual void swapTargets(SP a, SP b) = 0; + + // move a target in a given direction + virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent) = 0; + + // optional: handle layout messages + virtual std::expected layoutMsg(const std::string_view& sv); + + // optional: predict new window's size + virtual std::optional predictSizeForNewTarget(); + + // Impl'd here: focal point for dir + virtual std::optional focalPointForDir(SP t, Math::eDirection dir); + + protected: + IModeAlgorithm() = default; + + WP m_parent; + + friend class Layout::CAlgorithm; + }; +} \ No newline at end of file diff --git a/src/layout/algorithm/TiledAlgorithm.hpp b/src/layout/algorithm/TiledAlgorithm.hpp new file mode 100644 index 000000000..99d1bd993 --- /dev/null +++ b/src/layout/algorithm/TiledAlgorithm.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "../../helpers/math/Math.hpp" +#include "../../helpers/memory/Memory.hpp" + +#include "ModeAlgorithm.hpp" + +namespace Layout { + + class ITarget; + class CAlgorithm; + + class ITiledAlgorithm : public IModeAlgorithm { + public: + virtual ~ITiledAlgorithm() = default; + + virtual SP getNextCandidate(SP old) = 0; + + protected: + ITiledAlgorithm() = default; + + WP m_parent; + + friend class Layout::CAlgorithm; + }; +} \ No newline at end of file diff --git a/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp b/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp new file mode 100644 index 000000000..0d069e4f3 --- /dev/null +++ b/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp @@ -0,0 +1,252 @@ +#include "DefaultFloatingAlgorithm.hpp" + +#include "../../Algorithm.hpp" + +#include "../../../target/WindowTarget.hpp" +#include "../../../space/Space.hpp" + +#include "../../../../Compositor.hpp" +#include "../../../../helpers/Monitor.hpp" + +using namespace Layout; +using namespace Layout::Floating; + +constexpr const Vector2D DEFAULT_SIZE = {640, 400}; + +// +void CDefaultFloatingAlgorithm::newTarget(SP target) { + const auto WORK_AREA = m_parent->space()->workArea(true); + const auto DESIRED_GEOM = target->desiredGeometry(); + const auto MONITOR_POS = m_parent->space()->workspace()->m_monitor->logicalBox().pos(); + + CBox windowGeometry; + + if (!DESIRED_GEOM) { + switch (DESIRED_GEOM.error()) { + case GEOMETRY_INVALID_DESIRED: { + // if the desired is invalid, we hide the window. + if (target->type() == TARGET_TYPE_WINDOW) + dynamicPointerCast(target)->window()->setHidden(true); + return; + } + case GEOMETRY_NO_DESIRED: { + // add a default geom + windowGeometry = CBox{WORK_AREA.middle() - DEFAULT_SIZE / 2.F, DEFAULT_SIZE}; + break; + } + } + } else { + if (DESIRED_GEOM->pos) + windowGeometry = CBox{DESIRED_GEOM->pos.value(), DESIRED_GEOM->size}; + else + windowGeometry = CBox{WORK_AREA.middle() - DESIRED_GEOM->size / 2.F, DESIRED_GEOM->size}; + } + + bool posOverridden = false; + + if (target->window() && target->window()->m_firstMap) { + const auto WINDOW = target->window(); + + // set this here so that expressions can use it. This could be wrong of course. + WINDOW->m_realSize->setValueAndWarp(DESIRED_GEOM ? DESIRED_GEOM->size : DEFAULT_SIZE); + + if (!WINDOW->m_ruleApplicator->static_.size.empty()) { + const auto COMPUTED = WINDOW->calculateExpression(WINDOW->m_ruleApplicator->static_.size); + if (!COMPUTED) + Log::logger->log(Log::ERR, "failed to parse {} as an expression", WINDOW->m_ruleApplicator->static_.size); + else { + windowGeometry.w = COMPUTED->x; + windowGeometry.h = COMPUTED->y; + + // update for pos to work with size. + WINDOW->m_realPosition->setValueAndWarp(*COMPUTED); + } + } + + if (!WINDOW->m_ruleApplicator->static_.position.empty()) { + const auto COMPUTED = WINDOW->calculateExpression(WINDOW->m_ruleApplicator->static_.position); + if (!COMPUTED) + Log::logger->log(Log::ERR, "failed to parse {} as an expression", WINDOW->m_ruleApplicator->static_.position); + else { + windowGeometry.x = COMPUTED->x + MONITOR_POS.x; + windowGeometry.y = COMPUTED->y + MONITOR_POS.y; + posOverridden = true; + } + } + + if (WINDOW->m_ruleApplicator->static_.center.value_or(false)) { + const auto POS = WORK_AREA.middle() - windowGeometry.size() / 2.f; + windowGeometry.x = POS.x; + windowGeometry.y = POS.y; + posOverridden = true; + } + } else if (target->lastFloatingSize().x > 5 && target->lastFloatingSize().y > 5) { + windowGeometry.w = target->lastFloatingSize().x; + windowGeometry.h = target->lastFloatingSize().y; + } + + if (!posOverridden && (!DESIRED_GEOM || !DESIRED_GEOM->pos)) + windowGeometry = CBox{WORK_AREA.middle() - windowGeometry.size() / 2.F, windowGeometry.size()}; + + if (posOverridden // pos is overridden by a rule + || (DESIRED_GEOM && DESIRED_GEOM->pos && target->window() && target->window()->m_isX11) // X11 window with a geom + || WORK_AREA.containsPoint(windowGeometry.middle())) // geometry within work area + target->setPositionGlobal(windowGeometry); + else { + const auto POS = WORK_AREA.middle() - windowGeometry.size() / 2.f; + windowGeometry.x = POS.x; + windowGeometry.y = POS.y; + + target->setPositionGlobal(windowGeometry); + } + + // TODO: not very OOP, is it? + if (const auto WTARGET = dynamicPointerCast(target); WTARGET) { + const auto PWINDOW = WTARGET->window(); + + if (PWINDOW->m_X11DoesntWantBorders || (PWINDOW->m_isX11 && PWINDOW->isX11OverrideRedirect())) { + PWINDOW->m_realPosition->warp(); + PWINDOW->m_realSize->warp(); + } + + if (!PWINDOW->isX11OverrideRedirect()) + g_pCompositor->changeWindowZOrder(PWINDOW, true); + else { + PWINDOW->m_pendingReportedSize = PWINDOW->m_realSize->goal(); + PWINDOW->m_reportedSize = PWINDOW->m_pendingReportedSize; + } + } + + updateTarget(target); +} + +void CDefaultFloatingAlgorithm::movedTarget(SP target, std::optional focalPoint) { + auto LAST_SIZE = target->lastFloatingSize(); + const auto CURRENT_SIZE = target->position().size(); + + // ignore positioning a dragged target + if (g_layoutManager->dragController()->target() == target) + return; + + if (LAST_SIZE.x < 5 || LAST_SIZE.y < 5) { + const auto DESIRED = target->desiredGeometry(); + LAST_SIZE = DESIRED ? DESIRED->size : DEFAULT_SIZE; + } + + if (target->wasTiling()) { + // Avoid floating toggles that don't change size, they aren't easily visible to the user + if (std::abs(LAST_SIZE.x - CURRENT_SIZE.x) < 5 && std::abs(LAST_SIZE.y - CURRENT_SIZE.y) < 5) + LAST_SIZE += Vector2D{10, 10}; + + // calculate new position + const auto OLD_CENTER = target->position().middle(); + + // put around the current center, fit in workArea + target->setPositionGlobal(fitBoxInWorkArea(CBox{OLD_CENTER - LAST_SIZE / 2.F, LAST_SIZE}, target)); + + } else { + // calculate new position + const auto THIS_MON_POS = m_parent->space()->workspace()->m_monitor->m_position; + const auto OLD_POS = target->position().pos(); + const auto MON_FROM_OLD = g_pCompositor->getMonitorFromVector(OLD_POS); + const auto NEW_POS = MON_FROM_OLD ? OLD_POS - MON_FROM_OLD->m_position + THIS_MON_POS : OLD_POS; + + // put around the current center, fit in workArea + target->setPositionGlobal(fitBoxInWorkArea(CBox{NEW_POS, LAST_SIZE}, target)); + } + + updateTarget(target); +} + +CBox CDefaultFloatingAlgorithm::fitBoxInWorkArea(const CBox& box, SP t) { + const auto WORK_AREA = m_parent->space()->workArea(true); + const auto EXTENTS = t->window() ? t->window()->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS) : SBoxExtents{}; + CBox targetBox = box.copy().addExtents(EXTENTS); + + targetBox.x = std::max(targetBox.x, WORK_AREA.x); + targetBox.y = std::max(targetBox.y, WORK_AREA.y); + + if (targetBox.x + targetBox.w > WORK_AREA.x + WORK_AREA.w) + targetBox.x = WORK_AREA.x + WORK_AREA.w - targetBox.w; + + if (targetBox.y + targetBox.h > WORK_AREA.y + WORK_AREA.h) + targetBox.y = WORK_AREA.y + WORK_AREA.h - targetBox.h; + + return targetBox.addExtents(SBoxExtents{.topLeft = -EXTENTS.topLeft, .bottomRight = -EXTENTS.bottomRight}); +} + +void CDefaultFloatingAlgorithm::removeTarget(SP target) { + target->rememberFloatingSize(target->position().size()); + m_datas.erase(target); +} + +void CDefaultFloatingAlgorithm::resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner) { + auto pos = target->position(); + pos.w += Δ.x; + pos.h += Δ.y; + pos.translate(-Δ / 2.F); + target->setPositionGlobal(pos); + + if (g_layoutManager->dragController()->target() == target) + target->warpPositionSize(); + + updateTarget(target); +} + +void CDefaultFloatingAlgorithm::moveTarget(const Vector2D& Δ, SP target) { + auto pos = target->position(); + pos.translate(Δ); + target->setPositionGlobal(pos); + + if (g_layoutManager->dragController()->target() == target) + target->warpPositionSize(); + + updateTarget(target); +} + +void CDefaultFloatingAlgorithm::swapTargets(SP a, SP b) { + auto posABackup = a->position(); + a->setPositionGlobal(b->position()); + b->setPositionGlobal(posABackup); + + updateTarget(a); + updateTarget(b); +} + +void CDefaultFloatingAlgorithm::moveTargetInDirection(SP t, Math::eDirection dir, bool silent) { + auto pos = t->position(); + auto work = m_parent->space()->workArea(true); + + const auto EXTENTS = t->window() ? t->window()->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS) : SBoxExtents{}; + + switch (dir) { + case Math::DIRECTION_LEFT: pos.x = work.x + EXTENTS.topLeft.x; break; + case Math::DIRECTION_RIGHT: pos.x = work.x + work.w - pos.w - EXTENTS.bottomRight.x; break; + case Math::DIRECTION_UP: pos.y = work.y + EXTENTS.topLeft.y; break; + case Math::DIRECTION_DOWN: pos.y = work.y + work.h - pos.h - EXTENTS.bottomRight.y; break; + default: Log::logger->log(Log::ERR, "Invalid direction in CDefaultFloatingAlgorithm::moveTargetInDirection"); break; + } + + t->setPositionGlobal(pos); + + updateTarget(t); +} + +void CDefaultFloatingAlgorithm::recenter(SP t) { + if (!m_datas.contains(t)) { + IFloatingAlgorithm::recenter(t); + return; + } + + t->setPositionGlobal(m_datas.at(t).lastBox); +} + +void CDefaultFloatingAlgorithm::setTargetGeom(const CBox& geom, SP target) { + target->setPositionGlobal(geom); + + updateTarget(target); +} + +void CDefaultFloatingAlgorithm::updateTarget(SP t) { + m_datas[t] = {.lastBox = t->position()}; +} diff --git a/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.hpp b/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.hpp new file mode 100644 index 000000000..1e87fac16 --- /dev/null +++ b/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.hpp @@ -0,0 +1,40 @@ +#include "../../FloatingAlgorithm.hpp" + +#include + +namespace Layout { + class CAlgorithm; +} + +namespace Layout::Floating { + class CDefaultFloatingAlgorithm : public IFloatingAlgorithm { + public: + CDefaultFloatingAlgorithm() = default; + virtual ~CDefaultFloatingAlgorithm() = default; + + virtual void newTarget(SP target); + virtual void movedTarget(SP target, std::optional focalPoint = std::nullopt); + virtual void removeTarget(SP target); + + virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + virtual void moveTarget(const Vector2D& Δ, SP target); + + virtual void setTargetGeom(const CBox& geom, SP target); + + virtual void swapTargets(SP a, SP b); + virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + virtual void recenter(SP t); + + private: + CBox fitBoxInWorkArea(const CBox& box, SP t); + + void updateTarget(SP); + + struct SWindowData { + CBox lastBox; + }; + + std::map, SWindowData> m_datas; + }; +}; \ No newline at end of file diff --git a/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp new file mode 100644 index 000000000..baf767be0 --- /dev/null +++ b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp @@ -0,0 +1,849 @@ +#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 +#include + +using namespace Layout; +using namespace Layout::Tiled; + +using namespace Hyprutils::String; + +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"); + static auto PPRECISEMOUSEMOVE = CConfigValue("dwindle:precise_mouse_move"); + + if (*PPRESERVESPLIT == 0 && *PSMARTSPLIT == 0 && *PPRECISEMOUSEMOVE == 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) { + 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) { + auto locked = node->pTarget.lock(); + if (locked && locked != 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"); + static auto PPRECISEMOUSEMOVE = CConfigValue("dwindle:precise_mouse_move"); + + 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 || (*PPRECISEMOUSEMOVE == 1 && g_layoutManager->dragController()->wasDraggingWindow())) { + 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 || m_overrideFocalPoint || g_layoutManager->dragController()->wasDraggingWindow()) { + 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); + m_overrideFocalPoint.reset(); +} + +void CDwindleAlgorithm::removeTarget(SP target) { + const auto PNODE = getNodeFromTarget(target); + + if (!PNODE) { + Log::logger->log(Log::ERR, "onWindowRemovedTiling node null?"); + return; + } + + if (target->fullscreenMode() != FSMODE_NONE) + g_pCompositor->setWindowFullscreenInternal(target->window(), FSMODE_NONE); + + const auto PPARENT = PNODE->pParent; + + if (!PPARENT) { + Log::logger->log(Log::DEBUG, "Removing last node (dwindle)"); + std::erase(m_dwindleNodesData, PNODE); + return; + } + + const auto PSIBLING = PPARENT->children[0] == PNODE ? PPARENT->children[1] : PPARENT->children[0]; + + PSIBLING->pParent = PPARENT->pParent; + + if (PPARENT->pParent != nullptr) { + if (PPARENT->pParent->children[0] == PPARENT) + PPARENT->pParent->children[0] = PSIBLING; + else + PPARENT->pParent->children[1] = PSIBLING; + } + + PPARENT->valid = false; + PNODE->valid = false; + + std::erase(m_dwindleNodesData, PPARENT); + std::erase(m_dwindleNodesData, PNODE); + + recalculate(); +} + +void CDwindleAlgorithm::resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner) { + if (!validMapped(target->window())) + return; + + const auto PNODE = getNodeFromTarget(target); + + if (!PNODE) + return; + + static auto PANIMATE = CConfigValue("misc:animate_manual_resizes"); + static auto PSMARTRESIZING = CConfigValue("dwindle:smart_resizing"); + + // get some data about our window + const auto PMONITOR = m_parent->space()->workspace()->m_monitor; + const auto MONITOR_WORKAREA = PMONITOR->logicalBoxMinusReserved(); + const auto BOX = target->position(); + const bool DISPLAYLEFT = STICKS(BOX.x, MONITOR_WORKAREA.x); + const bool DISPLAYRIGHT = STICKS(BOX.x + BOX.w, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w); + const bool DISPLAYTOP = STICKS(BOX.y, MONITOR_WORKAREA.y); + const bool DISPLAYBOTTOM = STICKS(BOX.y + BOX.h, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h); + + // construct allowed movement + Vector2D allowedMovement = Δ; + if (DISPLAYLEFT && DISPLAYRIGHT) + allowedMovement.x = 0; + + if (DISPLAYBOTTOM && DISPLAYTOP) + allowedMovement.y = 0; + + if (*PSMARTRESIZING == 1) { + // Identify inner and outer nodes for both directions + SP PVOUTER = nullptr; + SP PVINNER = nullptr; + SP PHOUTER = nullptr; + SP PHINNER = nullptr; + + const auto LEFT = corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT || DISPLAYRIGHT; + const auto TOP = corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT || DISPLAYBOTTOM; + const auto RIGHT = corner == CORNER_TOPRIGHT || corner == CORNER_BOTTOMRIGHT || DISPLAYLEFT; + const auto BOTTOM = corner == CORNER_BOTTOMLEFT || corner == CORNER_BOTTOMRIGHT || DISPLAYTOP; + const auto NONE = corner == CORNER_NONE; + + for (auto PCURRENT = PNODE; PCURRENT && PCURRENT->pParent; PCURRENT = PCURRENT->pParent.lock()) { + const auto PPARENT = PCURRENT->pParent; + + if (!PVOUTER && PPARENT->splitTop && (NONE || (TOP && PPARENT->children[1] == PCURRENT) || (BOTTOM && PPARENT->children[0] == PCURRENT))) + PVOUTER = PCURRENT; + else if (!PVOUTER && !PVINNER && PPARENT->splitTop) + PVINNER = PCURRENT; + else if (!PHOUTER && !PPARENT->splitTop && (NONE || (LEFT && PPARENT->children[1] == PCURRENT) || (RIGHT && PPARENT->children[0] == PCURRENT))) + PHOUTER = PCURRENT; + else if (!PHOUTER && !PHINNER && !PPARENT->splitTop) + PHINNER = PCURRENT; + + if (PVOUTER && PHOUTER) + break; + } + + if (PHOUTER) { + PHOUTER->pParent->splitRatio = std::clamp(PHOUTER->pParent->splitRatio + allowedMovement.x * 2.f / PHOUTER->pParent->box.w, 0.1, 1.9); + + if (PHINNER) { + const auto ORIGINAL = PHINNER->box.w; + PHOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); + if (PHINNER->pParent->children[0] == PHINNER) + PHINNER->pParent->splitRatio = std::clamp((ORIGINAL - allowedMovement.x) / PHINNER->pParent->box.w * 2.f, 0.1, 1.9); + else + PHINNER->pParent->splitRatio = std::clamp(2 - (ORIGINAL + allowedMovement.x) / PHINNER->pParent->box.w * 2.f, 0.1, 1.9); + PHINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0); + } else + PHOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); + } + + if (PVOUTER) { + PVOUTER->pParent->splitRatio = std::clamp(PVOUTER->pParent->splitRatio + allowedMovement.y * 2.f / PVOUTER->pParent->box.h, 0.1, 1.9); + + if (PVINNER) { + const auto ORIGINAL = PVINNER->box.h; + PVOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); + if (PVINNER->pParent->children[0] == PVINNER) + PVINNER->pParent->splitRatio = std::clamp((ORIGINAL - allowedMovement.y) / PVINNER->pParent->box.h * 2.f, 0.1, 1.9); + else + PVINNER->pParent->splitRatio = std::clamp(2 - (ORIGINAL + allowedMovement.y) / PVINNER->pParent->box.h * 2.f, 0.1, 1.9); + PVINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0); + } else + PVOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); + } + } else { + // get the correct containers to apply splitratio to + const auto PPARENT = PNODE->pParent; + + if (!PPARENT) + return; // the only window on a workspace, ignore + + const bool PARENTSIDEBYSIDE = !PPARENT->splitTop; + + // Get the parent's parent + auto PPARENT2 = PPARENT->pParent; + + Hyprutils::Utils::CScopeGuard x([target, this] { + // snap all windows, don't animate resizes if they are manual + if (target == g_layoutManager->dragController()->target()) { + for (const auto& w : m_dwindleNodesData) { + if (w->isNode) + continue; + + w->pTarget->warpPositionSize(); + } + } + }); + + // No parent means we have only 2 windows, and thus one axis of freedom + if (!PPARENT2) { + if (PARENTSIDEBYSIDE) { + allowedMovement.x *= 2.f / PPARENT->box.w; + PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.x, 0.1, 1.9); + PPARENT->recalcSizePosRecursive(*PANIMATE == 0); + } else { + allowedMovement.y *= 2.f / PPARENT->box.h; + PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.y, 0.1, 1.9); + PPARENT->recalcSizePosRecursive(*PANIMATE == 0); + } + + return; + } + + // Get first parent with other split + while (PPARENT2 && PPARENT2->splitTop == !PARENTSIDEBYSIDE) + PPARENT2 = PPARENT2->pParent; + + // no parent, one axis of freedom + if (!PPARENT2) { + if (PARENTSIDEBYSIDE) { + allowedMovement.x *= 2.f / PPARENT->box.w; + PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.x, 0.1, 1.9); + PPARENT->recalcSizePosRecursive(*PANIMATE == 0); + } else { + allowedMovement.y *= 2.f / PPARENT->box.h; + PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.y, 0.1, 1.9); + PPARENT->recalcSizePosRecursive(*PANIMATE == 0); + } + + return; + } + + // 2 axes of freedom + const auto SIDECONTAINER = PARENTSIDEBYSIDE ? PPARENT : PPARENT2; + const auto TOPCONTAINER = PARENTSIDEBYSIDE ? PPARENT2 : PPARENT; + + allowedMovement.x *= 2.f / SIDECONTAINER->box.w; + allowedMovement.y *= 2.f / TOPCONTAINER->box.h; + + SIDECONTAINER->splitRatio = std::clamp(SIDECONTAINER->splitRatio + allowedMovement.x, 0.1, 1.9); + TOPCONTAINER->splitRatio = std::clamp(TOPCONTAINER->splitRatio + allowedMovement.y, 0.1, 1.9); + SIDECONTAINER->recalcSizePosRecursive(*PANIMATE == 0); + TOPCONTAINER->recalcSizePosRecursive(*PANIMATE == 0); + } + + // snap all windows, don't animate resizes if they are manual + if (target == g_layoutManager->dragController()->target()) { + for (const auto& w : m_dwindleNodesData) { + if (w->isNode) + continue; + + w->pTarget->warpPositionSize(); + } + } +} + +SP CDwindleAlgorithm::getNextCandidate(SP old) { + const auto MIDDLE = old->position().middle(); + + if (const auto NODE = getClosestNode(MIDDLE); NODE) + return NODE->pTarget.lock(); + + if (const auto NODE = getFirstNode(); NODE) + return NODE->pTarget.lock(); + + return nullptr; +} + +void CDwindleAlgorithm::swapTargets(SP a, SP b) { + auto nodeA = getNodeFromTarget(a); + auto nodeB = getNodeFromTarget(b); + + if (nodeA) + nodeA->pTarget = b; + if (nodeB) + nodeB->pTarget = a; +} + +void CDwindleAlgorithm::recalculate() { + calculateWorkspace(); +} + +std::optional CDwindleAlgorithm::predictSizeForNewTarget() { + // get window candidate + PHLWINDOW candidate = Desktop::focusState()->window(); + + if (!candidate || candidate->m_workspace != m_parent->space()->workspace()) + candidate = m_parent->space()->workspace()->getFirstWindow(); + + // create a fake node + SDwindleNodeData node; + + if (!candidate) + return Desktop::focusState()->monitor()->m_size; + else { + const auto PNODE = getNodeFromWindow(candidate); + + if (!PNODE) + return {}; + + node = *PNODE; + node.pTarget.reset(); + + CBox box = PNODE->box; + + static auto PFLMULT = CConfigValue("dwindle:split_width_multiplier"); + + bool splitTop = box.h * *PFLMULT > box.w; + + const auto SPLITSIDE = !splitTop; + + if (SPLITSIDE) + node.box = {{}, {box.w / 2.0, box.h}}; + else + node.box = {{}, {box.w, box.h / 2.0}}; + + // TODO: make this better and more accurate + + return node.box.size(); + } + + return {}; +} + +void CDwindleAlgorithm::moveTargetInDirection(SP t, Math::eDirection dir, bool silent) { + static auto PMONITORFALLBACK = CConfigValue("binds:window_direction_monitor_fallback"); + + const auto PNODE = getNodeFromTarget(t); + const Vector2D originalPos = t->position().middle(); + + if (!PNODE || !t->window()) + return; + + const auto FOCAL_POINT = focalPointForDir(t, dir); + + const auto PMONITORFOCAL = g_pCompositor->getMonitorFromVector(FOCAL_POINT.value_or(t->position().middle())); + + if (PMONITORFOCAL != m_parent->space()->workspace()->m_monitor && !*PMONITORFALLBACK) + return; // noop + + t->window()->setAnimationsToMove(); + + removeTarget(t); + + if (PMONITORFOCAL != m_parent->space()->workspace()->m_monitor) { + // move with a focal point + + if (PMONITORFOCAL->m_activeWorkspace) + t->assignToSpace(PMONITORFOCAL->m_activeWorkspace->m_space, FOCAL_POINT); + + return; + } + + movedTarget(t, FOCAL_POINT); + + // restore focus to the previous position + if (silent) { + const auto PNODETOFOCUS = getClosestNode(originalPos); + if (PNODETOFOCUS && PNODETOFOCUS->pTarget) + Desktop::focusState()->fullWindowFocus(PNODETOFOCUS->pTarget->window(), Desktop::FOCUS_REASON_KEYBIND); + } +} + +// --------- internal --------- // + +void CDwindleAlgorithm::calculateWorkspace() { + const auto PWORKSPACE = m_parent->space()->workspace(); + const auto PMONITOR = PWORKSPACE->m_monitor; + + if (!PMONITOR || PWORKSPACE->m_hasFullscreenWindow) + return; + + const auto TOPNODE = getMasterNode(); + + if (TOPNODE) { + TOPNODE->box = m_parent->space()->workArea(); + TOPNODE->recalcSizePosRecursive(); + } +} + +SP CDwindleAlgorithm::getNodeFromTarget(SP t) { + for (const auto& n : m_dwindleNodesData) { + if (n->pTarget == t) + return n; + } + + return nullptr; +} + +SP CDwindleAlgorithm::getNodeFromWindow(PHLWINDOW w) { + return w ? getNodeFromTarget(w->layoutTarget()) : nullptr; +} + +int CDwindleAlgorithm::getNodes() { + return m_dwindleNodesData.size(); +} + +SP CDwindleAlgorithm::getFirstNode() { + return m_dwindleNodesData.empty() ? nullptr : m_dwindleNodesData.at(0); +} + +SP CDwindleAlgorithm::getClosestNode(const Vector2D& point, SP skip) { + SP res = nullptr; + double distClosest = -1; + for (auto& n : m_dwindleNodesData) { + if (skip && n->pTarget == skip) + continue; + + if (n->pTarget && Desktop::View::validMapped(n->pTarget->window())) { + auto distAnother = vecToRectDistanceSquared(point, n->box.pos(), n->box.pos() + n->box.size()); + if (!res || distAnother < distClosest) { + res = n; + distClosest = distAnother; + } + } + } + return res; +} + +SP CDwindleAlgorithm::getMasterNode() { + for (auto& n : m_dwindleNodesData) { + if (!n->pParent) + return n; + } + return nullptr; +} + +std::expected CDwindleAlgorithm::layoutMsg(const std::string_view& sv) { + const auto ARGS = CVarList2(std::string{sv}, 0, ' '); + + const auto CURRENT_NODE = getNodeFromWindow(Desktop::focusState()->window()); + + if (ARGS[0] == "togglesplit") { + if (CURRENT_NODE) { + if (!toggleSplit(CURRENT_NODE)) + return std::unexpected("can't togglesplit in the current workspace"); + } + } else if (ARGS[0] == "swapsplit") { + if (CURRENT_NODE) { + if (!swapSplit(CURRENT_NODE)) + return std::unexpected("can't swapsplit in the current workspace"); + } + } else if (ARGS[0] == "rotatesplit") { + if (CURRENT_NODE) { + int angle = 90; + if (!ARGS[1].empty()) { + try { + angle = std::stoi(std::string{ARGS[1]}); + } catch (const std::exception& e) { + Log::logger->log(Log::WARN, "Invalid angle argument for rotatesplit: {}", ARGS[1]); + return std::unexpected("Invalid angle argument"); + } + } + rotateSplit(CURRENT_NODE, angle); + } + } else if (ARGS[0] == "movetoroot") { + auto node = CURRENT_NODE; + if (!ARGS[1].empty()) { + auto w = g_pCompositor->getWindowByRegex(std::string{ARGS[1]}); + if (w) + node = getNodeFromWindow(w); + } + + const auto STABLE = ARGS[2].empty() || ARGS[2] != "unstable"; + if (!moveToRoot(node, STABLE)) + return std::unexpected("can't movetoroot in the current workspace"); + } else if (ARGS[0] == "preselect") { + auto direction = ARGS[1]; + + if (direction.empty()) { + Log::logger->log(Log::ERR, "Expected direction for preselect"); + return std::unexpected("No direction for preselect"); + } + + switch (direction.front()) { + case 'u': + case 't': { + m_overrideDirection = Math::DIRECTION_UP; + break; + } + case 'd': + case 'b': { + m_overrideDirection = Math::DIRECTION_DOWN; + break; + } + case 'r': { + m_overrideDirection = Math::DIRECTION_RIGHT; + break; + } + case 'l': { + m_overrideDirection = Math::DIRECTION_LEFT; + break; + } + default: { + // any other character resets the focus direction + // needed for the persistent mode + m_overrideDirection = Math::DIRECTION_DEFAULT; + break; + } + } + } else if (ARGS[0] == "splitratio") { + auto ratio = ARGS[1]; + bool exact = ARGS[2].starts_with("exact"); + + if (ratio.empty()) + return std::unexpected("splitratio requires an arg"); + + auto delta = getPlusMinusKeywordResult(std::string{ratio}, 0.F); + + if (!CURRENT_NODE || !CURRENT_NODE->pParent) + return std::unexpected("cannot alter split ratio on no / single node"); + + if (!delta) + return std::unexpected(std::format("failed to parse \"{}\" as a delta", ratio)); + + const float newRatio = exact ? *delta : CURRENT_NODE->pParent->splitRatio + *delta; + CURRENT_NODE->pParent->splitRatio = std::clamp(newRatio, 0.1F, 1.9F); + + CURRENT_NODE->pParent->recalcSizePosRecursive(); + } + + return {}; +} + +bool CDwindleAlgorithm::toggleSplit(SP x) { + if (!x || !x->pParent) + return false; + + if (x->pTarget->fullscreenMode() != FSMODE_NONE) + return false; + + x->pParent->splitTop = !x->pParent->splitTop; + + x->pParent->recalcSizePosRecursive(); + + return true; +} + +bool CDwindleAlgorithm::swapSplit(SP x) { + if (x->pTarget->fullscreenMode() != FSMODE_NONE || !x->pParent) + return false; + + std::swap(x->pParent->children[0], x->pParent->children[1]); + + x->pParent->recalcSizePosRecursive(); + + return true; +} + +void CDwindleAlgorithm::rotateSplit(SP x, int angle) { + if (!x || !x->pParent) + return; + + if (x->pTarget->fullscreenMode() != FSMODE_NONE) + return; + + // normalize the angle to multiples of 90 degrees + int normalizedAngle = ((sc(angle / 90) % 4) + 4) % 4; // ensures positive modulo + + auto pParent = x->pParent; + + bool shouldSwap = false; + + switch (normalizedAngle) { + case 0: // 0 degrees - no change + break; + case 1: + if (pParent->splitTop) + shouldSwap = true; + pParent->splitTop = !pParent->splitTop; + break; + case 2: shouldSwap = true; break; + case 3: + if (!pParent->splitTop) + shouldSwap = true; + pParent->splitTop = !pParent->splitTop; + break; + default: break; // should never happen + } + + if (shouldSwap) + std::swap(pParent->children[0], pParent->children[1]); + + pParent->recalcSizePosRecursive(); +} + +bool CDwindleAlgorithm::moveToRoot(SP x, bool stable) { + if (!x || !x->pParent) + return false; + + if (x->pTarget->fullscreenMode() != FSMODE_NONE) + return false; + + // already at root + if (!x->pParent->pParent) + return false; + + auto& pNode = x->pParent->children[0] == x ? x->pParent->children[0] : x->pParent->children[1]; + + // instead of [getMasterNodeOnWorkspace], we walk back to root since we need + // to know which children of root is our ancestor + auto pAncestor = x, pRoot = x->pParent.lock(); + while (pRoot->pParent) { + pAncestor = pRoot; + pRoot = pRoot->pParent.lock(); + } + + auto& pSwap = pRoot->children[0] == pAncestor ? pRoot->children[1] : pRoot->children[0]; + std::swap(pNode, pSwap); + std::swap(pNode->pParent, pSwap->pParent); + + // [stable] in that the focused window occupies same side of screen + if (stable) + std::swap(pRoot->children[0], pRoot->children[1]); + + pRoot->recalcSizePosRecursive(); + + return true; +} diff --git a/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp new file mode 100644 index 000000000..043f87384 --- /dev/null +++ b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp @@ -0,0 +1,58 @@ +#include "../../TiledAlgorithm.hpp" + +namespace Layout { + class CAlgorithm; +} + +namespace Layout::Tiled { + struct SDwindleNodeData; + + class CDwindleAlgorithm : public ITiledAlgorithm { + public: + CDwindleAlgorithm() = default; + virtual ~CDwindleAlgorithm() = default; + + virtual void newTarget(SP target); + virtual void movedTarget(SP target, std::optional focalPoint = std::nullopt); + virtual void removeTarget(SP target); + + virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + virtual void recalculate(); + + virtual SP getNextCandidate(SP old); + + virtual std::expected layoutMsg(const std::string_view& sv); + virtual std::optional predictSizeForNewTarget(); + + virtual void swapTargets(SP a, SP b); + virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + private: + std::vector> m_dwindleNodesData; + + struct { + bool started = false; + bool pseudo = false; + bool xExtent = false; + bool yExtent = false; + } m_pseudoDragFlags; + + std::optional m_overrideFocalPoint; // for onWindowCreatedTiling. + + void addTarget(SP target); + 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; + }; +}; diff --git a/src/layout/algorithm/tiled/master/MasterAlgorithm.cpp b/src/layout/algorithm/tiled/master/MasterAlgorithm.cpp new file mode 100644 index 000000000..7017deb18 --- /dev/null +++ b/src/layout/algorithm/tiled/master/MasterAlgorithm.cpp @@ -0,0 +1,1309 @@ +#include "MasterAlgorithm.hpp" + +#include "../../Algorithm.hpp" +#include "../../../space/Space.hpp" +#include "../../../target/WindowTarget.hpp" + +#include "../../../../config/ConfigValue.hpp" +#include "../../../../config/shared/workspace/WorkspaceRuleManager.hpp" +#include "../../../../desktop/state/FocusState.hpp" +#include "../../../../helpers/Monitor.hpp" +#include "../../../../Compositor.hpp" +#include "../../../../render/Renderer.hpp" + +#include +#include + +using namespace Layout; +using namespace Layout::Tiled; +using namespace Hyprutils::String; + +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 = Config::workspaceRuleMgr()->getWorkspaceRuleFor(m_parent->space()->workspace()); + std::string orientationString; + if (WORKSPACERULE && WORKSPACERULE->m_layoutopts.contains("orientation")) + orientationString = WORKSPACERULE->m_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 || t->floating()) + return nullptr; + + const auto PNODE = getNodeFromTarget(t); + + auto nodes = m_masterNodesData; + if (!next) + std::ranges::reverse(nodes); + + const auto NODEIT = std::ranges::find(nodes, PNODE); + + const bool ISMASTER = PNODE->isMaster; + + auto CANDIDATE = std::find_if(NODEIT, nodes.end(), [&](const auto& other) { return other != PNODE && ISMASTER == other->isMaster; }); + if (CANDIDATE == nodes.end()) + CANDIDATE = std::ranges::find_if(nodes, [&](const auto& other) { return other != PNODE && ISMASTER != other->isMaster; }); + + if (CANDIDATE != nodes.end() && !loop) { + if ((*CANDIDATE)->isMaster && next) + return nullptr; + if (!(*CANDIDATE)->isMaster && ISMASTER && !next) + return nullptr; + } + + return CANDIDATE == nodes.end() ? nullptr : (*CANDIDATE)->pTarget.lock(); +} + +int CMasterAlgorithm::getMastersNo() { + return std::ranges::count_if(m_masterNodesData, [](const auto& n) { return n->isMaster; }); +} + +bool CMasterAlgorithm::isWindowTiled(PHLWINDOW x) { + return x && !x->layoutTarget()->floating(); +} + +SP CMasterAlgorithm::getClosestNode(const Vector2D& point) { + SP res = nullptr; + double distClosest = -1; + for (auto& n : m_masterNodesData) { + if (n->pTarget && Desktop::View::validMapped(n->pTarget->window())) { + auto distAnother = vecToRectDistanceSquared(point, n->position, n->position + n->size); + if (!res || distAnother < distClosest) { + res = n; + distClosest = distAnother; + } + } + } + return res; +} diff --git a/src/layout/algorithm/tiled/master/MasterAlgorithm.hpp b/src/layout/algorithm/tiled/master/MasterAlgorithm.hpp new file mode 100644 index 000000000..5cfa6b368 --- /dev/null +++ b/src/layout/algorithm/tiled/master/MasterAlgorithm.hpp @@ -0,0 +1,76 @@ +#include "../../TiledAlgorithm.hpp" + +#include + +namespace Layout { + class CAlgorithm; +} + +namespace Layout::Tiled { + struct SMasterNodeData; + + //orientation determines which side of the screen the master area resides + enum eOrientation : uint8_t { + ORIENTATION_LEFT = 0, + ORIENTATION_TOP, + ORIENTATION_RIGHT, + ORIENTATION_BOTTOM, + ORIENTATION_CENTER + }; + + struct SMasterWorkspaceData { + WORKSPACEID workspaceID = WORKSPACE_INVALID; + std::optional explicitOrientation; + // Previously focused non-master window when `focusmaster previous` command was issued + WP focusMasterPrev; + + // + bool operator==(const SMasterWorkspaceData& rhs) const { + return workspaceID == rhs.workspaceID; + } + }; + + class CMasterAlgorithm : public ITiledAlgorithm { + public: + CMasterAlgorithm() = default; + virtual ~CMasterAlgorithm() = default; + + virtual void newTarget(SP target); + virtual void movedTarget(SP target, std::optional focalPoint = std::nullopt); + virtual void removeTarget(SP target); + + virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + virtual void recalculate(); + + virtual SP getNextCandidate(SP old); + + virtual std::expected layoutMsg(const std::string_view& sv); + virtual std::optional predictSizeForNewTarget(); + + virtual void swapTargets(SP a, SP b); + virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + private: + std::vector> m_masterNodesData; + SMasterWorkspaceData m_workspaceData; + + void addTarget(SP target, bool firstMap); + + bool m_forceWarps = false; + + void buildOrientationCycleVectorFromVars(std::vector& cycle, Hyprutils::String::CVarList2* vars); + void buildOrientationCycleVectorFromEOperation(std::vector& cycle); + void runOrientationCycle(Hyprutils::String::CVarList2* vars, int next); + eOrientation getDynamicOrientation(); + int getNodesNo(); + SP getNodeFromWindow(PHLWINDOW); + SP getNodeFromTarget(SP); + SP getMasterNode(); + SP getClosestNode(const Vector2D&); + void calculateWorkspace(); + SP getNextTarget(SP, bool, bool); + int getMastersNo(); + bool isWindowTiled(PHLWINDOW); + eOrientation defaultOrientation(); + }; +}; \ No newline at end of file diff --git a/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp b/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp new file mode 100644 index 000000000..e1b4c6088 --- /dev/null +++ b/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp @@ -0,0 +1,279 @@ +#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 PMONITOR = t->space()->workspace()->m_monitor.lock(); + const auto PMONINDIR = g_pCompositor->getMonitorInDirection(PMONITOR, dir); + + // if we found a monitor, move the window there + if (PMONINDIR && PMONINDIR != PMONITOR) { + const auto TARGETWS = PMONINDIR->m_activeWorkspace; + + if (t->window()) + t->window()->setAnimationsToMove(); + + t->assignToSpace(TARGETWS->m_space, focalPointForDir(t, dir)); + } +} + +void CMonocleAlgorithm::cycleNext() { + if (m_targetDatas.empty()) + return; + + m_currentVisibleIndex = (m_currentVisibleIndex + 1) % m_targetDatas.size(); + updateVisible(); +} + +void CMonocleAlgorithm::cyclePrev() { + if (m_targetDatas.empty()) + return; + + m_currentVisibleIndex--; + if (m_currentVisibleIndex < 0) + m_currentVisibleIndex = m_targetDatas.size() - 1; + updateVisible(); +} + +void CMonocleAlgorithm::focusTargetUpdate(SP target) { + auto it = std::ranges::find_if(m_targetDatas, [target](const auto& data) { return data->target.lock() == target; }); + + if (it == m_targetDatas.end()) + return; + + const auto NEW_INDEX = std::distance(m_targetDatas.begin(), it); + + if (m_currentVisibleIndex != NEW_INDEX) { + m_currentVisibleIndex = NEW_INDEX; + updateVisible(); + } +} + +void CMonocleAlgorithm::updateVisible() { + recalculate(); + + const auto VISIBLE_TARGET = getVisibleTarget(); + if (!VISIBLE_TARGET) + return; + + const auto WINDOW = VISIBLE_TARGET->window(); + if (!WINDOW) + return; + + Desktop::focusState()->fullWindowFocus(WINDOW, Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE); +} + +SP CMonocleAlgorithm::getVisibleTarget() { + if (m_currentVisibleIndex < 0 || m_currentVisibleIndex >= (int)m_targetDatas.size()) + return nullptr; + + return m_targetDatas[m_currentVisibleIndex]->target.lock(); +} diff --git a/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp b/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp new file mode 100644 index 000000000..b23f85be7 --- /dev/null +++ b/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include "../../TiledAlgorithm.hpp" +#include "../../../../helpers/signal/Signal.hpp" + +#include + +namespace Layout::Tiled { + + struct SMonocleTargetData { + SMonocleTargetData(SP t) : target(t) { + ; + } + + WP target; + CBox layoutBox; + }; + + class CMonocleAlgorithm : public ITiledAlgorithm { + public: + CMonocleAlgorithm(); + virtual ~CMonocleAlgorithm(); + + virtual void newTarget(SP target); + virtual void movedTarget(SP target, std::optional focalPoint = std::nullopt); + virtual void removeTarget(SP target); + + virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + virtual void recalculate(); + + virtual SP getNextCandidate(SP old); + + virtual std::expected layoutMsg(const std::string_view& sv); + virtual std::optional predictSizeForNewTarget(); + + virtual void swapTargets(SP a, SP b); + virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + private: + std::vector> m_targetDatas; + CHyprSignalListener m_focusCallback; + + int m_currentVisibleIndex = 0; + + SP dataFor(SP t); + void cycleNext(); + void cyclePrev(); + void focusTargetUpdate(SP target); + void updateVisible(); + SP getVisibleTarget(); + }; +}; diff --git a/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp new file mode 100644 index 000000000..657b65426 --- /dev/null +++ b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp @@ -0,0 +1,310 @@ +#include "ScrollTapeController.hpp" +#include "ScrollingAlgorithm.hpp" +#include "../../../../config/ConfigValue.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 and fit method is not center, reset offset to 0 + static const auto PFITMETHOD = CConfigValue("scrolling:focus_fit_method"); + if (maxExtent > usablePrimary && m_offset < 0.0 && *PFITMETHOD != 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); + + const double lo = stripStart - usablePrimary + stripSize; + const double hi = stripStart; + + if (lo > hi) { + // strip is wider than viewport (e.g. during monitor reconnection after suspend), + // center the strip instead of hitting the std::clamp assertion + m_offset = stripStart - (usablePrimary - stripSize) / 2.0; + return; + } + + m_offset = std::clamp(m_offset, lo, hi); +} + +bool CScrollTapeController::isStripVisible(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne, bool full) const { + if (stripIndex >= m_strips.size()) + return false; + + const double stripStart = calculateStripStart(stripIndex, usableArea, fullscreenOnOne); + const double stripEnd = stripStart + calculateStripSize(stripIndex, usableArea, fullscreenOnOne); + const double viewStart = m_offset; + const double viewEnd = m_offset + getPrimary(usableArea.size()); + + if (!full) + return stripStart < viewEnd && viewStart < stripEnd; + else + return stripStart >= viewStart && stripEnd <= viewEnd; +} + +size_t CScrollTapeController::getStripAtCenter(const CBox& usableArea, bool fullscreenOnOne) const { + if (m_strips.empty()) + return 0; + + const double usablePrimary = getPrimary(usableArea.size()); + double currentPos = m_offset; + + for (size_t i = 0; i < m_strips.size(); ++i) { + const double stripSize = calculateStripSize(i, usableArea, fullscreenOnOne); + currentPos += stripSize; + + if (currentPos >= usablePrimary / 2.0 - 2.0) + return i; + } + + return m_strips.empty() ? 0 : m_strips.size() - 1; +} + +void CScrollTapeController::swapStrips(size_t a, size_t b) { + if (a >= m_strips.size() || b >= m_strips.size()) + return; + + std::swap(m_strips.at(a), m_strips.at(b)); +} + +bool CScrollTapeController::isBeingDragged() const { + for (const auto& s : m_strips) { + if (!s.userData) + continue; + + for (const auto& d : s.userData->targetDatas) { + if (d->target == g_layoutManager->dragController()->target()) + return true; + } + } + + return false; +} diff --git a/src/layout/algorithm/tiled/scrolling/ScrollTapeController.hpp b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.hpp new file mode 100644 index 000000000..da2efbba3 --- /dev/null +++ b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include "../../../../helpers/math/Math.hpp" +#include "../../../../helpers/memory/Memory.hpp" +#include + +namespace Layout::Tiled { + + struct SColumnData; + + enum eScrollDirection : uint8_t { + SCROLL_DIR_RIGHT = 0, + SCROLL_DIR_LEFT, + SCROLL_DIR_DOWN, + SCROLL_DIR_UP, + }; + + struct SStripData { + float size = 1.F; // size along primary axis + std::vector targetSizes; // sizes along secondary axis for each target in this strip + WP userData; + + SStripData() = default; + }; + + struct STapeLayoutResult { + CBox box; + size_t stripIndex = 0; + size_t targetIndex = 0; + }; + + class CScrollTapeController { + public: + CScrollTapeController(eScrollDirection direction = SCROLL_DIR_RIGHT); + ~CScrollTapeController() = default; + + void setDirection(eScrollDirection dir); + eScrollDirection getDirection() const; + bool isPrimaryHorizontal() const; + bool isReversed() const; + + size_t addStrip(float size = 1.0F); + void insertStrip(ssize_t afterIndex, float size = 1.0F); + void removeStrip(size_t index); + size_t stripCount() const; + SStripData& getStrip(size_t index); + const SStripData& getStrip(size_t index) const; + void swapStrips(size_t a, size_t b); + + void setOffset(double offset); + double getOffset() const; + void adjustOffset(double delta); + + double calculateMaxExtent(const CBox& usableArea, bool fullscreenOnOne = false) const; + double calculateStripStart(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false) const; + double calculateStripSize(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false) const; + + CBox calculateTargetBox(size_t stripIndex, size_t targetIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne = false); + + double calculateCameraOffset(const CBox& usableArea, bool fullscreenOnOne = false); + Vector2D getCameraTranslation(const CBox& usableArea, bool fullscreenOnOne = false); + + void centerStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false); + void fitStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false); + + bool isStripVisible(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false, bool full = false) const; + + size_t getStripAtCenter(const CBox& usableArea, bool fullscreenOnOne = false) const; + + private: + eScrollDirection m_direction = SCROLL_DIR_RIGHT; + std::vector m_strips; + double m_offset = 0.0; + + double getPrimary(const Vector2D& v) const; + double getSecondary(const Vector2D& v) const; + void setPrimary(Vector2D& v, double val) const; + void setSecondary(Vector2D& v, double val) const; + bool isBeingDragged() const; + + Vector2D makeVector(double primary, double secondary) const; + }; +}; diff --git a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp new file mode 100644 index 000000000..83ab0974f --- /dev/null +++ b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp @@ -0,0 +1,1605 @@ +#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/shared/workspace/WorkspaceRuleManager.hpp" +#include "../../../../render/Renderer.hpp" +#include "../../../../managers/input/InputManager.hpp" +#include "../../../../event/EventBus.hpp" + +#include +#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) { + if (targetDatas.empty()) + return false; + + 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(std::optional width) { + auto col = columns.emplace_back(makeShared(self.lock())); + col->self = col; + + size_t stripIdx = controller->addStrip(width.value_or(algorithm->defaultColumnWidth())); + controller->getStrip(stripIdx).userData = col; + + return col; +} + +SP SScrollingData::add(int after, std::optional width) { + auto col = makeShared(self.lock()); + col->self = col; + columns.insert(columns.begin() + after + 1, col); + + controller->insertStrip(after, width.value_or(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 || !algorithm->m_parent->space() || !algorithm->m_parent->space()->workspace() || !algorithm->m_parent->space()->workspace()->m_monitor || + 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) + 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; + const auto width = target->window()->m_ruleApplicator->static_.scrollingWidth; + + if (!droppingColumn) { + auto col = m_scrollingData->add(width); + 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(width) : m_scrollingData->add(idx, width); + 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() { + // guard against recalculation during transitional monitor states + // (e.g. monitor reconnecting after suspend where workspace/monitor may not be ready) + if (!m_parent || !m_parent->space() || !m_parent->space()->workspace() || !m_parent->space()->workspace()->m_monitor) + return; + + 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_UP; + case Math::DIRECTION_RIGHT: return Math::DIRECTION_DOWN; + 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_UP; + case Math::DIRECTION_RIGHT: return Math::DIRECTION_DOWN; + 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" || ARGS[0] == "consume" || ARGS[0] == "expel" || ARGS[0] == "consume_or_expel") { + const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr); + if (!TDATA) + return std::unexpected("no window focused"); + + const auto CURRENT_COL = TDATA->column.lock(); + if (!CURRENT_COL) + return std::unexpected("no current col"); + + // expel a target from srcCol into its own new column at insertIdx + auto expelTarget = [&](SP tdata, SP srcCol, std::optional insertIdx) { + auto col = !insertIdx ? m_scrollingData->add() : m_scrollingData->add(*insertIdx); + srcCol->remove(tdata->target.lock()); + col->add(tdata); + m_scrollingData->centerOrFitCol(col); + }; + + // consume the first target from adjCol into dstCol + auto consumeTarget = [&](SP dstCol, SP adjCol) { + const auto target = adjCol->targetDatas.front(); + adjCol->remove(target->target.lock()); + dstCol->add(target); + m_scrollingData->centerOrFitCol(dstCol); + }; + + if (ARGS[0] == "promote") { + auto idx = m_scrollingData->idx(CURRENT_COL); + expelTarget(TDATA, CURRENT_COL, idx == -1 ? std::nullopt : std::optional{idx}); + } else if (ARGS[0] == "expel") { + if (CURRENT_COL->targetDatas.size() < 2) + return std::unexpected("column has only one window"); + + const auto lastTarget = CURRENT_COL->targetDatas.back(); + const auto currentIdx = m_scrollingData->idx(CURRENT_COL); + const auto NEXT_COL = m_scrollingData->next(CURRENT_COL); + const auto insertIdx = !NEXT_COL ? std::nullopt : std::optional{currentIdx}; + + expelTarget(lastTarget, CURRENT_COL, insertIdx); + } else if (ARGS[0] == "consume") { + const auto NEXT_COL = m_scrollingData->next(CURRENT_COL); + if (!NEXT_COL) + return std::unexpected("no next column"); + + consumeTarget(CURRENT_COL, NEXT_COL); + } else if (ARGS[0] == "consume_or_expel") { + if (ARGS.size() < 2) + return std::unexpected("not enough args"); + + const std::string& direction = ARGS[1]; + const bool prev = direction == "prev"; + const bool next = direction == "next"; + + if (!prev && !next) + return std::unexpected("invalid direction, expected prev or next"); + + if (CURRENT_COL->targetDatas.size() > 1) { + const auto currentIdx = m_scrollingData->idx(CURRENT_COL); + expelTarget(TDATA, CURRENT_COL, prev ? currentIdx - 1 : currentIdx); + } else { + const auto ADJ_COL = prev ? m_scrollingData->prev(CURRENT_COL) : m_scrollingData->next(CURRENT_COL); + if (!ADJ_COL) + return std::unexpected("no adjacent column"); + + CURRENT_COL->remove(TDATA->target.lock()); + ADJ_COL->add(TDATA); + m_scrollingData->centerOrFitCol(ADJ_COL); + } + } + + 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 if (ARGS[0] == "center") { + 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"); + + m_scrollingData->centerCol(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 = Config::workspaceRuleMgr()->getWorkspaceRuleFor(m_parent->space()->workspace()); + std::string directionString; + if (WORKSPACERULE && WORKSPACERULE->m_layoutopts.contains("direction")) + directionString = WORKSPACERULE->m_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() { + if (!m_parent || !m_parent->space()) + return {}; + + 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); + + // ensure dimensions are never zero or negative, which can happen during + // monitor transitions (e.g. reconnection after suspend with stale reserved areas) + box.w = std::max(box.w, 1.0); + box.h = std::max(box.h, 1.0); + + return box; +} + +float CScrollingAlgorithm::defaultColumnWidth() { + static const auto PCOLWIDTH = CConfigValue("scrolling:column_width"); + return std::clamp(*PCOLWIDTH, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH); +} diff --git a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp new file mode 100644 index 000000000..1b2b1d1ea --- /dev/null +++ b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp @@ -0,0 +1,145 @@ +#pragma once + +#include "../../TiledAlgorithm.hpp" +#include "../../../../helpers/math/Direction.hpp" +#include "ScrollTapeController.hpp" +#include "../../../../helpers/signal/Signal.hpp" + +#include + +namespace Layout::Tiled { + class CScrollingAlgorithm; + struct SColumnData; + struct SScrollingData; + + struct SScrollingTargetData { + SScrollingTargetData(SP t, SP col) : target(t), column(col) { + ; + } + + WP target; + WP column; + bool ignoreFullscreenChecks = false; + + CBox layoutBox; + }; + + struct SColumnData { + SColumnData(SP data) : scrollingData(data) { + ; + } + + void add(SP t); + void add(SP t, int after); + void add(SP w); + void add(SP w, int after); + void remove(SP t); + bool has(SP t); + size_t idx(SP t); + + // index of lowest target that is above y. + size_t idxForHeight(float y); + + bool up(SP w); + bool down(SP w); + + SP next(SP w); + SP prev(SP w); + + std::vector> targetDatas; + WP scrollingData; + WP lastFocusedTarget; + + WP self; + + // Helper methods to access controller-managed data + float getColumnWidth() const; + void setColumnWidth(float width); + float getTargetSize(size_t idx) const; + void setTargetSize(size_t idx, float size); + float getTargetSize(SP target) const; + void setTargetSize(SP target, float size); + }; + + struct SScrollingData { + SScrollingData(CScrollingAlgorithm* algo); + + std::vector> columns; + + UP controller; + + SP add(std::optional width = std::nullopt); + SP add(int after, std::optional width = std::nullopt); + int64_t idx(SP c); + void remove(SP c); + double maxWidth(); + SP next(SP c); + SP prev(SP c); + SP atCenter(); + + bool visible(SP c, bool full = false); + void centerCol(SP c); + void fitCol(SP c); + void centerOrFitCol(SP c); + + void recalculate(bool forceInstant = false); + + CScrollingAlgorithm* algorithm = nullptr; + WP self; + std::optional lockedCameraOffset; + }; + + class CScrollingAlgorithm : public ITiledAlgorithm { + public: + CScrollingAlgorithm(); + virtual ~CScrollingAlgorithm(); + + virtual void newTarget(SP target); + virtual void movedTarget(SP target, std::optional focalPoint = std::nullopt); + virtual void removeTarget(SP target); + + virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + virtual void recalculate(); + + virtual SP getNextCandidate(SP old); + + virtual std::expected layoutMsg(const std::string_view& sv); + virtual std::optional predictSizeForNewTarget(); + + virtual void swapTargets(SP a, SP b); + virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + CBox usableArea(); + + enum eInputMode : uint8_t { + INPUT_MODE_SOFT = 0, + INPUT_MODE_CLICK, + INPUT_MODE_KB + }; + + private: + SP m_scrollingData; + + CHyprSignalListener m_configCallback; + CHyprSignalListener m_focusCallback; + CHyprSignalListener m_mouseButtonCallback; + + struct { + std::vector configuredWidths; + } m_config; + + eScrollDirection getDynamicDirection(); + + SP findBestNeighbor(SP pCurrent, SP pTargetCol); + SP dataFor(SP t); + SP closestNode(const Vector2D& posGlobglobgabgalab); + + void focusTargetUpdate(SP target); + void moveTargetTo(SP t, Math::eDirection dir, bool silent); + void focusOnInput(SP target, eInputMode input); + + float defaultColumnWidth(); + + friend struct SScrollingData; + }; +}; diff --git a/src/layout/space/Space.cpp b/src/layout/space/Space.cpp new file mode 100644 index 000000000..db0144729 --- /dev/null +++ b/src/layout/space/Space.cpp @@ -0,0 +1,207 @@ +#include "Space.hpp" + +#include "../target/Target.hpp" +#include "../algorithm/Algorithm.hpp" + +#include "../../debug/log/Logger.hpp" +#include "../../desktop/Workspace.hpp" +#include "../../config/shared/workspace/WorkspaceRuleManager.hpp" +#include "../../config/ConfigValue.hpp" +#include "../../event/EventBus.hpp" +#include "../../helpers/Monitor.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] { + // During monitor disconnect/reconnect (e.g. sleep/wake), some workspaces + // may have stale or null monitors. Guard against that to avoid crashing + // when recalculating layout for workspaces mid-migration. + if (!m_parent || !m_parent->m_monitor) + return; + + recheckWorkArea(); + + if (m_algorithm) + 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 = Config::workspaceRuleMgr()->getWorkspaceRuleFor(m_parent.lock()).value_or(Config::CWorkspaceRule{}); + + 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.m_gapsOut.value_or(*PGAPSOUT); + auto gapsFloat = WORKSPACERULE.m_gapsOut.value_or(*PFLOATGAPS); + + Desktop::CReservedArea reservedGaps{gapsOut.m_top, gapsOut.m_right, gapsOut.m_bottom, gapsOut.m_left}; + Desktop::CReservedArea reservedFloatGaps{gapsFloat.m_top, gapsFloat.m_right, gapsFloat.m_bottom, gapsFloat.m_left}; + + auto floatWorkArea = workArea; + + reservedFloatGaps.applyip(floatWorkArea); + reservedGaps.applyip(workArea); + + m_workArea = workArea; + m_floatingWorkArea = floatWorkArea; +} + +const CBox& CSpace::workArea(bool floating) const { + return floating ? m_floatingWorkArea : m_workArea; +} + +PHLWORKSPACE CSpace::workspace() const { + return m_parent.lock(); +} + +void CSpace::toggleTargetFloating(SP t) { + t->setWasTiling(true); + m_algorithm->setFloating(t, !t->floating()); + t->setWasTiling(false); + + m_parent->updateWindows(); + + recalculate(); +} + +CBox CSpace::targetPositionLocal(SP t) const { + return t->position().translate(-m_workArea.pos()); +} + +void CSpace::resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner) { + if (!m_algorithm) + return; + + m_algorithm->resizeTarget(Δ, target, corner); +} + +void CSpace::moveTarget(const Vector2D& Δ, SP target) { + if (!m_algorithm) + return; + + m_algorithm->moveTarget(Δ, target); +} + +SP CSpace::algorithm() const { + return m_algorithm; +} + +void CSpace::recalculate() { + recheckWorkArea(); + + if (m_algorithm) + m_algorithm->recalculate(); +} + +void CSpace::setFullscreen(SP t, eFullscreenMode mode) { + t->setFullscreenMode(mode); + + if (mode == FSMODE_NONE && m_algorithm && t->floating()) + m_algorithm->recenter(t); + + recalculate(); +} + +std::expected CSpace::layoutMsg(const std::string_view& sv) { + if (m_algorithm) + return m_algorithm->layoutMsg(sv); + + return {}; +} + +std::optional CSpace::predictSizeForNewTiledTarget() { + if (m_algorithm) + return m_algorithm->predictSizeForNewTiledTarget(); + + return std::nullopt; +} + +void CSpace::swap(SP a, SP b) { + for (auto& t : m_targets) { + if (t == a) + t = b; + else if (t == b) + t = a; + } + + if (m_algorithm) + m_algorithm->swapTargets(a, b); +} + +void CSpace::moveTargetInDirection(SP t, Math::eDirection dir, bool silent) { + if (m_algorithm) + m_algorithm->moveTargetInDirection(t, dir, silent); +} + +void CSpace::setTargetGeom(const CBox& box, SP target) { + if (m_algorithm) + m_algorithm->setTargetGeom(box, target); +} + +SP CSpace::getNextCandidate(SP old) { + return !m_algorithm ? nullptr : m_algorithm->getNextCandidate(old); +} + +const std::vector>& CSpace::targets() const { + return m_targets; +} diff --git a/src/layout/space/Space.hpp b/src/layout/space/Space.hpp new file mode 100644 index 000000000..e29a6d8fb --- /dev/null +++ b/src/layout/space/Space.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include "../../helpers/math/Math.hpp" +#include "../../helpers/math/Direction.hpp" +#include "../../helpers/memory/Memory.hpp" + +#include "../../desktop/DesktopTypes.hpp" + +#include "../LayoutManager.hpp" + +#include +#include + +namespace Layout { + class ITarget; + class CAlgorithm; + + class CSpace { + public: + static SP create(PHLWORKSPACE w); + ~CSpace() = default; + + void add(SP t); + void remove(SP t); + void move(SP t, std::optional focalPoint = std::nullopt); + + void swap(SP a, SP b); + + SP getNextCandidate(SP old); + + void setAlgorithmProvider(SP algo); + void recheckWorkArea(); + void setFullscreen(SP t, eFullscreenMode mode); + + void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + void recalculate(); + + void toggleTargetFloating(SP t); + + std::expected layoutMsg(const std::string_view& sv); + std::optional predictSizeForNewTiledTarget(); + + const CBox& workArea(bool floating = false) const; + PHLWORKSPACE workspace() const; + CBox targetPositionLocal(SP t) const; + + void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + void moveTarget(const Vector2D& Δ, SP target); + void setTargetGeom(const CBox& box, SP target); // only for float + + SP algorithm() const; + + const std::vector>& targets() const; + + private: + CSpace(PHLWORKSPACE parent); + + WP m_self; + + std::vector> m_targets; + SP m_algorithm; + PHLWORKSPACEREF m_parent; + + // work area is in global coords + CBox m_workArea, m_floatingWorkArea; + + // for recalc + CHyprSignalListener m_geomUpdateCallback; + }; +}; \ No newline at end of file diff --git a/src/layout/supplementary/DragController.cpp b/src/layout/supplementary/DragController.cpp new file mode 100644 index 000000000..790361868 --- /dev/null +++ b/src/layout/supplementary/DragController.cpp @@ -0,0 +1,372 @@ +#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() && m_dragThresholdReached) { + 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()); + + if (m_dragThresholdReached) { + DRAGGINGTARGET->setPositionGlobal(CBox{g_pInputManager->getMouseCoordsInternal() - DRAGGINGTARGET->position().size() / 2.F, DRAGGINGTARGET->position().size()}); + 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) { + // make sure to check if we are floating because drag into group could make us tiled already + if (draggingTarget->floating()) + g_layoutManager->changeFloatingMode(draggingTarget); + + draggingTarget->rememberFloatingSize(m_draggingWindowOriginalFloatSize); + } + + draggingTarget->damageEntire(); + + g_layoutManager->setTargetGeom(draggingTarget->position(), draggingTarget); + + Desktop::focusState()->fullWindowFocus(draggingTarget->window(), Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE); + + m_wasDraggingWindow = false; + m_dragMode = MBIND_INVALID; +} + +void CDragStateController::mouseMove(const Vector2D& mousePos) { + if (m_target.expired()) + return; + + const auto DRAGGINGTARGET = m_target.lock(); + static auto PDRAGTHRESHOLD = CConfigValue("binds:drag_threshold"); + + // Window invalid or drag begin size 0,0 meaning we rejected it. + if ((!validMapped(DRAGGINGTARGET->window()) || m_beginDragSizeXY == Vector2D())) { + CKeybindManager::changeMouseBindMode(MBIND_INVALID); + return; + } + + // Yoink dragged window here instead if using drag_threshold and it has been reached + if (*PDRAGTHRESHOLD > 0 && !m_dragThresholdReached) { + if ((m_beginDragXY.distanceSq(mousePos) <= std::pow(*PDRAGTHRESHOLD, 2) && m_beginDragXY == m_lastDragXY)) + return; + m_dragThresholdReached = true; + if (updateDragWindow()) + return; + } + + static auto TIMER = std::chrono::high_resolution_clock::now(), MSTIMER = TIMER; + + const auto DELTA = Vector2D(mousePos.x - m_beginDragXY.x, mousePos.y - m_beginDragXY.y); + const auto TICKDELTA = Vector2D(mousePos.x - m_lastDragXY.x, mousePos.y - m_lastDragXY.y); + + static auto SNAPENABLED = CConfigValue("general:snap:enabled"); + + const auto TIMERDELTA = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - TIMER).count(); + const auto MSDELTA = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - MSTIMER).count(); + const auto MSMONITOR = 1000.0 / g_pHyprRenderer->m_mostHzMonitor->m_refreshRate; + static int totalMs = 0; + bool canSkipUpdate = true; + + MSTIMER = std::chrono::high_resolution_clock::now(); + + if (m_mouseMoveEventCount == 1) + totalMs = 0; + + if (MSMONITOR > 16.0) { + totalMs += MSDELTA < MSMONITOR ? MSDELTA : std::round(totalMs * 1.0 / m_mouseMoveEventCount); + m_mouseMoveEventCount += 1; + + // check if time-window is enough to skip update on 60hz monitor + canSkipUpdate = std::clamp(MSMONITOR - TIMERDELTA, 0.0, MSMONITOR) > totalMs * 1.0 / m_mouseMoveEventCount; + } + + if ((abs(TICKDELTA.x) < 1.f && abs(TICKDELTA.y) < 1.f) || (TIMERDELTA < MSMONITOR && canSkipUpdate && (m_dragMode != MBIND_MOVE))) + return; + + TIMER = std::chrono::high_resolution_clock::now(); + + m_lastDragXY = mousePos; + + DRAGGINGTARGET->damageEntire(); + + if (m_dragMode == MBIND_MOVE) { + + Vector2D newPos = m_beginDragPositionXY + DELTA; + Vector2D newSize = DRAGGINGTARGET->position().size(); + + if (*SNAPENABLED && !m_draggingTiled) + g_layoutManager->performSnap(newPos, newSize, DRAGGINGTARGET, MBIND_MOVE, -1, m_beginDragSizeXY); + + newPos = newPos.round(); + + DRAGGINGTARGET->setPositionGlobal({newPos, newSize}); + DRAGGINGTARGET->warpPositionSize(); + } else if (m_dragMode == MBIND_RESIZE || m_dragMode == MBIND_RESIZE_FORCE_RATIO || m_dragMode == MBIND_RESIZE_BLOCK_RATIO) { + if (DRAGGINGTARGET->floating()) { + + Vector2D MINSIZE = DRAGGINGTARGET->minSize().value_or(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}); + Vector2D MAXSIZE = DRAGGINGTARGET->maxSize().value_or(Math::VECTOR2D_MAX); + + Vector2D newSize = m_beginDragSizeXY; + Vector2D newPos = m_beginDragPositionXY; + + if (m_grabbedCorner == CORNER_BOTTOMRIGHT) + newSize = newSize + DELTA; + else if (m_grabbedCorner == CORNER_TOPLEFT) + newSize = newSize - DELTA; + else if (m_grabbedCorner == CORNER_TOPRIGHT) + newSize = newSize + Vector2D(DELTA.x, -DELTA.y); + else if (m_grabbedCorner == CORNER_BOTTOMLEFT) + newSize = newSize + Vector2D(-DELTA.x, DELTA.y); + + eMouseBindMode mode = m_dragMode; + if (DRAGGINGTARGET->window() && DRAGGINGTARGET->window()->m_ruleApplicator->keepAspectRatio().valueOrDefault() && mode != MBIND_RESIZE_BLOCK_RATIO) + mode = MBIND_RESIZE_FORCE_RATIO; + + if (m_beginDragSizeXY.x >= 1 && m_beginDragSizeXY.y >= 1 && mode == MBIND_RESIZE_FORCE_RATIO) { + + const float RATIO = m_beginDragSizeXY.y / m_beginDragSizeXY.x; + + if (MINSIZE.x * RATIO > MINSIZE.y) + MINSIZE = Vector2D(MINSIZE.x, MINSIZE.x * RATIO); + else + MINSIZE = Vector2D(MINSIZE.y / RATIO, MINSIZE.y); + + if (MAXSIZE.x * RATIO < MAXSIZE.y) + MAXSIZE = Vector2D(MAXSIZE.x, MAXSIZE.x * RATIO); + else + MAXSIZE = Vector2D(MAXSIZE.y / RATIO, MAXSIZE.y); + + if (newSize.x * RATIO > newSize.y) + newSize = Vector2D(newSize.x, newSize.x * RATIO); + else + newSize = Vector2D(newSize.y / RATIO, newSize.y); + } + + newSize = newSize.clamp(MINSIZE, MAXSIZE); + + if (m_grabbedCorner == CORNER_TOPLEFT) + newPos = newPos - newSize + m_beginDragSizeXY; + else if (m_grabbedCorner == CORNER_TOPRIGHT) + newPos = newPos + Vector2D(0.0, (m_beginDragSizeXY - newSize).y); + else if (m_grabbedCorner == CORNER_BOTTOMLEFT) + newPos = newPos + Vector2D((m_beginDragSizeXY - newSize).x, 0.0); + + if (*SNAPENABLED) { + g_layoutManager->performSnap(newPos, newSize, DRAGGINGTARGET, mode, m_grabbedCorner, m_beginDragSizeXY); + newSize = newSize.clamp(MINSIZE, MAXSIZE); + } + + CBox wb = {newPos, newSize}; + wb.round(); + + DRAGGINGTARGET->setPositionGlobal(wb); + DRAGGINGTARGET->warpPositionSize(); + } else { + g_layoutManager->resizeTarget(TICKDELTA, DRAGGINGTARGET, m_grabbedCorner); + DRAGGINGTARGET->warpPositionSize(); + } + } + + // get middle point + Vector2D middle = DRAGGINGTARGET->position().middle(); + + // and check its monitor + const auto PMONITOR = g_pCompositor->getMonitorFromVector(middle); + + if (PMONITOR && PMONITOR->m_activeWorkspace && DRAGGINGTARGET->floating() /* If we're resaizing a tiled target, don't do this */) { + const auto WS = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace; + DRAGGINGTARGET->assignToSpace(WS->m_space); + } + + DRAGGINGTARGET->damageEntire(); +} diff --git a/src/layout/supplementary/DragController.hpp b/src/layout/supplementary/DragController.hpp new file mode 100644 index 000000000..3a0d8071f --- /dev/null +++ b/src/layout/supplementary/DragController.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include "../target/Target.hpp" +#include "../../managers/input/InputManager.hpp" + +namespace Layout { + enum eRectCorner : uint8_t; +} + +namespace Layout::Supplementary { + + // DragStateController contains logic to begin and end a drag, which shouldn't be part of the layout's job. It's stuff like + // toggling float when dragging tiled, remembering sizes, checking deltas, etc. + class CDragStateController { + public: + CDragStateController() = default; + ~CDragStateController() = default; + + void dragBegin(SP target, eMouseBindMode mode); + void dragEnd(); + + void mouseMove(const Vector2D& mousePos); + eMouseBindMode mode() const; + bool wasDraggingWindow() const; + bool dragThresholdReached() const; + void resetDragThresholdReached(); + bool draggingTiled() const; + + /* + Called to try to pick up window for dragging. + Updates drag related variables and floats window if threshold reached. + Return true to reject + */ + bool updateDragWindow(); + + SP target() const; + + private: + WP m_target; + + eMouseBindMode m_dragMode = MBIND_INVALID; + bool m_wasDraggingWindow = false; + bool m_dragThresholdReached = false; + bool m_draggingTiled = false; + + int m_mouseMoveEventCount = 0; + Vector2D m_beginDragXY; + Vector2D m_lastDragXY; + Vector2D m_beginDragPositionXY; + Vector2D m_beginDragSizeXY; + Vector2D m_draggingWindowOriginalFloatSize; + Layout::eRectCorner m_grabbedCorner = sc(0) /* CORNER_NONE */; + }; +}; diff --git a/src/layout/supplementary/WorkspaceAlgoMatcher.cpp b/src/layout/supplementary/WorkspaceAlgoMatcher.cpp new file mode 100644 index 000000000..98cdb773c --- /dev/null +++ b/src/layout/supplementary/WorkspaceAlgoMatcher.cpp @@ -0,0 +1,139 @@ +#include "WorkspaceAlgoMatcher.hpp" + +#include "../../config/ConfigValue.hpp" +#include "../../config/shared/workspace/WorkspaceRuleManager.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 = Config::workspaceRuleMgr()->getWorkspaceRuleFor(w); + return rule && rule->m_layout.has_value() ? rule->m_layout.value() : *PLAYOUT; +} + +SP CWorkspaceAlgoMatcher::createAlgorithmForWorkspace(PHLWORKSPACE w) { + return CAlgorithm::create(algoForNameTiled(tiledAlgoForWorkspace(w)), makeUnique(), w->m_space); +} + +void CWorkspaceAlgoMatcher::updateWorkspaceLayouts() { + // TODO: make this ID-based, string comparison is slow + for (const auto& ws : g_pCompositor->getWorkspaces()) { + if (!ws) + continue; + + const auto& TILED_ALGO = ws->m_space->algorithm()->tiledAlgo(); + + if (!TILED_ALGO) + continue; + + const auto LAYOUT_TO_USE = tiledAlgoForWorkspace(ws.lock()); + + if (m_algoNames.contains(&typeid(*TILED_ALGO.get())) && m_algoNames.at(&typeid(*TILED_ALGO.get())) == LAYOUT_TO_USE) + continue; + + // needs a switchup + ws->m_space->algorithm()->updateTiledAlgo(algoForNameTiled(LAYOUT_TO_USE)); + } +} + +std::string CWorkspaceAlgoMatcher::getNameForTiledAlgo(const std::type_info* type) { + if (m_algoNames.contains(type)) + return m_algoNames.at(type); + return "unknown"; +} diff --git a/src/layout/supplementary/WorkspaceAlgoMatcher.hpp b/src/layout/supplementary/WorkspaceAlgoMatcher.hpp new file mode 100644 index 000000000..d39e29988 --- /dev/null +++ b/src/layout/supplementary/WorkspaceAlgoMatcher.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "../../desktop/DesktopTypes.hpp" + +#include +#include +#include +#include + +namespace Layout { + class CAlgorithm; + class ITiledAlgorithm; + class IFloatingAlgorithm; +} + +namespace Layout::Supplementary { + class CWorkspaceAlgoMatcher { + public: + CWorkspaceAlgoMatcher(); + ~CWorkspaceAlgoMatcher() = default; + + SP createAlgorithmForWorkspace(PHLWORKSPACE w); + void updateWorkspaceLayouts(); + std::string getNameForTiledAlgo(const std::type_info* type); + + // these fns can fail due to name collisions + bool registerTiledAlgo(const std::string& name, const std::type_info* typeInfo, std::function()>&& factory); + bool registerFloatingAlgo(const std::string& name, const std::type_info* typeInfo, std::function()>&& factory); + + // this fn fails if the algo isn't registered + bool unregisterAlgo(const std::string& name); + + private: + UP algoForNameTiled(const std::string& s); + UP algoForNameFloat(const std::string& s); + + std::string tiledAlgoForWorkspace(const PHLWORKSPACE&); + + std::map()>> m_tiledAlgos; + std::map()>> m_floatingAlgos; + + std::map m_algoNames; + }; + + const UP& algoMatcher(); +} \ No newline at end of file diff --git a/src/layout/target/Target.cpp b/src/layout/target/Target.cpp new file mode 100644 index 000000000..e8ce48bd7 --- /dev/null +++ b/src/layout/target/Target.cpp @@ -0,0 +1,151 @@ +#include "Target.hpp" +#include "../space/Space.hpp" +#include "../../debug/log/Logger.hpp" + +#include + +using namespace Layout; +using namespace Hyprutils::Utils; + +void ITarget::setPositionGlobal(const STargetBox& box) { + m_box = box; + m_box.logicalBox.round(); + m_box.visualBox.round(); +} + +void ITarget::setPositionGlobal(const CBox& box) { + setPositionGlobal({.logicalBox = box}); +} + +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.logicalBox; +} + +void ITarget::rememberFloatingSize(const Vector2D& size) { + m_floatingSize = size; +} + +Vector2D ITarget::lastFloatingSize() const { + return m_floatingSize; +} + +void ITarget::recalc() { + setPositionGlobal(m_box); +} + +void ITarget::setPseudo(bool x) { + if (m_pseudo == x) + return; + + m_pseudo = x; + + recalc(); +} + +bool ITarget::isPseudo() const { + return m_pseudo; +} + +void ITarget::setPseudoSize(const Vector2D& size) { + m_pseudoSize = size; + + recalc(); +} + +Vector2D ITarget::pseudoSize() { + return m_pseudoSize; +} + +void ITarget::swap(SP b) { + const auto IS_FLOATING = floating(); + const auto IS_FLOATING_B = b->floating(); + + // Keep workspaces alive during a swap: moving one window will unref the ws + + // NOLINTNEXTLINE + const auto PWS1 = workspace(); + // NOLINTNEXTLINE + const auto PWS2 = b->workspace(); + + CScopeGuard x([&] { + b->setFloating(IS_FLOATING); + setFloating(IS_FLOATING_B); + + // update the spaces + b->onUpdateSpace(); + onUpdateSpace(); + }); + + if (b->space() == m_space) { + // simplest + m_space->swap(m_self.lock(), b); + m_space->recalculate(); + return; + } + + // spaces differ + if (m_space) + m_space->swap(m_self.lock(), b); + if (b->space()) + b->space()->swap(b, m_self.lock()); + + std::swap(m_space, b->m_space); + + // recalc both + if (m_space) + m_space->recalculate(); + if (b->space()) + b->space()->recalculate(); +} + +bool ITarget::wasTiling() const { + return m_wasTiling; +} + +void ITarget::setWasTiling(bool x) { + m_wasTiling = x; +} diff --git a/src/layout/target/Target.hpp b/src/layout/target/Target.hpp new file mode 100644 index 000000000..bae5eee5e --- /dev/null +++ b/src/layout/target/Target.hpp @@ -0,0 +1,85 @@ +#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; + }; + + struct STargetBox { + CBox logicalBox; + CBox visualBox; + }; + + class ITarget { + public: + virtual ~ITarget() = default; + + virtual eTargetType type() = 0; + + // position is within its space + virtual void setPositionGlobal(const STargetBox& box); + 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; + + STargetBox 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; + }; +}; diff --git a/src/layout/target/WindowGroupTarget.cpp b/src/layout/target/WindowGroupTarget.cpp new file mode 100644 index 000000000..8f9260e20 --- /dev/null +++ b/src/layout/target/WindowGroupTarget.cpp @@ -0,0 +1,93 @@ +#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 STargetBox& 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); + + if (space) + m_group->updateWorkspace(space->workspace()); +} + +bool CWindowGroupTarget::floating() { + return m_group->current()->m_target->floating(); +} + +void CWindowGroupTarget::setFloating(bool x) { + for (const auto& w : m_group->windows()) { + w->m_target->setFloating(x); + } +} + +std::expected CWindowGroupTarget::desiredGeometry() { + return m_group->current()->m_target->desiredGeometry(); +} + +PHLWINDOW CWindowGroupTarget::window() const { + return m_group->current(); +} + +eFullscreenMode CWindowGroupTarget::fullscreenMode() { + return m_group->current()->m_fullscreenState.internal; +} + +void CWindowGroupTarget::setFullscreenMode(eFullscreenMode mode) { + m_group->current()->m_fullscreenState.internal = mode; +} + +std::optional CWindowGroupTarget::minSize() { + return m_group->current()->minSize(); +} + +std::optional CWindowGroupTarget::maxSize() { + return m_group->current()->maxSize(); +} + +void CWindowGroupTarget::damageEntire() { + g_pHyprRenderer->damageWindow(m_group->current()); +} + +void CWindowGroupTarget::warpPositionSize() { + for (const auto& w : m_group->windows()) { + w->m_target->warpPositionSize(); + } +} + +void CWindowGroupTarget::onUpdateSpace() { + for (const auto& w : m_group->windows()) { + w->m_target->onUpdateSpace(); + } +} diff --git a/src/layout/target/WindowGroupTarget.hpp b/src/layout/target/WindowGroupTarget.hpp new file mode 100644 index 000000000..b3e797498 --- /dev/null +++ b/src/layout/target/WindowGroupTarget.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "Target.hpp" + +#include "../../desktop/view/Window.hpp" +#include "../../desktop/view/Group.hpp" + +namespace Layout { + + class CWindowGroupTarget : public ITarget { + public: + static SP create(SP g); + virtual ~CWindowGroupTarget() = default; + + virtual eTargetType type(); + + virtual void setPositionGlobal(const STargetBox& 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; + }; +}; diff --git a/src/layout/target/WindowTarget.cpp b/src/layout/target/WindowTarget.cpp new file mode 100644 index 000000000..cc6bfab47 --- /dev/null +++ b/src/layout/target/WindowTarget.cpp @@ -0,0 +1,387 @@ +#include "WindowTarget.hpp" + +#include "../space/Space.hpp" +#include "../algorithm/Algorithm.hpp" + +#include "../../protocols/core/Compositor.hpp" +#include "../../config/shared/workspace/WorkspaceRuleManager.hpp" +#include "../../helpers/Monitor.hpp" +#include "../../xwayland/XSurface.hpp" +#include "../../Compositor.hpp" +#include "../../render/Renderer.hpp" +#include "../../desktop/state/FloatState.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 STargetBox& 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.logicalBox.pos(); + m_window->m_size = m_box.logicalBox.size(); + + *m_window->m_realPosition = m_box.logicalBox.pos(); + *m_window->m_realSize = m_box.logicalBox.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({.logicalBox = m_space->workArea(floating())}); + + if (!m_space->workspace()) + return; + + const auto PMONITOR = m_space->workspace()->m_monitor; + const auto PWORKSPACE = m_space->workspace(); + const auto MONITOR_WORKAREA = m_space->workArea(); + + // get specific gaps and rules for this workspace, + // if user specified them in config + const auto WORKSPACERULE = Config::workspaceRuleMgr()->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()); + + CBox nodeBox = m_box.logicalBox; + nodeBox.round(); + + m_window->m_size = nodeBox.size(); + m_window->m_position = nodeBox.pos(); + + auto calcPos = m_box.visualBox.pos(); + auto calcSize = m_box.visualBox.size(); + + if (m_box.visualBox.empty()) { + calcPos = nodeBox.pos(); + calcSize = nodeBox.size(); + // for gaps outer + const bool DISPLAYLEFT = STICKS(m_box.logicalBox.x, MONITOR_WORKAREA.x); + const bool DISPLAYRIGHT = STICKS(m_box.logicalBox.x + m_box.logicalBox.w, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w); + const bool DISPLAYTOP = STICKS(m_box.logicalBox.y, MONITOR_WORKAREA.y); + const bool DISPLAYBOTTOM = STICKS(m_box.logicalBox.y + m_box.logicalBox.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.logicalBox.x, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w); + const bool DISPLAYINVERSERIGHT = STICKS(m_box.logicalBox.x + m_box.logicalBox.w, MONITOR_WORKAREA.x); + + static auto PGAPSINDATA = CConfigValue("general:gaps_in"); + auto* const PGAPSIN = sc((PGAPSINDATA.ptr())->getData()); + auto gapsIn = (WORKSPACERULE && WORKSPACERULE->m_gapsIn.has_value()) ? WORKSPACERULE->m_gapsIn.value() : *PGAPSIN; + + 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() ? Desktop::floatState()->get(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.logicalBox.size()); + + m_window->m_fullscreenState.internal = mode; +} + +std::optional CWindowTarget::minSize() { + return m_window->minSize(); +} + +std::optional CWindowTarget::maxSize() { + return m_window->maxSize(); +} + +void CWindowTarget::damageEntire() { + g_pHyprRenderer->damageWindow(m_window.lock()); +} + +void CWindowTarget::warpPositionSize() { + m_window->m_realSize->warp(); + m_window->m_realPosition->warp(); + m_window->updateWindowDecos(); +} + +void CWindowTarget::onUpdateSpace() { + if (!space()) + return; + + m_window->m_monitor = space()->workspace()->m_monitor; + m_window->moveToWorkspace(space()->workspace()); + m_window->updateToplevel(); + m_window->updateWindowData(); + m_window->updateWindowDecos(); +} diff --git a/src/layout/target/WindowTarget.hpp b/src/layout/target/WindowTarget.hpp new file mode 100644 index 000000000..e42f4d780 --- /dev/null +++ b/src/layout/target/WindowTarget.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "Target.hpp" + +#include "../../desktop/view/Window.hpp" + +namespace Layout { + + class CWindowTarget : public ITarget { + public: + static SP create(PHLWINDOW w); + virtual ~CWindowTarget() = default; + + virtual eTargetType type(); + + virtual void setPositionGlobal(const STargetBox& 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; + }; +}; diff --git a/src/macros.hpp b/src/macros.hpp index 7fa25cfb3..fc109296b 100644 --- a/src/macros.hpp +++ b/src/macros.hpp @@ -6,7 +6,7 @@ #include #include "helpers/memory/Memory.hpp" -#include "debug/Log.hpp" +#include "debug/log/Logger.hpp" #ifndef NDEBUG #ifdef HYPRLAND_DEBUG @@ -34,13 +34,6 @@ // max value 32 because killed is a int uniform #define POINTER_PRESSED_HISTORY_LENGTH 32 -#define LISTENER(name) \ - void listener_##name(wl_listener*, void*); \ - inline wl_listener listen_##name = {.notify = listener_##name} -#define DYNLISTENFUNC(name) void listener_##name(void*, void*) -#define DYNLISTENER(name) CHyprWLListener hyprListener_##name -#define DYNMULTILISTENER(name) wl_listener listen_##name - #define VECINRECT(vec, x1, y1, x2, y2) ((vec).x >= (x1) && (vec).x < (x2) && (vec).y >= (y1) && (vec).y < (y2)) #define VECNOTINRECT(vec, x1, y1, x2, y2) ((vec).x < (x1) || (vec).x >= (x2) || (vec).y < (y1) || (vec).y >= (y2)) @@ -52,9 +45,9 @@ #define RASSERT(expr, reason, ...) \ if (!(expr)) { \ - Debug::log(CRIT, "\n==========================================================================================\nASSERTION FAILED! \n\n{}\n\nat: line {} in {}", \ - std::format(reason, ##__VA_ARGS__), __LINE__, \ - ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })()); \ + Log::logger->log(Log::CRIT, "\n==========================================================================================\nASSERTION FAILED! \n\n{}\n\nat: line {} in {}", \ + std::format(reason, ##__VA_ARGS__), __LINE__, \ + ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })()); \ std::print("Assertion failed! See the log in /tmp/hypr/hyprland.log for more info."); \ raise(SIGABRT); \ } @@ -90,7 +83,7 @@ #if ISDEBUG #define UNREACHABLE() \ { \ - Debug::log(CRIT, "\n\nMEMORY CORRUPTED: Unreachable failed! (Reached an unreachable position, memory corruption!!!)"); \ + Log::logger->log(Log::CRIT, "\n\nMEMORY CORRUPTED: Unreachable failed! (Reached an unreachable position, memory corruption!!!)"); \ raise(SIGABRT); \ std::unreachable(); \ } @@ -103,10 +96,13 @@ #define GLCALL(__CALL__) \ { \ __CALL__; \ - auto err = glGetError(); \ - if (err != GL_NO_ERROR) { \ - Debug::log(ERR, "[GLES] Error in call at {}@{}: 0x{:x}", __LINE__, \ - ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })(), err); \ + static const auto GLDEBUG = CConfigValue("debug:gl_debugging"); \ + if (*GLDEBUG) { \ + auto err = glGetError(); \ + if (err != GL_NO_ERROR) { \ + Log::logger->log(Log::ERR, "[GLES] Error in call at {}@{}: 0x{:x}", __LINE__, \ + ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })(), err); \ + } \ } \ } diff --git a/src/main.cpp b/src/main.cpp index 2574d8222..566a2544b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,9 +1,10 @@ #include "defines.hpp" -#include "debug/Log.hpp" +#include "debug/log/Logger.hpp" #include "Compositor.hpp" -#include "config/ConfigManager.hpp" +#include "config/legacy/ConfigManager.hpp" #include "init/initHelpers.hpp" #include "debug/HyprCtl.hpp" +#include "helpers/env/Env.hpp" #include #include @@ -25,15 +26,18 @@ using namespace Hyprutils::Memory; static void help() { std::println("usage: Hyprland [arg [...]].\n"); - std::println(R"(Arguments: + std::println(R"#(Arguments: --help -h - Show this message again --config FILE -c FILE - Specify config file to use --socket NAME - Sets the Wayland socket name (for Wayland socket handover) --wayland-fd FD - Sets the Wayland socket fd (for Wayland socket handover) + --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 - --version -v - Print this binary's version)"); + --version -v - Print this binary's version + --version-json - Print this binary's version as json)#"); } static void reapZombieChildrenAutomatically() { @@ -67,7 +71,8 @@ int main(int argc, char** argv) { std::string configPath; std::string socketName; int socketFd = -1; - bool ignoreSudo = false, verifyConfig = false; + bool ignoreSudo = false, verifyConfig = false, safeMode = false; + int watchdogFd = -1; if (argc > 1) { std::span args{argv + 1, sc(argc - 1)}; @@ -130,7 +135,7 @@ int main(int argc, char** argv) { return 1; } - Debug::log(LOG, "User-specified config location: '{}'", configPath); + Log::logger->log(Log::DEBUG, "User-specified config location: '{}'", configPath); it++; @@ -142,12 +147,32 @@ int main(int argc, char** argv) { } else if (value == "-v" || value == "--version") { std::println("{}", versionRequest(eHyprCtlOutputFormat::FORMAT_NORMAL, "")); return 0; + } else if (value == "--version-json") { + std::println("{}", versionRequest(eHyprCtlOutputFormat::FORMAT_JSON, "")); + return 0; } else if (value == "--systeminfo") { std::println("{}", systemInfoRequest(eHyprCtlOutputFormat::FORMAT_NORMAL, "")); return 0; } else if (value == "--verify-config") { verifyConfig = true; continue; + } else if (value == "--safe-mode") { + safeMode = true; + continue; + } else if (value == "--watchdog-fd") { + if (std::next(it) == args.end()) { + help(); + return 1; + } + + try { + watchdogFd = std::stoi(*std::next(it)); + it++; + } catch (...) { + std::println(stderr, "[ ERROR ] Invalid fd for watchdog fd"); + help(); + return 1; + } } else { std::println(stderr, "[ ERROR ] Unknown option '{}' !", value); help(); @@ -189,15 +214,25 @@ int main(int argc, char** argv) { reapZombieChildrenAutomatically(); + bool watchdogOk = watchdogFd > 0; + + if (watchdogFd > 0) + watchdogOk = g_pCompositor->setWatchdogFd(watchdogFd); + if (safeMode) + g_pCompositor->m_safeMode = true; + + if (!watchdogOk && !verifyConfig) + Log::logger->log(Log::WARN, "WARNING: Hyprland is being launched without start-hyprland. This is highly advised against."); + g_pCompositor->initServer(socketName, socketFd); if (verifyConfig) - return !g_pConfigManager->m_lastConfigVerificationWasSuccessful; + return !Config::mgr()->configVerifPassed(); - if (!envEnabled("HYPRLAND_NO_RT")) + if (!Env::envEnabled("HYPRLAND_NO_RT")) NInit::gainRealTime(); - Debug::log(LOG, "Hyprland init finished."); + Log::logger->log(Log::DEBUG, "Hyprland init finished."); // If all's good to go, start. g_pCompositor->startCompositor(); @@ -206,7 +241,7 @@ int main(int argc, char** argv) { g_pCompositor.reset(); - Debug::log(LOG, "Hyprland has reached the end."); + Log::logger->log(Log::DEBUG, "Hyprland has reached the end."); return EXIT_SUCCESS; } diff --git a/src/managers/ANRManager.cpp b/src/managers/ANRManager.cpp index daab4d0a9..9f613df8a 100644 --- a/src/managers/ANRManager.cpp +++ b/src/managers/ANRManager.cpp @@ -1,14 +1,15 @@ #include "ANRManager.hpp" + #include "../helpers/fs/FsUtils.hpp" -#include "../debug/Log.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; @@ -16,7 +17,7 @@ static constexpr auto TIMER_TIMEOUT = std::chrono::milliseconds(1500); CANRManager::CANRManager() { if (!NFsUtils::executableExistsInPath("hyprland-dialog")) { - Debug::log(ERR, "hyprland-dialog missing from PATH, cannot start ANRManager"); + Log::logger->log(Log::ERR, "hyprland-dialog missing from PATH, cannot start ANRManager"); return; } @@ -25,10 +26,12 @@ CANRManager::CANRManager() { m_active = true; - static auto P = g_pHookSystem->hookDynamic("openWindow", [this](void* self, SCallbackInfo& info, std::any data) { - auto window = std::any_cast(data); - + static auto P = Event::bus()->m_events.window.open.listen([this](PHLWINDOW window) { for (const auto& d : m_data) { + // Window is ANR dialog + if (d->isRunning() && d->dialogBox->getPID() == window->getPID()) + return; + if (d->fitsWindow(window)) return; } @@ -36,9 +39,7 @@ CANRManager::CANRManager() { m_data.emplace_back(makeShared(window)); }); - static auto P1 = g_pHookSystem->hookDynamic("closeWindow", [this](void* self, SCallbackInfo& info, std::any data) { - auto window = std::any_cast(data); - + static auto P1 = Event::bus()->m_events.window.close.listen([this](PHLWINDOW window) { for (const auto& d : m_data) { if (!d->fitsWindow(window)) continue; @@ -48,8 +49,9 @@ CANRManager::CANRManager() { d->killDialog(); d->missedResponses = 0; d->dialogSaidWait = false; - return; } + + std::erase_if(m_data, [&window](auto& w) { return w == window; }); }); m_timer->updateTimeout(TIMER_TIMEOUT); @@ -84,7 +86,7 @@ void CANRManager::onTick() { if (data->missedResponses >= *PANRTHRESHOLD) { if (!data->isRunning() && !data->dialogSaidWait) { - data->runDialog(firstWindow->m_title, firstWindow->m_class, data->getPid()); + data->runDialog(firstWindow->m_title, firstWindow->m_class, data->getPID()); for (const auto& w : g_pCompositor->m_windows) { if (!w->m_isMapped) @@ -183,25 +185,29 @@ void CANRManager::SANRData::runDialog(const std::string& appName, const std::str const auto OPTION_TERMINATE_STR = I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_OPTION_TERMINATE, {}); const auto OPTION_WAIT_STR = I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_OPTION_WAIT, {}); + const auto OPTIONS = std::vector{OPTION_TERMINATE_STR, OPTION_WAIT_STR}; + const auto CLASS_STR = appClass.empty() ? I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_PROP_UNKNOWN, {}) : appClass; + const auto TITLE_STR = appName.empty() ? I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_PROP_UNKNOWN, {}) : appName; + const auto DESCRIPTION_STR = I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_CONTENT, {{"title", TITLE_STR}, {"class", CLASS_STR}}); - dialogBox = - CAsyncDialogBox::create(I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_TITLE, {}), - I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_CONTENT, - { - // - {"class", appClass.empty() ? I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_PROP_UNKNOWN, {}) : appClass}, // - {"title", appName.empty() ? I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_PROP_UNKNOWN, {}) : appName} // - }), - std::vector{ - // - OPTION_TERMINATE_STR, // - OPTION_WAIT_STR // - } // - ); + dialogBox = CAsyncDialogBox::create(I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_TITLE, {}), DESCRIPTION_STR, OPTIONS); + + for (const auto& w : g_pCompositor->m_windows) { + if (!w->m_isMapped) + continue; + + if (!fitsWindow(w)) + continue; + + if (w->m_workspace) + dialogBox->setExecRule(std::format("workspace {} silent", w->m_workspace->getConfigName())); + + break; + } dialogBox->open()->then([dialogWmPID, this, OPTION_TERMINATE_STR, OPTION_WAIT_STR](SP> r) { if (r->hasError()) { - Debug::log(ERR, "CANRManager::SANRData::runDialog: error spawning dialog"); + Log::logger->log(Log::ERR, "CANRManager::SANRData::runDialog: error spawning dialog"); return; } @@ -212,7 +218,7 @@ void CANRManager::SANRData::runDialog(const std::string& appName, const std::str else if (result.starts_with(OPTION_WAIT_STR)) dialogSaidWait = true; else - Debug::log(ERR, "CANRManager::SANRData::runDialog: lambda: unrecognized result: {}", result); + Log::logger->log(Log::ERR, "CANRManager::SANRData::runDialog: lambda: unrecognized result: {}", result); }); } @@ -240,7 +246,7 @@ bool CANRManager::SANRData::isDefunct() const { return xdgBase.expired() && xwaylandSurface.expired(); } -pid_t CANRManager::SANRData::getPid() const { +pid_t CANRManager::SANRData::getPID() const { if (xdgBase) { pid_t pid = 0; wl_client_get_credentials(xdgBase->client(), &pid, nullptr, nullptr); diff --git a/src/managers/ANRManager.hpp b/src/managers/ANRManager.hpp index 286e834f9..3880249dc 100644 --- a/src/managers/ANRManager.hpp +++ b/src/managers/ANRManager.hpp @@ -44,7 +44,7 @@ class CANRManager { void killDialog(); bool isDefunct() const; bool fitsWindow(PHLWINDOW pWindow) const; - pid_t getPid() const; + pid_t getPID() const; void ping(); }; @@ -57,4 +57,4 @@ class CANRManager { std::vector> m_data; }; -inline UP g_pANRManager; \ No newline at end of file +inline UP g_pANRManager; diff --git a/src/managers/CursorManager.cpp b/src/managers/CursorManager.cpp index d2905a1e3..7564ca753 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); @@ -16,7 +16,7 @@ static void hcLogger(enum eHyprcursorLogLevel level, char* message) { if (level == HC_LOG_TRACE) return; - Debug::log(NONE, "[hc] {}", message); + Log::logger->log(Log::DEBUG, "[hc] {}", message); } CCursorBuffer::CCursorBuffer(cairo_surface_t* surf, const Vector2D& size_, const Vector2D& hot_) : m_hotspot(hot_), m_stride(cairo_image_surface_get_stride(surf)) { @@ -83,11 +83,11 @@ CCursorManager::CCursorManager() { } if (m_size <= 0) { - Debug::log(WARN, "HYPRCURSOR_SIZE size not set, defaulting to size 24"); + Log::logger->log(Log::WARN, "HYPRCURSOR_SIZE size not set, defaulting to size 24"); m_size = 24; } } else { - Debug::log(ERR, "Hyprcursor failed loading theme \"{}\", falling back to Xcursor.", m_theme); + Log::logger->log(Log::ERR, "Hyprcursor failed loading theme \"{}\", falling back to Xcursor.", m_theme); auto const* SIZE = getenv("XCURSOR_SIZE"); if (SIZE) { @@ -97,7 +97,7 @@ CCursorManager::CCursorManager() { } if (m_size <= 0) { - Debug::log(WARN, "XCURSOR_SIZE size not set, defaulting to size 24"); + Log::logger->log(Log::WARN, "XCURSOR_SIZE size not set, defaulting to size 24"); m_size = 24; } } @@ -111,7 +111,7 @@ CCursorManager::CCursorManager() { updateTheme(); - static auto P = g_pHookSystem->hookDynamic("monitorLayoutChanged", [this](void* self, SCallbackInfo& info, std::any param) { this->updateTheme(); }); + static auto P = Event::bus()->m_events.monitor.layoutChanged.listen([this] { this->updateTheme(); }); } CCursorManager::~CCursorManager() { @@ -128,7 +128,7 @@ SP CCursorManager::getCursorBuffer() { return !m_cursorBuffers.empty() ? m_cursorBuffers.back() : nullptr; } -void CCursorManager::setCursorSurface(SP surf, const Vector2D& hotspot) { +void CCursorManager::setCursorSurface(SP surf, const Vector2D& hotspot) { if (!surf || !surf->resource()) g_pPointerManager->resetCursorImage(); else @@ -203,7 +203,7 @@ void CCursorManager::setCursorFromName(const std::string& name) { } if (m_currentCursorShapeData.images.empty()) { - Debug::log(ERR, "BUG THIS: No fallback found for a cursor in setCursorFromName"); + Log::logger->log(Log::ERR, "BUG THIS: No fallback found for a cursor in setCursorFromName"); return false; } } @@ -328,7 +328,7 @@ bool CCursorManager::changeTheme(const std::string& name, const int size) { m_hyprcursor = makeUnique(m_theme.empty() ? nullptr : m_theme.c_str(), options); if (!m_hyprcursor->valid()) { - Debug::log(ERR, "Hyprcursor failed loading theme \"{}\", falling back to XCursor.", m_theme); + Log::logger->log(Log::ERR, "Hyprcursor failed loading theme \"{}\", falling back to XCursor.", m_theme); m_xcursor->loadTheme(m_theme.empty() ? xcursor_theme : m_theme, m_size, m_cursorScale); } } else diff --git a/src/managers/CursorManager.hpp b/src/managers/CursorManager.hpp index dd3238af4..f4c42d30d 100644 --- a/src/managers/CursorManager.hpp +++ b/src/managers/CursorManager.hpp @@ -3,6 +3,7 @@ #include #include #include "../includes.hpp" +#include "../desktop/view/WLSurface.hpp" #include "../helpers/math/Math.hpp" #include "../helpers/memory/Memory.hpp" #include "../macros.hpp" @@ -10,8 +11,6 @@ #include "managers/XCursorManager.hpp" #include -class CWLSurface; - AQUAMARINE_FORWARD(IBuffer); class CCursorBuffer : public Aquamarine::IBuffer { @@ -43,7 +42,7 @@ class CCursorManager { SP getCursorBuffer(); void setCursorFromName(const std::string& name); - void setCursorSurface(SP surf, const Vector2D& hotspot); + void setCursorSurface(SP surf, const Vector2D& hotspot); void setCursorBuffer(SP buf, const Vector2D& hotspot, const float& scale); void setAnimationTimer(const int& frame, const int& delay); diff --git a/src/managers/DonationNagManager.cpp b/src/managers/DonationNagManager.cpp index 826439b2c..62dd15b70 100644 --- a/src/managers/DonationNagManager.cpp +++ b/src/managers/DonationNagManager.cpp @@ -1,5 +1,5 @@ #include "DonationNagManager.hpp" -#include "../debug/Log.hpp" +#include "../debug/log/Logger.hpp" #include "VersionKeeperManager.hpp" #include "eventLoop/EventLoopManager.hpp" #include "../config/ConfigValue.hpp" @@ -69,12 +69,12 @@ CDonationNagManager::CDonationNagManager() { // don't nag if the last nag was less than a month ago. This is // mostly for first-time nags, as other nags happen in specific time frames shorter than a month if (EPOCH - state.epoch < MONTH_IN_SECONDS) { - Debug::log(LOG, "DonationNag: last nag was {} days ago, too early for a nag.", sc(std::round((EPOCH - state.epoch) / sc(DAY_IN_SECONDS)))); + Log::logger->log(Log::DEBUG, "DonationNag: last nag was {} days ago, too early for a nag.", sc(std::round((EPOCH - state.epoch) / sc(DAY_IN_SECONDS)))); return; } if (!NFsUtils::executableExistsInPath("hyprland-donate-screen")) { - Debug::log(ERR, "DonationNag: executable doesn't exist, skipping."); + Log::logger->log(Log::ERR, "DonationNag: executable doesn't exist, skipping."); return; } @@ -91,7 +91,7 @@ CDonationNagManager::CDonationNagManager() { if (DAY < nagPoint.dayStart || DAY > nagPoint.dayEnd) continue; - Debug::log(LOG, "DonationNag: hit nag month {} days {}-{}, it's {} today, nagging", MONTH, nagPoint.dayStart, nagPoint.dayEnd, DAY); + Log::logger->log(Log::DEBUG, "DonationNag: hit nag month {} days {}-{}, it's {} today, nagging", MONTH, nagPoint.dayStart, nagPoint.dayEnd, DAY); fire(); @@ -103,10 +103,10 @@ CDonationNagManager::CDonationNagManager() { } if (!m_fired) - Debug::log(LOG, "DonationNag: didn't hit any nagging periods, checking update"); + Log::logger->log(Log::DEBUG, "DonationNag: didn't hit any nagging periods, checking update"); if (state.major < currentMajor) { - Debug::log(LOG, "DonationNag: hit nag for major update {} -> {}", state.major, currentMajor); + Log::logger->log(Log::DEBUG, "DonationNag: hit nag for major update {} -> {}", state.major, currentMajor); fire(); @@ -116,7 +116,7 @@ CDonationNagManager::CDonationNagManager() { } if (!m_fired) - Debug::log(LOG, "DonationNag: didn't hit nagging conditions"); + Log::logger->log(Log::DEBUG, "DonationNag: didn't hit nagging conditions"); } bool CDonationNagManager::fired() { diff --git a/src/managers/EventManager.cpp b/src/managers/EventManager.cpp index c4c6c5d78..6d42a8185 100644 --- a/src/managers/EventManager.cpp +++ b/src/managers/EventManager.cpp @@ -13,27 +13,27 @@ using namespace Hyprutils::OS; CEventManager::CEventManager() : m_socketFD(socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) { if (!m_socketFD.isValid()) { - Debug::log(ERR, "Couldn't start the Hyprland Socket 2. (1) IPC will not work."); + Log::logger->log(Log::ERR, "Couldn't start the Hyprland Socket 2. (1) IPC will not work."); return; } sockaddr_un SERVERADDRESS = {.sun_family = AF_UNIX}; const auto PATH = g_pCompositor->m_instancePath + "/.socket2.sock"; if (PATH.length() > sizeof(SERVERADDRESS.sun_path) - 1) { - Debug::log(ERR, "Socket2 path is too long. (2) IPC will not work."); + Log::logger->log(Log::ERR, "Socket2 path is too long. (2) IPC will not work."); return; } strncpy(SERVERADDRESS.sun_path, PATH.c_str(), sizeof(SERVERADDRESS.sun_path) - 1); if (bind(m_socketFD.get(), rc(&SERVERADDRESS), SUN_LEN(&SERVERADDRESS)) < 0) { - Debug::log(ERR, "Couldn't bind the Hyprland Socket 2. (3) IPC will not work."); + Log::logger->log(Log::ERR, "Couldn't bind the Hyprland Socket 2. (3) IPC will not work."); return; } // 10 max queued. if (listen(m_socketFD.get(), 10) < 0) { - Debug::log(ERR, "Couldn't listen on the Hyprland Socket 2. (4) IPC will not work."); + Log::logger->log(Log::ERR, "Couldn't listen on the Hyprland Socket 2. (4) IPC will not work."); return; } @@ -59,7 +59,7 @@ int CEventManager::onClientEvent(int fd, uint32_t mask, void* data) { int CEventManager::onServerEvent(int fd, uint32_t mask) { if (mask & WL_EVENT_ERROR || mask & WL_EVENT_HANGUP) { - Debug::log(ERR, "Socket2 hangup?? IPC broke"); + Log::logger->log(Log::ERR, "Socket2 hangup?? IPC broke"); wl_event_source_remove(m_eventSource); m_eventSource = nullptr; @@ -73,7 +73,7 @@ int CEventManager::onServerEvent(int fd, uint32_t mask) { CFileDescriptor ACCEPTEDCONNECTION{accept4(m_socketFD.get(), rc(&clientAddress), &clientSize, SOCK_CLOEXEC | SOCK_NONBLOCK)}; if (!ACCEPTEDCONNECTION.isValid()) { if (errno != EAGAIN) { - Debug::log(ERR, "Socket2 failed receiving connection, errno: {}", errno); + Log::logger->log(Log::ERR, "Socket2 failed receiving connection, errno: {}", errno); wl_event_source_remove(m_eventSource); m_eventSource = nullptr; m_socketFD.reset(); @@ -82,7 +82,7 @@ int CEventManager::onServerEvent(int fd, uint32_t mask) { return 0; } - Debug::log(LOG, "Socket2 accepted a new client at FD {}", ACCEPTEDCONNECTION.get()); + Log::logger->log(Log::DEBUG, "Socket2 accepted a new client at FD {}", ACCEPTEDCONNECTION.get()); // add to event loop so we can close it when we need to auto* eventSource = wl_event_loop_add_fd(g_pCompositor->m_wlEventLoop, ACCEPTEDCONNECTION.get(), 0, onServerEvent, nullptr); @@ -97,7 +97,7 @@ int CEventManager::onServerEvent(int fd, uint32_t mask) { int CEventManager::onClientEvent(int fd, uint32_t mask) { if (mask & WL_EVENT_ERROR || mask & WL_EVENT_HANGUP) { - Debug::log(LOG, "Socket2 fd {} hung up", fd); + Log::logger->log(Log::DEBUG, "Socket2 fd {} hung up", fd); removeClientByFD(fd); return 0; } @@ -142,7 +142,7 @@ std::string CEventManager::formatEvent(const SHyprIPCEvent& event) const { void CEventManager::postEvent(const SHyprIPCEvent& event) { if (g_pCompositor->m_isShuttingDown) { - Debug::log(WARN, "Suppressed (shutting down) event of type {}, content: {}", event.event, event.data); + Log::logger->log(Log::WARN, "Suppressed (shutting down) event of type {}, content: {}", event.event, event.data); return; } @@ -154,7 +154,7 @@ void CEventManager::postEvent(const SHyprIPCEvent& event) { if (QUEUESIZE > 0 || write(it->fd.get(), sharedEvent->c_str(), sharedEvent->length()) < 0) { if (QUEUESIZE >= MAX_QUEUED_EVENTS) { // too many events queued, remove the client - Debug::log(ERR, "Socket2 fd {} overflowed event queue, removing", it->fd.get()); + Log::logger->log(Log::ERR, "Socket2 fd {} overflowed event queue, removing", it->fd.get()); it = removeClientByFD(it->fd.get()); continue; } diff --git a/src/managers/HookSystemManager.cpp b/src/managers/HookSystemManager.cpp deleted file mode 100644 index a5623f08b..000000000 --- a/src/managers/HookSystemManager.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "HookSystemManager.hpp" - -#include "../plugins/PluginSystem.hpp" - -CHookSystemManager::CHookSystemManager() { - ; // -} - -// returns the pointer to the function -SP CHookSystemManager::hookDynamic(const std::string& event, HOOK_CALLBACK_FN fn, HANDLE handle) { - SP hookFN = makeShared(fn); - m_registeredHooks[event].emplace_back(SCallbackFNPtr{.fn = hookFN, .handle = handle}); - return hookFN; -} - -void CHookSystemManager::unhook(SP fn) { - for (auto& [k, v] : m_registeredHooks) { - std::erase_if(v, [&](const auto& other) { - SP fn_ = other.fn.lock(); - - return fn_.get() == fn.get(); - }); - } -} - -void CHookSystemManager::emit(std::vector* const callbacks, SCallbackInfo& info, std::any data) { - if (callbacks->empty()) - return; - - std::vector faultyHandles; - volatile bool needsDeadCleanup = false; - - for (auto const& cb : *callbacks) { - - m_currentEventPlugin = false; - - if (!cb.handle) { - // we don't guard hl hooks - - if (SP fn = cb.fn.lock()) - (*fn)(fn.get(), info, data); - else - needsDeadCleanup = true; - continue; - } - - m_currentEventPlugin = true; - - if (std::ranges::find(faultyHandles, cb.handle) != faultyHandles.end()) - continue; - - try { - if (!setjmp(m_hookFaultJumpBuf)) { - if (SP fn = cb.fn.lock()) - (*fn)(fn.get(), info, data); - else - needsDeadCleanup = true; - } else { - // this module crashed. - throw std::exception(); - } - } catch (std::exception& e) { - // TODO: this works only once...? - faultyHandles.push_back(cb.handle); - Debug::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)) - Debug::log(LOG, "[hookSystem] New hook event registered: {}", event); - - return &m_registeredHooks[event]; -} diff --git a/src/managers/HookSystemManager.hpp b/src/managers/HookSystemManager.hpp deleted file mode 100644 index 647e96703..000000000 --- a/src/managers/HookSystemManager.hpp +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#include "../defines.hpp" - -#include -#include -#include -#include - -#include - -#define HANDLE void* - -// global type alias for hooked functions. Passes itself as a ptr when called, and `data` additionally. - -using HOOK_CALLBACK_FN = std::function; - -struct SCallbackFNPtr { - WP fn; - HANDLE handle = nullptr; -}; - -#define EMIT_HOOK_EVENT(name, param) \ - { \ - static auto* const PEVENTVEC = g_pHookSystem->getVecForEvent(name); \ - SCallbackInfo info; \ - g_pHookSystem->emit(PEVENTVEC, info, param); \ - } - -#define EMIT_HOOK_EVENT_CANCELLABLE(name, param) \ - { \ - static auto* const PEVENTVEC = g_pHookSystem->getVecForEvent(name); \ - SCallbackInfo info; \ - g_pHookSystem->emit(PEVENTVEC, info, param); \ - if (info.cancelled) \ - return; \ - } - -class CHookSystemManager { - public: - CHookSystemManager(); - - // returns the pointer to the function. - // losing this pointer (letting it get destroyed) - // will equal to unregistering the callback. - [[nodiscard("Losing this pointer instantly unregisters the callback")]] SP hookDynamic(const std::string& event, HOOK_CALLBACK_FN fn, - HANDLE handle = nullptr); - void unhook(SP fn); - - void emit(std::vector* const callbacks, SCallbackInfo& info, std::any data = 0); - std::vector* getVecForEvent(const std::string& event); - - bool m_currentEventPlugin = false; - jmp_buf m_hookFaultJumpBuf; - - private: - std::unordered_map> m_registeredHooks; -}; - -inline UP g_pHookSystem; diff --git a/src/managers/KeybindManager.cpp b/src/managers/KeybindManager.cpp index 4423bbdb7..9bd7ec971 100644 --- a/src/managers/KeybindManager.cpp +++ b/src/managers/KeybindManager.cpp @@ -1,6 +1,10 @@ #include "../config/ConfigValue.hpp" +#include "../config/legacy/ConfigManager.hpp" +#include "../config/shared/monitor/MonitorRuleManager.hpp" #include "../devices/IKeyboard.hpp" #include "../desktop/state/FocusState.hpp" +#include "../desktop/history/WindowHistoryTracker.hpp" +#include "../desktop/history/WorkspaceHistoryTracker.hpp" #include "../managers/SeatManager.hpp" #include "../protocols/LayerShell.hpp" #include "../protocols/ShortcutsInhibit.hpp" @@ -13,17 +17,24 @@ #include "Compositor.hpp" #include "TokenManager.hpp" #include "eventLoop/EventLoopManager.hpp" -#include "debug/Log.hpp" -#include "../managers/HookSystemManager.hpp" +#include "debug/log/Logger.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 "../errorOverlay/Overlay.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 "../config/supplementary/executor/Executor.hpp" #include #include @@ -33,6 +44,8 @@ #include #include +#include +#include #include using namespace Hyprutils::String; using namespace Hyprutils::OS; @@ -48,31 +61,6 @@ using namespace Hyprutils::OS; #include #endif -static std::vector> getHyprlandLaunchEnv(PHLWORKSPACE pInitialWorkspace) { - static auto PINITIALWSTRACKING = CConfigValue("misc:initial_workspace_tracking"); - - if (!*PINITIALWSTRACKING || g_pConfigManager->m_isLaunchingExecOnce) - return {}; - - const auto PMONITOR = Desktop::focusState()->monitor(); - if (!PMONITOR || !PMONITOR->m_activeWorkspace) - return {}; - - std::vector> result; - - if (!pInitialWorkspace) { - if (PMONITOR->m_activeSpecialWorkspace) - pInitialWorkspace = PMONITOR->m_activeSpecialWorkspace; - else - pInitialWorkspace = PMONITOR->m_activeWorkspace; - } - - result.push_back(std::make_pair<>("HL_INITIAL_WORKSPACE_TOKEN", - g_pTokenManager->registerNewToken(SInitialWorkspaceToken{{}, pInitialWorkspace->getConfigName()}, std::chrono::months(1337)))); - - return result; -} - CKeybindManager::CKeybindManager() { // initialize all dispatchers @@ -101,9 +89,6 @@ CKeybindManager::CKeybindManager() { m_dispatchers["togglegroup"] = toggleGroup; m_dispatchers["changegroupactive"] = changeGroupActive; m_dispatchers["movegroupwindow"] = moveGroupWindow; - m_dispatchers["togglesplit"] = toggleSplit; - m_dispatchers["swapsplit"] = swapSplit; - m_dispatchers["splitratio"] = alterSplitRatio; m_dispatchers["focusmonitor"] = focusMonitor; m_dispatchers["movecursortocorner"] = moveCursorToCorner; m_dispatchers["movecursor"] = moveCursor; @@ -140,6 +125,7 @@ CKeybindManager::CKeybindManager() { m_dispatchers["lockgroups"] = lockGroups; m_dispatchers["lockactivegroup"] = lockActiveGroup; m_dispatchers["moveintogroup"] = moveIntoGroup; + m_dispatchers["moveintoorcreategroup"] = moveIntoOrCreateGroup; m_dispatchers["moveoutofgroup"] = moveOutOfGroup; m_dispatchers["movewindoworgroup"] = moveWindowOrGroup; m_dispatchers["setignoregrouplock"] = setIgnoreGroupLock; @@ -163,7 +149,7 @@ CKeybindManager::CKeybindManager() { const auto DISPATCHER = g_pKeybindManager->m_dispatchers.find(m_lastLongPressKeybind->handler); - Debug::log(LOG, "Long press timeout passed, calling dispatcher."); + Log::logger->log(Log::DEBUG, "Long press timeout passed, calling dispatcher."); DISPATCHER->second(m_lastLongPressKeybind->arg); }, nullptr); @@ -181,7 +167,7 @@ CKeybindManager::CKeybindManager() { for (const auto& k : m_activeKeybinds) { const auto DISPATCHER = g_pKeybindManager->m_dispatchers.find(k->handler); - Debug::log(LOG, "Keybind repeat triggered, calling dispatcher."); + Log::logger->log(Log::DEBUG, "Keybind repeat triggered, calling dispatcher."); DISPATCHER->second(k->arg); } @@ -195,8 +181,7 @@ CKeybindManager::CKeybindManager() { g_pEventLoopManager->addTimer(m_repeatKeyTimer); } - static auto P = g_pHookSystem->hookDynamic("configReloaded", [this](void* hk, SCallbackInfo& info, std::any param) { - // clear cuz realloc'd + static auto P = Event::bus()->m_events.config.reloaded.listen([this] { m_activeKeybinds.clear(); m_lastLongPressKeybind.reset(); m_pressedSpecialBinds.clear(); @@ -295,7 +280,7 @@ void CKeybindManager::updateXKBTranslationState() { xkb_rule_names rules = {.rules = RULES.c_str(), .model = MODEL.c_str(), .layout = LAYOUT.c_str(), .variant = VARIANT.c_str(), .options = OPTIONS.c_str()}; const auto PCONTEXT = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - FILE* const KEYMAPFILE = FILEPATH.empty() ? nullptr : fopen(absolutePath(FILEPATH, g_pConfigManager->m_configCurrentPath).c_str(), "r"); + FILE* const KEYMAPFILE = FILEPATH.empty() ? nullptr : fopen(absolutePath(FILEPATH, Config::mgr()->currentConfigPath()).c_str(), "r"); auto PKEYMAP = KEYMAPFILE ? xkb_keymap_new_from_file(PCONTEXT, KEYMAPFILE, XKB_KEYMAP_FORMAT_TEXT_V2, XKB_KEYMAP_COMPILE_NO_FLAGS) : xkb_keymap_new_from_names2(PCONTEXT, &rules, XKB_KEYMAP_FORMAT_TEXT_V2, XKB_KEYMAP_COMPILE_NO_FLAGS); @@ -303,12 +288,12 @@ void CKeybindManager::updateXKBTranslationState() { fclose(KEYMAPFILE); if (!PKEYMAP) { - g_pHyprError->queueCreate("[Runtime Error] Invalid keyboard layout passed. ( rules: " + RULES + ", model: " + MODEL + ", variant: " + VARIANT + ", options: " + OPTIONS + - ", layout: " + LAYOUT + " )", - CHyprColor(1.0, 50.0 / 255.0, 50.0 / 255.0, 1.0)); + ErrorOverlay::overlay()->queueCreate("[Runtime Error] Invalid keyboard layout passed. ( rules: " + RULES + ", model: " + MODEL + ", variant: " + VARIANT + + ", options: " + OPTIONS + ", layout: " + LAYOUT + " )", + ErrorOverlay::Colors::ERROR); - Debug::log(ERR, "[XKBTranslationState] Keyboard layout {} with variant {} (rules: {}, model: {}, options: {}) couldn't have been loaded.", rules.layout, rules.variant, - rules.rules, rules.model, rules.options); + Log::logger->log(Log::ERR, "[XKBTranslationState] Keyboard layout {} with variant {} (rules: {}, model: {}, options: {}) couldn't have been loaded.", rules.layout, + rules.variant, rules.rules, rules.model, rules.options); memset(&rules, 0, sizeof(rules)); PKEYMAP = xkb_keymap_new_from_names2(PCONTEXT, &rules, XKB_KEYMAP_FORMAT_TEXT_V2, XKB_KEYMAP_COMPILE_NO_FLAGS); @@ -320,10 +305,10 @@ void CKeybindManager::updateXKBTranslationState() { } bool CKeybindManager::ensureMouseBindState() { - if (!g_pInputManager->m_currentlyDraggedWindow) + if (!g_layoutManager->dragController()->target()) return false; - if (!g_pInputManager->m_currentlyDraggedWindow.expired()) { + if (g_layoutManager->dragController()->target()) { changeMouseBindMode(MBIND_INVALID); return true; } @@ -349,7 +334,7 @@ bool CKeybindManager::tryMoveFocusToMonitor(PHLMONITOR monitor) { if (!LASTMONITOR) return false; if (LASTMONITOR == monitor) { - Debug::log(LOG, "Tried to move to active monitor"); + Log::logger->log(Log::DEBUG, "Tried to move to active monitor"); return false; } @@ -360,14 +345,13 @@ bool CKeybindManager::tryMoveFocusToMonitor(PHLMONITOR monitor) { const auto PNEWMAINWORKSPACE = monitor->m_activeWorkspace; g_pInputManager->unconstrainMouse(); - PNEWMAINWORKSPACE->rememberPrevWorkspace(PWORKSPACE); const auto PNEWWORKSPACE = monitor->m_activeSpecialWorkspace ? monitor->m_activeSpecialWorkspace : PNEWMAINWORKSPACE; const auto PNEWWINDOW = PNEWWORKSPACE->getLastFocusedWindow(); if (PNEWWINDOW) { updateRelativeCursorCoords(); - Desktop::focusState()->fullWindowFocus(PNEWWINDOW); + Desktop::focusState()->fullWindowFocus(PNEWWINDOW, Desktop::FOCUS_REASON_KEYBIND); PNEWWINDOW->warpCursor(); if (*PNOWARPS == 0 || *PFOLLOWMOUSE < 2) { @@ -376,7 +360,7 @@ bool CKeybindManager::tryMoveFocusToMonitor(PHLMONITOR monitor) { g_pInputManager->m_forcedFocus.reset(); } } else { - Desktop::focusState()->rawWindowFocus(nullptr); + Desktop::focusState()->rawWindowFocus(nullptr, Desktop::FOCUS_REASON_KEYBIND); g_pCompositor->warpCursorTo(monitor->middle()); } Desktop::focusState()->rawMonitorFocus(monitor); @@ -384,7 +368,7 @@ bool CKeybindManager::tryMoveFocusToMonitor(PHLMONITOR monitor) { return true; } -void CKeybindManager::switchToWindow(PHLWINDOW PWINDOWTOCHANGETO, bool preserveFocusHistory, bool forceFSCycle) { +void CKeybindManager::switchToWindow(PHLWINDOW PWINDOWTOCHANGETO, bool forceFSCycle) { static auto PFOLLOWMOUSE = CConfigValue("input:follow_mouse"); static auto PNOWARPS = CConfigValue("cursor:no_warps"); @@ -397,10 +381,10 @@ void CKeybindManager::switchToWindow(PHLWINDOW PWINDOWTOCHANGETO, bool preserveF g_pInputManager->unconstrainMouse(); if (PLASTWINDOW && PLASTWINDOW->m_workspace == PWINDOWTOCHANGETO->m_workspace && PLASTWINDOW->isFullscreen()) - Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, nullptr, preserveFocusHistory, forceFSCycle); + Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, Desktop::FOCUS_REASON_KEYBIND, nullptr, forceFSCycle); else { updateRelativeCursorCoords(); - Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, nullptr, preserveFocusHistory, forceFSCycle); + Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, Desktop::FOCUS_REASON_KEYBIND, nullptr, forceFSCycle); PWINDOWTOCHANGETO->warpCursor(); // Move mouse focus to the new window if required by current follow_mouse and warp modes @@ -429,7 +413,7 @@ bool CKeybindManager::onKeyEvent(std::any event, SP pKeyboard) { return true; if (!m_xkbTranslationState) { - Debug::log(ERR, "BUG THIS: m_pXKBTranslationState nullptr!"); + Log::logger->log(Log::ERR, "BUG THIS: m_pXKBTranslationState nullptr!"); updateXKBTranslationState(); if (!m_xkbTranslationState) @@ -477,7 +461,7 @@ bool CKeybindManager::onKeyEvent(std::any event, SP pKeyboard) { m_pressedKeys.push_back(KEY); - suppressEvent = !handleKeybinds(MODS, KEY, true, pKeyboard).passEvent; + suppressEvent = !handleKeybinds(MODS, KEY, true, pKeyboard, pKeyboard).passEvent; if (suppressEvent) shadowKeybinds(keysym, KEYCODE); @@ -488,7 +472,7 @@ bool CKeybindManager::onKeyEvent(std::any event, SP pKeyboard) { bool foundInPressedKeys = false; for (auto it = m_pressedKeys.begin(); it != m_pressedKeys.end();) { if (it->keycode == KEYCODE) { - handleKeybinds(MODS, *it, false, pKeyboard); + handleKeybinds(MODS, *it, false, pKeyboard, pKeyboard); foundInPressedKeys = true; suppressEvent = !it->sent; it = m_pressedKeys.erase(it); @@ -497,9 +481,9 @@ bool CKeybindManager::onKeyEvent(std::any event, SP pKeyboard) { } } if (!foundInPressedKeys) { - Debug::log(ERR, "BUG THIS: key not found in m_dPressedKeys"); + Log::logger->log(Log::ERR, "BUG THIS: key not found in m_dPressedKeys"); // fallback with wrong `KEY.modmaskAtPressTime`, this can be buggy - suppressEvent = !handleKeybinds(MODS, KEY, false, pKeyboard).passEvent; + suppressEvent = !handleKeybinds(MODS, KEY, false, pKeyboard, pKeyboard).passEvent; } shadowKeybinds(); @@ -508,7 +492,7 @@ bool CKeybindManager::onKeyEvent(std::any event, SP pKeyboard) { return !suppressEvent && !mouseBindWasActive; } -bool CKeybindManager::onAxisEvent(const IPointer::SAxisEvent& e) { +bool CKeybindManager::onAxisEvent(const IPointer::SAxisEvent& e, SP pointer) { const auto MODS = g_pInputManager->getModsFromAllKBs(); static auto PDELAY = CConfigValue("binds:scroll_event_delay"); @@ -523,14 +507,14 @@ bool CKeybindManager::onAxisEvent(const IPointer::SAxisEvent& e) { bool found = false; if (e.source == WL_POINTER_AXIS_SOURCE_WHEEL && e.axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { if (e.delta < 0) - found = !handleKeybinds(MODS, SPressedKeyWithMods{.keyName = "mouse_down"}, true, nullptr).passEvent; + found = !handleKeybinds(MODS, SPressedKeyWithMods{.keyName = "mouse_down"}, true, nullptr, pointer).passEvent; else - found = !handleKeybinds(MODS, SPressedKeyWithMods{.keyName = "mouse_up"}, true, nullptr).passEvent; + found = !handleKeybinds(MODS, SPressedKeyWithMods{.keyName = "mouse_up"}, true, nullptr, pointer).passEvent; } else if (e.source == WL_POINTER_AXIS_SOURCE_WHEEL && e.axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) { if (e.delta < 0) - found = !handleKeybinds(MODS, SPressedKeyWithMods{.keyName = "mouse_left"}, true, nullptr).passEvent; + found = !handleKeybinds(MODS, SPressedKeyWithMods{.keyName = "mouse_left"}, true, nullptr, pointer).passEvent; else - found = !handleKeybinds(MODS, SPressedKeyWithMods{.keyName = "mouse_right"}, true, nullptr).passEvent; + found = !handleKeybinds(MODS, SPressedKeyWithMods{.keyName = "mouse_right"}, true, nullptr, pointer).passEvent; } if (found) @@ -539,7 +523,7 @@ bool CKeybindManager::onAxisEvent(const IPointer::SAxisEvent& e) { return !found; } -bool CKeybindManager::onMouseEvent(const IPointer::SButtonEvent& e) { +bool CKeybindManager::onMouseEvent(const IPointer::SButtonEvent& e, SP mouse) { const auto MODS = g_pInputManager->getModsFromAllKBs(); bool suppressEvent = false; @@ -563,7 +547,7 @@ bool CKeybindManager::onMouseEvent(const IPointer::SButtonEvent& e) { if (e.state == WL_POINTER_BUTTON_STATE_PRESSED) { m_pressedKeys.push_back(KEY); - suppressEvent = !handleKeybinds(MODS, KEY, true, nullptr).passEvent; + suppressEvent = !handleKeybinds(MODS, KEY, true, nullptr, mouse).passEvent; if (suppressEvent) shadowKeybinds(); @@ -573,7 +557,7 @@ bool CKeybindManager::onMouseEvent(const IPointer::SButtonEvent& e) { bool foundInPressedKeys = false; for (auto it = m_pressedKeys.begin(); it != m_pressedKeys.end();) { if (it->keyName == KEY_NAME) { - suppressEvent = !handleKeybinds(MODS, *it, false, nullptr).passEvent; + suppressEvent = !handleKeybinds(MODS, *it, false, nullptr, mouse).passEvent; foundInPressedKeys = true; suppressEvent = !it->sent; it = m_pressedKeys.erase(it); @@ -582,9 +566,9 @@ bool CKeybindManager::onMouseEvent(const IPointer::SButtonEvent& e) { } } if (!foundInPressedKeys) { - Debug::log(ERR, "BUG THIS: key not found in m_dPressedKeys (2)"); + Log::logger->log(Log::ERR, "BUG THIS: key not found in m_dPressedKeys (2)"); // fallback with wrong `KEY.modmaskAtPressTime`, this can be buggy - suppressEvent = !handleKeybinds(MODS, KEY, false, nullptr).passEvent; + suppressEvent = !handleKeybinds(MODS, KEY, false, nullptr, mouse).passEvent; } shadowKeybinds(); @@ -598,15 +582,15 @@ void CKeybindManager::resizeWithBorder(const IPointer::SButtonEvent& e) { } void CKeybindManager::onSwitchEvent(const std::string& switchName) { - handleKeybinds(0, SPressedKeyWithMods{.keyName = "switch:" + switchName}, true, nullptr); + handleKeybinds(0, SPressedKeyWithMods{.keyName = "switch:" + switchName}, true, nullptr, nullptr); } void CKeybindManager::onSwitchOnEvent(const std::string& switchName) { - handleKeybinds(0, SPressedKeyWithMods{.keyName = "switch:on:" + switchName}, true, nullptr); + handleKeybinds(0, SPressedKeyWithMods{.keyName = "switch:on:" + switchName}, true, nullptr, nullptr); } void CKeybindManager::onSwitchOffEvent(const std::string& switchName) { - handleKeybinds(0, SPressedKeyWithMods{.keyName = "switch:off:" + switchName}, true, nullptr); + handleKeybinds(0, SPressedKeyWithMods{.keyName = "switch:off:" + switchName}, true, nullptr, nullptr); } eMultiKeyCase CKeybindManager::mkKeysymSetMatches(const std::set keybindKeysyms, const std::set pressedKeysyms) { @@ -639,23 +623,29 @@ SSubmap CKeybindManager::getCurrentSubmap() { return m_currentSelectedSubmap; } -SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SPressedKeyWithMods& key, bool pressed, SP keyboard) { +SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SPressedKeyWithMods& key, bool pressed, SP keyboard, SP device) { static auto PDISABLEINHIBIT = CConfigValue("binds:disable_keybind_grabbing"); static auto PDRAGTHRESHOLD = CConfigValue("binds:drag_threshold"); bool found = false; SDispatchResult res; - if (pressed) { - if (keycodeToModifier(key.keycode)) - m_mkMods.insert(key.keysym); - else - m_mkKeys.insert(key.keysym); - } else { - if (keycodeToModifier(key.keycode)) - m_mkMods.erase(key.keysym); - else - m_mkKeys.erase(key.keysym); + // Skip keysym tracking for events with no keysym (e.g., scroll wheel events). + // Scroll events have keysym=0 and are always "pressed" (never released), + // so without this check, 0 gets inserted into m_mkKeys and never removed, + // breaking multi-key binds (binds flag 's'). See issue #8699. + if (key.keysym != 0) { + if (pressed) { + if (keycodeToModifier(key.keycode)) + m_mkMods.insert(key.keysym); + else + m_mkKeys.insert(key.keysym); + } else { + if (keycodeToModifier(key.keycode)) + m_mkMods.erase(key.keysym); + else + m_mkKeys.erase(key.keysym); + } } for (auto& k : m_keybinds) { @@ -673,6 +663,11 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP if (!IGNORECONDITIONS && ((modmask != k->modmask && !k->ignoreMods) || (k->submap != m_currentSelectedSubmap && !k->submapUniversal) || k->shadowed)) continue; + if (device) { + if (k->deviceInclusive ^ k->devices.contains(device->m_hlName)) + continue; + } + if (k->multiKey) { switch (mkBindMatches(k)) { case MK_NO_MATCH: continue; @@ -744,9 +739,9 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP // Require mouse to stay inside drag_threshold for clicks, outside for drags // Check if either a mouse bind has triggered or currently over the threshold (maybe there is no mouse bind on the same key) const auto THRESHOLDREACHED = key.mousePosAtPress.distanceSq(g_pInputManager->getMouseCoordsInternal()) > std::pow(*PDRAGTHRESHOLD, 2); - if (k->click && (g_pInputManager->m_dragThresholdReached || THRESHOLDREACHED)) + if (k->click && (g_layoutManager->dragController()->dragThresholdReached() || THRESHOLDREACHED)) continue; - else if (k->drag && !g_pInputManager->m_dragThresholdReached && !THRESHOLDREACHED) + else if (k->drag && !g_layoutManager->dragController()->dragThresholdReached() && !THRESHOLDREACHED) continue; } @@ -768,10 +763,10 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP // Should never happen, as we check in the ConfigManager, but oh well if (DISPATCHER == m_dispatchers.end()) { - Debug::log(ERR, "Invalid handler in a keybind! (handler {} does not exist)", k->handler); + Log::logger->log(Log::ERR, "Invalid handler in a keybind! (handler {} does not exist)", k->handler); } else { // call the dispatcher - Debug::log(LOG, "Keybind triggered, calling dispatcher ({}, {}, {}, {})", modmask, key.keyName, key.keysym, DISPATCHER->first); + Log::logger->log(Log::DEBUG, "Keybind triggered, calling dispatcher ({}, {}, {}, {})", modmask, key.keyName, key.keysym, DISPATCHER->first); m_passPressed = sc(pressed); @@ -803,13 +798,13 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP found = true; } - g_pInputManager->m_dragThresholdReached = false; + g_layoutManager->dragController()->resetDragThresholdReached(); // if keybind wasn't found (or dispatcher said to) then pass event res.passEvent |= !found; if (!found && !*PDISABLEINHIBIT && PROTO::shortcutsInhibit->isInhibited()) { - Debug::log(LOG, "Keybind handling is disabled due to an inhibitor"); + Log::logger->log(Log::DEBUG, "Keybind handling is disabled due to an inhibitor"); res.success = false; if (res.error.empty()) @@ -876,7 +871,7 @@ bool CKeybindManager::handleVT(xkb_keysym_t keysym) { if (!CURRENT_TTY.has_value() || *CURRENT_TTY == TTY) return true; - Debug::log(LOG, "Switching from VT {} to VT {}", *CURRENT_TTY, TTY); + Log::logger->log(Log::DEBUG, "Switching from VT {} to VT {}", *CURRENT_TTY, TTY); g_pCompositor->m_aqBackend->session->switchVT(TTY); } @@ -903,93 +898,22 @@ bool CKeybindManager::handleInternalKeybinds(xkb_keysym_t keysym) { // Dispatchers SDispatchResult CKeybindManager::spawn(std::string args) { - const uint64_t PROC = spawnWithRules(args, nullptr); - return {.success = PROC > 0, .error = std::format("Failed to start process {}", args)}; -} - -uint64_t CKeybindManager::spawnWithRules(std::string args, PHLWORKSPACE pInitialWorkspace) { - - args = trim(args); - - std::string RULES = ""; - - if (args[0] == '[') { - // we have exec rules - RULES = args.substr(1, args.substr(1).find_first_of(']')); - args = args.substr(args.find_first_of(']') + 1); - } - - std::string execToken = ""; - - if (!RULES.empty()) { - auto rule = Desktop::Rule::CWindowRule::buildFromExecString(std::move(RULES)); - - execToken = rule->execToken(); - - Desktop::Rule::ruleEngine()->registerRule(std::move(rule)); - - Debug::log(LOG, "Applied rule arguments for exec."); - } - - const uint64_t PROC = spawnRawProc(args, pInitialWorkspace, execToken); - - return PROC; + const auto PROC = Config::Supplementary::executor()->spawn(args); + 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)}; } SDispatchResult CKeybindManager::spawnRaw(std::string args) { - const uint64_t PROC = spawnRawProc(args, nullptr); - return {.success = PROC > 0, .error = std::format("Failed to start process {}", args)}; -} - -uint64_t CKeybindManager::spawnRawProc(std::string args, PHLWORKSPACE pInitialWorkspace, const std::string& execRuleToken) { - Debug::log(LOG, "Executing {}", args); - - const auto HLENV = getHyprlandLaunchEnv(pInitialWorkspace); - - pid_t child = fork(); - if (child < 0) { - Debug::log(LOG, "Fail to fork"); - return 0; - } - if (child == 0) { - // run in child - g_pCompositor->restoreNofile(); - - sigset_t set; - sigemptyset(&set); - sigprocmask(SIG_SETMASK, &set, nullptr); - - for (auto const& e : HLENV) { - setenv(e.first.c_str(), e.second.c_str(), 1); - } - setenv("WAYLAND_DISPLAY", g_pCompositor->m_wlDisplaySocket.c_str(), 1); - if (!execRuleToken.empty()) - setenv(Desktop::Rule::EXEC_RULE_ENV_NAME, execRuleToken.c_str(), true); - - int devnull = open("/dev/null", O_WRONLY | O_CLOEXEC); - if (devnull != -1) { - dup2(devnull, STDOUT_FILENO); - dup2(devnull, STDERR_FILENO); - close(devnull); - } - - execl("/bin/sh", "/bin/sh", "-c", args.c_str(), nullptr); - - // exit child - _exit(0); - } - // run in parent - - Debug::log(LOG, "Process Created with pid {}", child); - - return child; + const auto PROC = Config::Supplementary::executor()->spawnRaw(args); + return {.success = PROC && *PROC > 0, .error = std::format("Failed to start process {}", args)}; } SDispatchResult CKeybindManager::killActive(std::string args) { const auto PWINDOW = Desktop::focusState()->window(); if (!PWINDOW) { - Debug::log(ERR, "killActive: no window found"); + Log::logger->log(Log::ERR, "killActive: no window found"); return {.success = false, .error = "killActive: no window found"}; } @@ -1011,7 +935,7 @@ SDispatchResult CKeybindManager::closeWindow(std::string args) { const auto PWINDOW = g_pCompositor->getWindowByRegex(args); if (!PWINDOW) { - Debug::log(ERR, "closeWindow: no window found"); + Log::logger->log(Log::ERR, "closeWindow: no window found"); return {.success = false, .error = "closeWindow: no window found"}; } @@ -1027,7 +951,7 @@ SDispatchResult CKeybindManager::killWindow(std::string args) { const auto PWINDOW = g_pCompositor->getWindowByRegex(args); if (!PWINDOW) { - Debug::log(ERR, "killWindow: no window found"); + Log::logger->log(Log::ERR, "killWindow: no window found"); return {.success = false, .error = "killWindow: no window found"}; } @@ -1043,12 +967,12 @@ SDispatchResult CKeybindManager::signalActive(std::string args) { try { const auto SIGNALNUM = std::stoi(args); if (SIGNALNUM < 1 || SIGNALNUM > 31) { - Debug::log(ERR, "signalActive: invalid signal number {}", SIGNALNUM); + Log::logger->log(Log::ERR, "signalActive: invalid signal number {}", SIGNALNUM); return {.success = false, .error = std::format("signalActive: invalid signal number {}", SIGNALNUM)}; } kill(Desktop::focusState()->window()->getPID(), SIGNALNUM); } catch (const std::exception& e) { - Debug::log(ERR, "signalActive: invalid signal format \"{}\"", args); + Log::logger->log(Log::ERR, "signalActive: invalid signal format \"{}\"", args); return {.success = false, .error = std::format("signalActive: invalid signal format \"{}\"", args)}; } @@ -1064,7 +988,7 @@ SDispatchResult CKeybindManager::signalWindow(std::string args) { const auto PWINDOW = g_pCompositor->getWindowByRegex(WINDOWREGEX); if (!PWINDOW) { - Debug::log(ERR, "signalWindow: no window"); + Log::logger->log(Log::ERR, "signalWindow: no window"); return {.success = false, .error = "signalWindow: no window"}; } @@ -1074,12 +998,12 @@ SDispatchResult CKeybindManager::signalWindow(std::string args) { try { const auto SIGNALNUM = std::stoi(SIGNAL); if (SIGNALNUM < 1 || SIGNALNUM > 31) { - Debug::log(ERR, "signalWindow: invalid signal number {}", SIGNALNUM); + Log::logger->log(Log::ERR, "signalWindow: invalid signal number {}", SIGNALNUM); return {.success = false, .error = std::format("signalWindow: invalid signal number {}", SIGNALNUM)}; } kill(PWINDOW->getPID(), SIGNALNUM); } catch (const std::exception& e) { - Debug::log(ERR, "signalWindow: invalid signal format \"{}\"", SIGNAL); + Log::logger->log(Log::ERR, "signalWindow: invalid signal format \"{}\"", SIGNAL); return {.success = false, .error = std::format("signalWindow: invalid signal format \"{}\"", SIGNAL)}; } @@ -1105,32 +1029,19 @@ static SDispatchResult toggleActiveFloatingCore(std::string args, std::optional< return {}; // remove drag status - if (!g_pInputManager->m_currentlyDraggedWindow.expired()) + if (g_layoutManager->dragController()->target()) CKeybindManager::changeMouseBindMode(MBIND_INVALID); - if (PWINDOW->m_groupData.pNextWindow.lock() && PWINDOW->m_groupData.pNextWindow.lock() != PWINDOW) { - const auto PCURRENT = PWINDOW->getGroupCurrent(); + g_layoutManager->changeFloatingMode(PWINDOW->layoutTarget()); - 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_isFloating) + g_pCompositor->changeWindowZOrder(PWINDOW, true); if (PWINDOW->m_workspace) { PWINDOW->m_workspace->updateWindows(); PWINDOW->m_workspace->updateWindowData(); } - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID()); g_pCompositor->updateAllWindowsAnimatedDecorationValues(); return {}; @@ -1156,12 +1067,7 @@ SDispatchResult CKeybindManager::centerWindow(std::string args) { const auto PMONITOR = PWINDOW->m_monitor.lock(); - auto RESERVEDOFFSET = Vector2D(); - if (args == "1") - RESERVEDOFFSET = (PMONITOR->m_reservedTopLeft - PMONITOR->m_reservedBottomRight) / 2.f; - - *PWINDOW->m_realPosition = PMONITOR->middle() - PWINDOW->m_realSize->goal() / 2.f + RESERVEDOFFSET; - PWINDOW->m_position = PWINDOW->m_realPosition->goal(); + PWINDOW->layoutTarget()->setPositionGlobal(CBox{PMONITOR->logicalBoxMinusReserved().middle() - PWINDOW->m_realSize->goal() / 2.F, PWINDOW->layoutTarget()->position().size()}); return {}; } @@ -1177,10 +1083,7 @@ SDispatchResult CKeybindManager::toggleActivePseudo(std::string args) { if (!PWINDOW) return {.success = false, .error = "Window not found"}; - PWINDOW->m_isPseudotiled = !PWINDOW->m_isPseudotiled; - - if (!PWINDOW->isFullscreen()) - g_pLayoutManager->getCurrentLayout()->recalculateWindow(PWINDOW); + PWINDOW->layoutTarget()->setPseudo(!PWINDOW->layoutTarget()->isPseudo()); return {}; } @@ -1191,10 +1094,11 @@ static SWorkspaceIDName getWorkspaceToChangeFromArgs(std::string args, PHLWORKSP } const bool PER_MON = args.contains("_per_monitor"); - const SWorkspaceIDName PPREVWS = PER_MON ? PMONITOR->getPrevWorkspaceIDName(PCURRENTWORKSPACE->m_id) : PCURRENTWORKSPACE->getPrevWorkspaceIDName(); + const SWorkspaceIDName PPREVWS = PER_MON ? Desktop::History::workspaceTracker()->previousWorkspaceIDName(PCURRENTWORKSPACE, PMONITOR.lock()) : + Desktop::History::workspaceTracker()->previousWorkspaceIDName(PCURRENTWORKSPACE); // Do nothing if there's no previous workspace, otherwise switch to it. if (PPREVWS.id == -1 || PPREVWS.id == PCURRENTWORKSPACE->m_id) { - Debug::log(LOG, "No previous workspace to change to"); + Log::logger->log(Log::DEBUG, "No previous workspace to change to"); return {.id = WORKSPACE_NOT_CHANGED}; } @@ -1209,7 +1113,6 @@ SDispatchResult CKeybindManager::changeworkspace(std::string args) { // Workspace_back_and_forth being enabled means that an attempt to switch to // the current workspace will instead switch to the previous. static auto PBACKANDFORTH = CConfigValue("binds:workspace_back_and_forth"); - static auto PALLOWWORKSPACECYCLES = CConfigValue("binds:allow_workspace_cycles"); static auto PWORKSPACECENTERON = CConfigValue("binds:workspace_center_on"); static auto PHIDESPECIALONWORKSPACECHANGE = CConfigValue("binds:hide_special_on_workspace_change"); @@ -1223,14 +1126,15 @@ SDispatchResult CKeybindManager::changeworkspace(std::string args) { const auto& [workspaceToChangeTo, workspaceName, isAutoID] = getWorkspaceToChangeFromArgs(args, PCURRENTWORKSPACE, PMONITOR); if (workspaceToChangeTo == WORKSPACE_INVALID) { - Debug::log(ERR, "Error in changeworkspace, invalid value"); + Log::logger->log(Log::ERR, "Error in changeworkspace, invalid value"); return {.success = false, .error = "Error in changeworkspace, invalid value"}; } if (workspaceToChangeTo == WORKSPACE_NOT_CHANGED) return {}; - const SWorkspaceIDName PPREVWS = args.contains("_per_monitor") ? PMONITOR->getPrevWorkspaceIDName(PCURRENTWORKSPACE->m_id) : PCURRENTWORKSPACE->getPrevWorkspaceIDName(); + const SWorkspaceIDName PPREVWS = args.contains("_per_monitor") ? Desktop::History::workspaceTracker()->previousWorkspaceIDName(PCURRENTWORKSPACE, PMONITOR) : + Desktop::History::workspaceTracker()->previousWorkspaceIDName(PCURRENTWORKSPACE); const bool BISWORKSPACECURRENT = workspaceToChangeTo == PCURRENTWORKSPACE->m_id; if (BISWORKSPACECURRENT && (!(*PBACKANDFORTH || EXPLICITPREVIOUS) || PPREVWS.id == -1)) { @@ -1265,14 +1169,6 @@ SDispatchResult CKeybindManager::changeworkspace(std::string args) { Desktop::focusState()->rawMonitorFocus(PMONITORWORKSPACEOWNER); - if (BISWORKSPACECURRENT) { - if (*PALLOWWORKSPACECYCLES) - pWorkspaceToChangeTo->rememberPrevWorkspace(PCURRENTWORKSPACE); - else if (!EXPLICITPREVIOUS && !*PBACKANDFORTH) - pWorkspaceToChangeTo->rememberPrevWorkspace(nullptr); - } else - pWorkspaceToChangeTo->rememberPrevWorkspace(PCURRENTWORKSPACE); - if (*PHIDESPECIALONWORKSPACECHANGE) PMONITORWORKSPACEOWNER->setSpecialWorkspace(nullptr); PMONITORWORKSPACEOWNER->changeWorkspace(pWorkspaceToChangeTo, false, true); @@ -1280,7 +1176,7 @@ SDispatchResult CKeybindManager::changeworkspace(std::string args) { if (PMONITOR != PMONITORWORKSPACEOWNER) { Vector2D middle = PMONITORWORKSPACEOWNER->middle(); if (const auto PLAST = pWorkspaceToChangeTo->getLastFocusedWindow(); PLAST) { - Desktop::focusState()->fullWindowFocus(PLAST); + Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND); if (*PWORKSPACECENTERON == 1) middle = PLAST->middle(); } @@ -1298,9 +1194,9 @@ SDispatchResult CKeybindManager::changeworkspace(std::string args) { if (*PWARPONWORKSPACECHANGE > 0) { auto PLAST = pWorkspaceToChangeTo->getLastFocusedWindow(); - auto HLSurface = CWLSurface::fromResource(g_pSeatManager->m_state.pointerFocus.lock()); + auto HLSurface = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.pointerFocus.lock()); - if (PLAST && (!HLSurface || HLSurface->getWindow())) + if (PLAST && (!HLSurface || HLSurface->view()->type() == Desktop::View::VIEW_TYPE_WINDOW)) PLAST->warpCursor(*PWARPONWORKSPACECHANGE == 2); } @@ -1348,16 +1244,17 @@ SDispatchResult CKeybindManager::fullscreenStateActive(std::string args) { clientMode = std::stoi(ARGS[1]); } catch (std::exception& e) { clientMode = -1; } - const SFullscreenState STATE = SFullscreenState{.internal = (internalMode != -1 ? sc(internalMode) : PWINDOW->m_fullscreenState.internal), - .client = (clientMode != -1 ? sc(clientMode) : PWINDOW->m_fullscreenState.client)}; + const Desktop::View::SFullscreenState STATE = + Desktop::View::SFullscreenState{.internal = (internalMode != -1 ? sc(internalMode) : PWINDOW->m_fullscreenState.internal), + .client = (clientMode != -1 ? sc(clientMode) : PWINDOW->m_fullscreenState.client)}; if (ARGS.size() <= 2 || ARGS[2] == "toggle") { if (internalMode != -1 && clientMode != -1 && PWINDOW->m_fullscreenState.internal == STATE.internal && PWINDOW->m_fullscreenState.client == STATE.client) - g_pCompositor->setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = FSMODE_NONE, .client = FSMODE_NONE}); + g_pCompositor->setWindowFullscreenState(PWINDOW, Desktop::View::SFullscreenState{.internal = FSMODE_NONE, .client = FSMODE_NONE}); else if (internalMode != -1 && clientMode == -1 && PWINDOW->m_fullscreenState.internal == STATE.internal) - g_pCompositor->setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = FSMODE_NONE, .client = PWINDOW->m_fullscreenState.client}); + g_pCompositor->setWindowFullscreenState(PWINDOW, Desktop::View::SFullscreenState{.internal = FSMODE_NONE, .client = PWINDOW->m_fullscreenState.client}); else if (internalMode == -1 && clientMode != -1 && PWINDOW->m_fullscreenState.client == STATE.client) - g_pCompositor->setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = PWINDOW->m_fullscreenState.internal, .client = FSMODE_NONE}); + g_pCompositor->setWindowFullscreenState(PWINDOW, Desktop::View::SFullscreenState{.internal = PWINDOW->m_fullscreenState.internal, .client = FSMODE_NONE}); else g_pCompositor->setWindowFullscreenState(PWINDOW, STATE); } else if (ARGS[2] == "set") { @@ -1386,19 +1283,18 @@ SDispatchResult CKeybindManager::moveActiveToWorkspace(std::string args) { const auto& [WORKSPACEID, workspaceName, isAutoID] = getWorkspaceIDNameFromString(args); if (WORKSPACEID == WORKSPACE_INVALID) { - Debug::log(LOG, "Invalid workspace in moveActiveToWorkspace"); + Log::logger->log(Log::DEBUG, "Invalid workspace in moveActiveToWorkspace"); return {.success = false, .error = "Invalid workspace in moveActiveToWorkspace"}; } if (WORKSPACEID == PWINDOW->workspaceID()) { - Debug::log(LOG, "Not moving to workspace because it didn't change."); + Log::logger->log(Log::DEBUG, "Not moving to workspace because it didn't change."); return {.success = false, .error = "Not moving to workspace because it didn't change."}; } - auto pWorkspace = g_pCompositor->getWorkspaceByID(WORKSPACEID); - PHLMONITOR pMonitor = nullptr; - const auto POLDWS = PWINDOW->m_workspace; - static auto PALLOWWORKSPACECYCLES = CConfigValue("binds:allow_workspace_cycles"); + auto pWorkspace = g_pCompositor->getWorkspaceByID(WORKSPACEID); + PHLMONITOR pMonitor = nullptr; + const auto POLDWS = PWINDOW->m_workspace; updateRelativeCursorCoords(); @@ -1423,12 +1319,9 @@ SDispatchResult CKeybindManager::moveActiveToWorkspace(std::string args) { else if (POLDWS->m_isSpecialWorkspace) POLDWS->m_monitor.lock()->setSpecialWorkspace(nullptr); - if (*PALLOWWORKSPACECYCLES) - pWorkspace->rememberPrevWorkspace(POLDWS); - pMonitor->changeWorkspace(pWorkspace); - Desktop::focusState()->fullWindowFocus(PWINDOW); + Desktop::focusState()->fullWindowFocus(PWINDOW, Desktop::FOCUS_REASON_KEYBIND); PWINDOW->warpCursor(); return {}; @@ -1449,7 +1342,7 @@ SDispatchResult CKeybindManager::moveActiveToWorkspaceSilent(std::string args) { const auto& [WORKSPACEID, workspaceName, isAutoID] = getWorkspaceIDNameFromString(args); if (WORKSPACEID == WORKSPACE_INVALID) { - Debug::log(ERR, "Error in moveActiveToWorkspaceSilent, invalid value"); + Log::logger->log(Log::ERR, "Error in moveActiveToWorkspaceSilent, invalid value"); return {.success = false, .error = "Error in moveActiveToWorkspaceSilent, invalid value"}; } @@ -1469,8 +1362,10 @@ SDispatchResult CKeybindManager::moveActiveToWorkspaceSilent(std::string args) { } if (PWINDOW == Desktop::focusState()->window()) { - if (const auto PATCOORDS = g_pCompositor->vectorToWindowUnified(OLDMIDDLE, RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING, PWINDOW); PATCOORDS) - Desktop::focusState()->fullWindowFocus(PATCOORDS); + 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); else g_pInputManager->refocus(); } @@ -1479,94 +1374,92 @@ SDispatchResult CKeybindManager::moveActiveToWorkspaceSilent(std::string args) { } SDispatchResult CKeybindManager::moveFocusTo(std::string args) { - static auto PFULLCYCLE = CConfigValue("binds:movefocus_cycles_fullscreen"); - static auto PMONITORFALLBACK = CConfigValue("binds:window_direction_monitor_fallback"); - static auto PGROUPCYCLE = CConfigValue("binds:movefocus_cycles_groupfirst"); - char arg = args[0]; + static auto PFULLCYCLE = CConfigValue("binds:movefocus_cycles_fullscreen"); + static auto PGROUPCYCLE = CConfigValue("binds:movefocus_cycles_groupfirst"); + static auto PMONITORFALLBACK = CConfigValue("binds:window_direction_monitor_fallback"); + Math::eDirection dir = Math::fromChar(args[0]); - if (!isDirection(args)) { - Debug::log(ERR, "Cannot move focus in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg); - return {.success = false, .error = std::format("Cannot move focus in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg)}; + if (dir == Math::DIRECTION_DEFAULT) { + Log::logger->log(Log::ERR, "Cannot move focus in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0]); + return {.success = false, .error = std::format("Cannot move focus in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0])}; } const auto PLASTWINDOW = Desktop::focusState()->window(); - if (!PLASTWINDOW) { + if (!PLASTWINDOW || !PLASTWINDOW->aliveAndVisible()) { if (*PMONITORFALLBACK) - tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(arg)); - + tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(dir)); return {}; } const auto PWINDOWTOCHANGETO = *PFULLCYCLE && PLASTWINDOW->isFullscreen() ? - g_pCompositor->getWindowCycle(PLASTWINDOW, true, {}, false, arg != 'd' && arg != 'b' && arg != 'r') : - g_pCompositor->getWindowInDirection(PLASTWINDOW, arg); + g_pCompositor->getWindowCycle(PLASTWINDOW, true, {}, false, dir != Math::DIRECTION_DOWN && dir != Math::DIRECTION_RIGHT) : + g_pCompositor->getWindowInDirection(PLASTWINDOW, dir); // Prioritize focus change within groups if the window is a part of it. - if (*PGROUPCYCLE && PLASTWINDOW->m_groupData.pNextWindow) { + if (*PGROUPCYCLE && PLASTWINDOW->m_group) { auto isTheOnlyGroupOnWs = !PWINDOWTOCHANGETO && g_pCompositor->m_monitors.size() == 1; - if (arg == 'l' && (PLASTWINDOW != PLASTWINDOW->getGroupHead() || isTheOnlyGroupOnWs)) { - PLASTWINDOW->setGroupCurrent(PLASTWINDOW->getGroupPrevious()); + if (dir == Math::DIRECTION_LEFT && (PLASTWINDOW != PLASTWINDOW->m_group->head() || isTheOnlyGroupOnWs)) { + PLASTWINDOW->m_group->moveCurrent(false); return {}; } - else if (arg == 'r' && (PLASTWINDOW != PLASTWINDOW->getGroupTail() || isTheOnlyGroupOnWs)) { - PLASTWINDOW->setGroupCurrent(PLASTWINDOW->m_groupData.pNextWindow.lock()); + else if (dir == Math::DIRECTION_RIGHT && (PLASTWINDOW != PLASTWINDOW->m_group->tail() || isTheOnlyGroupOnWs)) { + PLASTWINDOW->m_group->moveCurrent(true); return {}; } } // Found window in direction, switch to it if (PWINDOWTOCHANGETO) { - switchToWindow(PWINDOWTOCHANGETO, false, *PFULLCYCLE && PLASTWINDOW->isFullscreen()); + switchToWindow(PWINDOWTOCHANGETO, *PFULLCYCLE && PLASTWINDOW->isFullscreen()); return {}; } - Debug::log(LOG, "No window found in direction {}, looking for a monitor", arg); + Log::logger->log(Log::DEBUG, "No window found in direction {}, looking for a monitor", Math::toString(dir)); - if (*PMONITORFALLBACK && tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(arg))) + if (*PMONITORFALLBACK && tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(dir))) return {}; static auto PNOFALLBACK = CConfigValue("general:no_focus_fallback"); if (*PNOFALLBACK) - return {.success = false, .error = std::format("Nothing to focus to in direction {}", arg)}; + return {.success = false, .error = std::format("Nothing to focus to in direction {}", Math::toString(dir))}; - Debug::log(LOG, "No monitor found in direction {}, getting the inverse edge", arg); + Log::logger->log(Log::DEBUG, "No monitor found in direction {}, getting the inverse edge", Math::toString(dir)); const auto PMONITOR = PLASTWINDOW->m_monitor.lock(); if (!PMONITOR) return {.success = false, .error = "last window has no monitor?"}; - if (arg == 'l' || arg == 'r') { + if (dir == Math::DIRECTION_LEFT || dir == Math::DIRECTION_RIGHT) { if (STICKS(PLASTWINDOW->m_position.x, PMONITOR->m_position.x) && STICKS(PLASTWINDOW->m_size.x, PMONITOR->m_size.x)) return {.success = false, .error = "move does not make sense, would return back"}; } else if (STICKS(PLASTWINDOW->m_position.y, PMONITOR->m_position.y) && STICKS(PLASTWINDOW->m_size.y, PMONITOR->m_size.y)) return {.success = false, .error = "move does not make sense, would return back"}; CBox box = PMONITOR->logicalBox(); - switch (arg) { - case 'l': + switch (dir) { + case Math::DIRECTION_LEFT: box.x += box.w; box.w = 1; break; - case 'r': + case Math::DIRECTION_RIGHT: box.x -= 1; box.w = 1; break; - case 'u': - case 't': + case Math::DIRECTION_UP: box.y += box.h; box.h = 1; break; - case 'd': - case 'b': + case Math::DIRECTION_DOWN: box.y -= 1; box.h = 1; break; + default: break; } const auto PWINDOWCANDIDATE = g_pCompositor->getWindowInDirection(box, PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace, - arg, PLASTWINDOW, PLASTWINDOW->m_isFloating); + dir, PLASTWINDOW, PLASTWINDOW->m_isFloating); if (PWINDOWCANDIDATE) switchToWindow(PWINDOWCANDIDATE); @@ -1574,9 +1467,9 @@ SDispatchResult CKeybindManager::moveFocusTo(std::string args) { } SDispatchResult CKeybindManager::focusUrgentOrLast(std::string args) { - const auto PWINDOWURGENT = g_pCompositor->getUrgentWindow(); - const auto PWINDOWPREV = Desktop::focusState()->window() ? (Desktop::focusState()->windowHistory().size() < 2 ? nullptr : Desktop::focusState()->windowHistory()[1].lock()) : - (Desktop::focusState()->windowHistory().empty() ? nullptr : Desktop::focusState()->windowHistory()[0].lock()); + const auto& HISTORY = Desktop::History::windowTracker()->fullHistory(); + const auto PWINDOWURGENT = g_pCompositor->getUrgentWindow(); + const auto PWINDOWPREV = Desktop::focusState()->window() ? (HISTORY.size() < 2 ? nullptr : HISTORY[1].lock()) : (HISTORY.empty() ? nullptr : HISTORY[0].lock()); if (!PWINDOWURGENT && !PWINDOWPREV) return {.success = false, .error = "Window not found"}; @@ -1587,8 +1480,12 @@ SDispatchResult CKeybindManager::focusUrgentOrLast(std::string args) { } SDispatchResult CKeybindManager::focusCurrentOrLast(std::string args) { - const auto PWINDOWPREV = Desktop::focusState()->window() ? (Desktop::focusState()->windowHistory().size() < 2 ? nullptr : Desktop::focusState()->windowHistory()[1].lock()) : - (Desktop::focusState()->windowHistory().empty() ? nullptr : Desktop::focusState()->windowHistory()[0].lock()); + const auto& HISTORY = Desktop::History::windowTracker()->fullHistory(); + + if (HISTORY.size() <= 1) + return {.success = false, .error = "History too short"}; + + const auto PWINDOWPREV = HISTORY[HISTORY.size() - 2].lock(); if (!PWINDOWPREV) return {.success = false, .error = "Window not found"}; @@ -1599,7 +1496,6 @@ SDispatchResult CKeybindManager::focusCurrentOrLast(std::string args) { } SDispatchResult CKeybindManager::swapActive(std::string args) { - char arg = args[0]; const auto PLASTWINDOW = Desktop::focusState()->window(); PHLWINDOW PWINDOWTOCHANGETO = nullptr; @@ -1609,26 +1505,26 @@ SDispatchResult CKeybindManager::swapActive(std::string args) { if (PLASTWINDOW->isFullscreen()) return {.success = false, .error = "Can't swap fullscreen window"}; - if (isDirection(args)) - PWINDOWTOCHANGETO = g_pCompositor->getWindowInDirection(PLASTWINDOW, arg); - else + if (isDirection(args)) { + Math::eDirection dir = Math::fromChar(args[0]); + PWINDOWTOCHANGETO = g_pCompositor->getWindowInDirection(PLASTWINDOW, dir); + } else PWINDOWTOCHANGETO = g_pCompositor->getWindowByRegex(args); if (!PWINDOWTOCHANGETO || PWINDOWTOCHANGETO == PLASTWINDOW) { - Debug::log(ERR, "Can't swap with {}, invalid window", args); + Log::logger->log(Log::ERR, "Can't swap with {}, invalid window", args); return {.success = false, .error = std::format("Can't swap with {}, invalid window", args)}; } - Debug::log(LOG, "Swapping active window with {}", args); + Log::logger->log(Log::DEBUG, "Swapping active window with {}", args); updateRelativeCursorCoords(); - g_pLayoutManager->getCurrentLayout()->switchWindows(PLASTWINDOW, PWINDOWTOCHANGETO); + g_layoutManager->switchTargets(PLASTWINDOW->layoutTarget(), PWINDOWTOCHANGETO->layoutTarget(), true); PLASTWINDOW->warpCursor(); return {}; } SDispatchResult CKeybindManager::moveActiveTo(std::string args) { - char arg = args[0]; bool silent = args.ends_with(" silent"); if (silent) args = args.substr(0, args.length() - 7); @@ -1646,9 +1542,10 @@ SDispatchResult CKeybindManager::moveActiveTo(std::string args) { return {}; } - if (!isDirection(args)) { - Debug::log(ERR, "Cannot move window in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg); - return {.success = false, .error = std::format("Cannot move window in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg)}; + Math::eDirection dir = Math::fromChar(args[0]); + if (dir == Math::DIRECTION_DEFAULT) { + Log::logger->log(Log::ERR, "Cannot move window in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0]); + return {.success = false, .error = std::format("Cannot move window in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0])}; } const auto PLASTWINDOW = Desktop::focusState()->window(); @@ -1659,59 +1556,11 @@ SDispatchResult CKeybindManager::moveActiveTo(std::string args) { if (PLASTWINDOW->isFullscreen()) return {.success = false, .error = "Can't move fullscreen window"}; - if (PLASTWINDOW->m_isFloating) { - std::optional vPosx, vPosy; - const auto PMONITOR = PLASTWINDOW->m_monitor.lock(); - const auto BORDERSIZE = PLASTWINDOW->getRealBorderSize(); - static auto PGAPSCUSTOMDATA = CConfigValue("general:float_gaps"); - static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); - auto* PGAPSOUT = sc(PGAPSCUSTOMDATA.ptr()->getData()); - if (PGAPSOUT->m_left < 0 || PGAPSOUT->m_right < 0 || PGAPSOUT->m_top < 0 || PGAPSOUT->m_bottom < 0) - PGAPSOUT = sc(PGAPSOUTDATA.ptr()->getData()); + updateRelativeCursorCoords(); - switch (arg) { - case 'l': vPosx = PMONITOR->m_reservedTopLeft.x + BORDERSIZE + PMONITOR->m_position.x + PGAPSOUT->m_left; break; - case 'r': - vPosx = PMONITOR->m_size.x - PMONITOR->m_reservedBottomRight.x - PLASTWINDOW->m_realSize->goal().x - BORDERSIZE + PMONITOR->m_position.x - PGAPSOUT->m_right; - break; - case 't': - case 'u': vPosy = PMONITOR->m_reservedTopLeft.y + BORDERSIZE + PMONITOR->m_position.y + PGAPSOUT->m_top; break; - case 'b': - case 'd': - vPosy = PMONITOR->m_size.y - PMONITOR->m_reservedBottomRight.y - PLASTWINDOW->m_realSize->goal().y - BORDERSIZE + PMONITOR->m_position.y - PGAPSOUT->m_bottom; - break; - } - - *PLASTWINDOW->m_realPosition = Vector2D(vPosx.value_or(PLASTWINDOW->m_realPosition->goal().x), vPosy.value_or(PLASTWINDOW->m_realPosition->goal().y)); - - return {}; - } - - // If the window to change to is on the same workspace, switch them - const auto PWINDOWTOCHANGETO = g_pCompositor->getWindowInDirection(PLASTWINDOW, arg); - if (PWINDOWTOCHANGETO) { - updateRelativeCursorCoords(); - - g_pLayoutManager->getCurrentLayout()->moveWindowTo(PLASTWINDOW, args, silent); - if (!silent) - PLASTWINDOW->warpCursor(); - return {}; - } - - static auto PMONITORFALLBACK = CConfigValue("binds:window_direction_monitor_fallback"); - if (!*PMONITORFALLBACK) - return {}; - - // Otherwise, we always want to move to the next monitor in that direction - const auto PMONITORTOCHANGETO = g_pCompositor->getMonitorInDirection(arg); - if (!PMONITORTOCHANGETO) - return {.success = false, .error = "Nowhere to move active window to"}; - - const auto PWORKSPACE = PMONITORTOCHANGETO->m_activeWorkspace; - if (silent) - moveActiveToWorkspaceSilent(PWORKSPACE->getConfigName()); - else - moveActiveToWorkspace(PWORKSPACE->getConfigName()); + g_layoutManager->moveInDirection(PLASTWINDOW->layoutTarget(), args, silent); + if (!silent) + PLASTWINDOW->warpCursor(); return {}; } @@ -1725,10 +1574,10 @@ SDispatchResult CKeybindManager::toggleGroup(std::string args) { if (PWINDOW->isFullscreen()) g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); - if (PWINDOW->m_groupData.pNextWindow.expired()) - PWINDOW->createGroup(); + if (!PWINDOW->m_group) + PWINDOW->m_group = Desktop::View::CGroup::create({PWINDOW}); else - PWINDOW->destroyGroup(); + PWINDOW->m_group->destroy(); return {}; } @@ -1739,87 +1588,29 @@ SDispatchResult CKeybindManager::changeGroupActive(std::string args) { if (!PWINDOW) return {.success = false, .error = "Window not found"}; - if (PWINDOW->m_groupData.pNextWindow.expired()) - return {.success = false, .error = "No next window in group"}; + if (!PWINDOW->m_group) + return {.success = false, .error = "No group"}; - if (PWINDOW->m_groupData.pNextWindow.lock() == PWINDOW) + if (PWINDOW->m_group->size() == 1) return {.success = false, .error = "Only one window in group"}; if (isNumber(args, false)) { // index starts from '1'; '0' means last window - const int INDEX = std::stoi(args); - if (INDEX > PWINDOW->getGroupSize()) - return {.success = false, .error = "Index too big, there aren't that many windows in this group"}; - if (INDEX == 0) - PWINDOW->setGroupCurrent(PWINDOW->getGroupTail()); - else - PWINDOW->setGroupCurrent(PWINDOW->getGroupWindowByIndex(INDEX - 1)); + try { + const int INDEX = std::stoi(args); + if (INDEX <= 0) + PWINDOW->m_group->setCurrent(PWINDOW->m_group->size() - 1); + else + PWINDOW->m_group->setCurrent(INDEX - 1); + } catch (...) { return {.success = false, .error = "invalid idx"}; } + return {}; } if (args != "b" && args != "prev") - PWINDOW->setGroupCurrent(PWINDOW->m_groupData.pNextWindow.lock()); + PWINDOW->m_group->moveCurrent(true); else - PWINDOW->setGroupCurrent(PWINDOW->getGroupPrevious()); - - return {}; -} - -SDispatchResult CKeybindManager::toggleSplit(std::string args) { - SLayoutMessageHeader header; - header.pWindow = Desktop::focusState()->window(); - - if (!header.pWindow) - return {.success = false, .error = "Window not found"}; - - const auto PWORKSPACE = header.pWindow->m_workspace; - - if (PWORKSPACE->m_hasFullscreenWindow) - return {.success = false, .error = "Can't split windows that already split"}; - - g_pLayoutManager->getCurrentLayout()->layoutMessage(header, "togglesplit"); - - return {}; -} - -SDispatchResult CKeybindManager::swapSplit(std::string args) { - SLayoutMessageHeader header; - header.pWindow = Desktop::focusState()->window(); - - if (!header.pWindow) - return {.success = false, .error = "Window not found"}; - - const auto PWORKSPACE = header.pWindow->m_workspace; - - if (PWORKSPACE->m_hasFullscreenWindow) - return {.success = false, .error = "Can't split windows that already split"}; - - g_pLayoutManager->getCurrentLayout()->layoutMessage(header, "swapsplit"); - - return {}; -} - -SDispatchResult CKeybindManager::alterSplitRatio(std::string args) { - std::optional splitResult; - bool exact = false; - - if (args.starts_with("exact")) { - exact = true; - splitResult = getPlusMinusKeywordResult(args.substr(5), 0); - } else - splitResult = getPlusMinusKeywordResult(args, 0); - - if (!splitResult.has_value()) { - Debug::log(ERR, "Splitratio invalid in alterSplitRatio!"); - return {.success = false, .error = "Splitratio invalid in alterSplitRatio!"}; - } - - const auto PLASTWINDOW = Desktop::focusState()->window(); - - if (!PLASTWINDOW) - return {.success = false, .error = "Window not found"}; - - g_pLayoutManager->getCurrentLayout()->alterSplitRatio(PLASTWINDOW, splitResult.value(), exact); + PWINDOW->m_group->moveCurrent(false); return {}; } @@ -1833,14 +1624,14 @@ SDispatchResult CKeybindManager::focusMonitor(std::string arg) { SDispatchResult CKeybindManager::moveCursorToCorner(std::string arg) { if (!isNumber(arg)) { - Debug::log(ERR, "moveCursorToCorner, arg has to be a number."); + Log::logger->log(Log::ERR, "moveCursorToCorner, arg has to be a number."); return {.success = false, .error = "moveCursorToCorner, arg has to be a number."}; } const auto CORNER = std::stoi(arg); if (CORNER < 0 || CORNER > 3) { - Debug::log(ERR, "moveCursorToCorner, corner not 0 - 3."); + Log::logger->log(Log::ERR, "moveCursorToCorner, corner not 0 - 3."); return {.success = false, .error = "moveCursorToCorner, corner not 0 - 3."}; } @@ -1878,7 +1669,7 @@ SDispatchResult CKeybindManager::moveCursor(std::string args) { size_t i = args.find_first_of(' '); if (i == std::string::npos) { - Debug::log(ERR, "moveCursor, takes 2 arguments."); + Log::logger->log(Log::ERR, "moveCursor, takes 2 arguments."); return {.success = false, .error = "moveCursor, takes 2 arguments"}; } @@ -1886,11 +1677,11 @@ SDispatchResult CKeybindManager::moveCursor(std::string args) { y_str = args.substr(i + 1); if (!isNumber(x_str)) { - Debug::log(ERR, "moveCursor, x argument has to be a number."); + Log::logger->log(Log::ERR, "moveCursor, x argument has to be a number."); return {.success = false, .error = "moveCursor, x argument has to be a number."}; } if (!isNumber(y_str)) { - Debug::log(ERR, "moveCursor, y argument has to be a number."); + Log::logger->log(Log::ERR, "moveCursor, y argument has to be a number."); return {.success = false, .error = "moveCursor, y argument has to be a number."}; } @@ -1904,58 +1695,7 @@ SDispatchResult CKeybindManager::moveCursor(std::string args) { } SDispatchResult CKeybindManager::workspaceOpt(std::string args) { - - // current workspace - const auto PWORKSPACE = Desktop::focusState()->monitor()->m_activeWorkspace; - - if (!PWORKSPACE) - return {.success = false, .error = "Workspace not found"}; // ???? - - if (args == "allpseudo") { - PWORKSPACE->m_defaultPseudo = !PWORKSPACE->m_defaultPseudo; - - // apply - for (auto const& w : g_pCompositor->m_windows) { - if (!w->m_isMapped || w->m_workspace != PWORKSPACE) - continue; - - w->m_isPseudotiled = PWORKSPACE->m_defaultPseudo; - } - } else if (args == "allfloat") { - PWORKSPACE->m_defaultFloating = !PWORKSPACE->m_defaultFloating; - // apply - - // we make a copy because changeWindowFloatingMode might invalidate the iterator - std::vector ptrs(g_pCompositor->m_windows.begin(), g_pCompositor->m_windows.end()); - - for (auto const& w : ptrs) { - if (!w->m_isMapped || w->m_workspace != PWORKSPACE || w->isHidden()) - continue; - - if (!w->m_requestsFloat && w->m_isFloating != PWORKSPACE->m_defaultFloating) { - const auto SAVEDPOS = w->m_realPosition->goal(); - const auto SAVEDSIZE = w->m_realSize->goal(); - - w->m_isFloating = PWORKSPACE->m_defaultFloating; - g_pLayoutManager->getCurrentLayout()->changeWindowFloatingMode(w); - - if (PWORKSPACE->m_defaultFloating) { - w->m_realPosition->setValueAndWarp(SAVEDPOS); - w->m_realSize->setValueAndWarp(SAVEDSIZE); - *w->m_realSize = w->m_realSize->value() + Vector2D(4, 4); - *w->m_realPosition = w->m_realPosition->value() - Vector2D(2, 2); - } - } - } - } else { - Debug::log(ERR, "Invalid arg in workspaceOpt, opt \"{}\" doesn't exist.", args); - return {.success = false, .error = std::format("Invalid arg in workspaceOpt, opt \"{}\" doesn't exist.", args)}; - } - - // recalc mon - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(Desktop::focusState()->monitor()->m_id); - - return {}; + return {.success = false, .error = "workspaceopt is deprecated"}; } SDispatchResult CKeybindManager::renameWorkspace(std::string args) { @@ -1973,7 +1713,7 @@ SDispatchResult CKeybindManager::renameWorkspace(std::string args) { else return {.success = false, .error = "No such workspace"}; } catch (std::exception& e) { - Debug::log(ERR, R"(Invalid arg in renameWorkspace, expected numeric id only or a numeric id and string name. "{}": "{}")", args, e.what()); + Log::logger->log(Log::ERR, R"(Invalid arg in renameWorkspace, expected numeric id only or a numeric id and string name. "{}": "{}")", args, e.what()); return {.success = false, .error = std::format(R"(Invalid arg in renameWorkspace, expected numeric id only or a numeric id and string name. "{}": "{}")", args, e.what())}; } @@ -1981,7 +1721,7 @@ SDispatchResult CKeybindManager::renameWorkspace(std::string args) { } SDispatchResult CKeybindManager::exitHyprland(std::string argz) { - g_pConfigManager->dispatchExecShutdown(); + Event::bus()->m_events.exit.emit(); if (g_pCompositor->m_finalRequests) return {}; // Exiting deferred until requests complete @@ -1994,14 +1734,14 @@ SDispatchResult CKeybindManager::moveCurrentWorkspaceToMonitor(std::string args) PHLMONITOR PMONITOR = g_pCompositor->getMonitorFromString(args); if (!PMONITOR) { - Debug::log(ERR, "Ignoring moveCurrentWorkspaceToMonitor: monitor doesn't exist"); + Log::logger->log(Log::ERR, "Ignoring moveCurrentWorkspaceToMonitor: monitor doesn't exist"); return {.success = false, .error = "Ignoring moveCurrentWorkspaceToMonitor: monitor doesn't exist"}; } // get the current workspace const auto PCURRENTWORKSPACE = Desktop::focusState()->monitor()->m_activeWorkspace; if (!PCURRENTWORKSPACE) { - Debug::log(ERR, "moveCurrentWorkspaceToMonitor invalid workspace!"); + Log::logger->log(Log::ERR, "moveCurrentWorkspaceToMonitor invalid workspace!"); return {.success = false, .error = "moveCurrentWorkspaceToMonitor invalid workspace!"}; } @@ -2020,21 +1760,21 @@ SDispatchResult CKeybindManager::moveWorkspaceToMonitor(std::string args) { const auto PMONITOR = g_pCompositor->getMonitorFromString(monitor); if (!PMONITOR) { - Debug::log(ERR, "Ignoring moveWorkspaceToMonitor: monitor doesn't exist"); + Log::logger->log(Log::ERR, "Ignoring moveWorkspaceToMonitor: monitor doesn't exist"); return {.success = false, .error = "Ignoring moveWorkspaceToMonitor: monitor doesn't exist"}; } const auto WORKSPACEID = getWorkspaceIDNameFromString(workspace).id; if (WORKSPACEID == WORKSPACE_INVALID) { - Debug::log(ERR, "moveWorkspaceToMonitor invalid workspace!"); + Log::logger->log(Log::ERR, "moveWorkspaceToMonitor invalid workspace!"); return {.success = false, .error = "moveWorkspaceToMonitor invalid workspace!"}; } const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(WORKSPACEID); if (!PWORKSPACE) { - Debug::log(ERR, "moveWorkspaceToMonitor workspace doesn't exist!"); + Log::logger->log(Log::ERR, "moveWorkspaceToMonitor workspace doesn't exist!"); return {.success = false, .error = "moveWorkspaceToMonitor workspace doesn't exist!"}; } @@ -2046,14 +1786,14 @@ SDispatchResult CKeybindManager::moveWorkspaceToMonitor(std::string args) { SDispatchResult CKeybindManager::focusWorkspaceOnCurrentMonitor(std::string args) { auto [workspaceID, workspaceName, isAutoID] = getWorkspaceIDNameFromString(args); if (workspaceID == WORKSPACE_INVALID) { - Debug::log(ERR, "focusWorkspaceOnCurrentMonitor invalid workspace!"); + Log::logger->log(Log::ERR, "focusWorkspaceOnCurrentMonitor invalid workspace!"); return {.success = false, .error = "focusWorkspaceOnCurrentMonitor invalid workspace!"}; } const auto PCURRMONITOR = Desktop::focusState()->monitor(); if (!PCURRMONITOR) { - Debug::log(ERR, "focusWorkspaceOnCurrentMonitor monitor doesn't exist!"); + Log::logger->log(Log::ERR, "focusWorkspaceOnCurrentMonitor monitor doesn't exist!"); return {.success = false, .error = "focusWorkspaceOnCurrentMonitor monitor doesn't exist!"}; } @@ -2067,7 +1807,7 @@ SDispatchResult CKeybindManager::focusWorkspaceOnCurrentMonitor(std::string args } static auto PBACKANDFORTH = CConfigValue("binds:workspace_back_and_forth"); - const auto PREVWS = pWorkspace->getPrevWorkspaceIDName(); + const auto PREVWS = Desktop::History::workspaceTracker()->previousWorkspaceIDName(pWorkspace); if (*PBACKANDFORTH && PCURRMONITOR->activeWorkspaceID() == workspaceID && PREVWS.id != -1) { // Workspace to focus is previous workspace @@ -2081,7 +1821,7 @@ SDispatchResult CKeybindManager::focusWorkspaceOnCurrentMonitor(std::string args if (pWorkspace->m_monitor != PCURRMONITOR) { const auto POLDMONITOR = pWorkspace->m_monitor.lock(); if (!POLDMONITOR) { // wat - Debug::log(ERR, "focusWorkspaceOnCurrentMonitor old monitor doesn't exist!"); + Log::logger->log(Log::ERR, "focusWorkspaceOnCurrentMonitor old monitor doesn't exist!"); return {.success = false, .error = "focusWorkspaceOnCurrentMonitor old monitor doesn't exist!"}; } if (POLDMONITOR->activeWorkspaceID() == workspaceID) { @@ -2100,7 +1840,7 @@ SDispatchResult CKeybindManager::focusWorkspaceOnCurrentMonitor(std::string args SDispatchResult CKeybindManager::toggleSpecialWorkspace(std::string args) { const auto& [workspaceID, workspaceName, isAutoID] = getWorkspaceIDNameFromString("special:" + args); if (workspaceID == WORKSPACE_INVALID || !g_pCompositor->isWorkspaceSpecial(workspaceID)) { - Debug::log(ERR, "Invalid workspace passed to special"); + Log::logger->log(Log::ERR, "Invalid workspace passed to special"); return {.success = false, .error = "Invalid workspace passed to special"}; } @@ -2121,12 +1861,12 @@ SDispatchResult CKeybindManager::toggleSpecialWorkspace(std::string args) { if (requestedWorkspaceIsAlreadyOpen && specialOpenOnMonitor == workspaceID) { // already open on this monitor - Debug::log(LOG, "Toggling special workspace {} to closed", workspaceID); + Log::logger->log(Log::DEBUG, "Toggling special workspace {} to closed", workspaceID); PMONITOR->setSpecialWorkspace(nullptr); focusedWorkspace = PMONITOR->m_activeWorkspace; } else { - Debug::log(LOG, "Toggling special workspace {} to open", workspaceID); + Log::logger->log(Log::DEBUG, "Toggling special workspace {} to open", workspaceID); auto PSPECIALWORKSPACE = g_pCompositor->getWorkspaceByID(workspaceID); if (!PSPECIALWORKSPACE) @@ -2141,9 +1881,9 @@ SDispatchResult CKeybindManager::toggleSpecialWorkspace(std::string args) { if (*PWARPONTOGGLESPECIAL > 0) { auto PLAST = focusedWorkspace->getLastFocusedWindow(); - auto HLSurface = CWLSurface::fromResource(g_pSeatManager->m_state.pointerFocus.lock()); + auto HLSurface = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.pointerFocus.lock()); - if (PLAST && (!HLSurface || HLSurface->getWindow())) + if (PLAST && (!HLSurface || HLSurface->view()->type() == Desktop::View::VIEW_TYPE_WINDOW)) PLAST->warpCursor(*PWARPONTOGGLESPECIAL == 2); } @@ -2157,8 +1897,8 @@ SDispatchResult CKeybindManager::forceRendererReload(std::string args) { if (!m->m_output) continue; - auto rule = g_pConfigManager->getMonitorRuleFor(m); - if (!m->applyMonitorRule(&rule, true)) { + auto rule = Config::monitorRuleMgr()->get(m); + if (!m->applyMonitorRule(std::move(rule), true)) { overAgain = true; break; } @@ -2184,7 +1924,7 @@ SDispatchResult CKeybindManager::resizeActive(std::string args) { if (SIZ.x < 1 || SIZ.y < 1) return {.success = false, .error = "Invalid size provided"}; - g_pLayoutManager->getCurrentLayout()->resizeActiveWindow(SIZ - PLASTWINDOW->m_realSize->goal()); + g_layoutManager->resizeTarget(SIZ - PLASTWINDOW->m_realSize->goal(), PLASTWINDOW->layoutTarget()); if (PLASTWINDOW->m_realSize->goal().x > 1 && PLASTWINDOW->m_realSize->goal().y > 1) PLASTWINDOW->setHidden(false); @@ -2203,7 +1943,7 @@ SDispatchResult CKeybindManager::moveActive(std::string args) { const auto POS = g_pCompositor->parseWindowVectorArgsRelative(args, PLASTWINDOW->m_realPosition->goal()); - g_pLayoutManager->getCurrentLayout()->moveActiveWindow(POS - PLASTWINDOW->m_realPosition->goal()); + g_layoutManager->moveTarget(POS - PLASTWINDOW->m_realPosition->goal(), PLASTWINDOW->layoutTarget()); return {}; } @@ -2216,7 +1956,7 @@ SDispatchResult CKeybindManager::moveWindow(std::string args) { const auto PWINDOW = g_pCompositor->getWindowByRegex(WINDOWREGEX); if (!PWINDOW) { - Debug::log(ERR, "moveWindow: no window"); + Log::logger->log(Log::ERR, "moveWindow: no window"); return {.success = false, .error = "moveWindow: no window"}; } @@ -2225,7 +1965,7 @@ SDispatchResult CKeybindManager::moveWindow(std::string args) { const auto POS = g_pCompositor->parseWindowVectorArgsRelative(MOVECMD, PWINDOW->m_realPosition->goal()); - g_pLayoutManager->getCurrentLayout()->moveActiveWindow(POS - PWINDOW->m_realPosition->goal(), PWINDOW); + g_layoutManager->moveTarget(POS - PWINDOW->m_realPosition->goal(), PWINDOW->layoutTarget()); return {}; } @@ -2238,7 +1978,7 @@ SDispatchResult CKeybindManager::resizeWindow(std::string args) { const auto PWINDOW = g_pCompositor->getWindowByRegex(WINDOWREGEX); if (!PWINDOW) { - Debug::log(ERR, "resizeWindow: no window"); + Log::logger->log(Log::ERR, "resizeWindow: no window"); return {.success = false, .error = "resizeWindow: no window"}; } @@ -2250,7 +1990,7 @@ SDispatchResult CKeybindManager::resizeWindow(std::string args) { if (SIZ.x < 1 || SIZ.y < 1) return {.success = false, .error = "Invalid size provided"}; - g_pLayoutManager->getCurrentLayout()->resizeActiveWindow(SIZ - PWINDOW->m_realSize->goal(), CORNER_NONE, PWINDOW); + g_layoutManager->resizeTarget(SIZ - PWINDOW->m_realSize->goal(), PWINDOW->layoutTarget(), Layout::CORNER_NONE); if (PWINDOW->m_realSize->goal().x > 1 && PWINDOW->m_realSize->goal().y > 1) PWINDOW->setHidden(false); @@ -2272,14 +2012,32 @@ SDispatchResult CKeybindManager::circleNext(std::string arg) { CVarList args{arg, 0, 's', true}; + const auto PREV = args.contains("prev") || args.contains("p") || args.contains("last") || args.contains("l"); + std::optional floatStatus = {}; - if (args.contains("tile") || args.contains("tiled")) - floatStatus = false; - else if (args.contains("float") || args.contains("floating")) + if (args.contains("tile") || args.contains("tiled")) { + // if we want just tiled, and we are on a tiled window, use layoutmsg for layouts that support it + + if (!Desktop::focusState()->window()->m_isFloating) { + if (const auto SPACE = Desktop::focusState()->window()->layoutTarget()->space(); SPACE) { + + constexpr const std::array LAYOUTS_WITH_CYCLE_NEXT = { + &typeid(Layout::Tiled::CMonocleAlgorithm), + &typeid(Layout::Tiled::CMasterAlgorithm), + }; + + if (std::ranges::contains(LAYOUTS_WITH_CYCLE_NEXT, &typeid(*SPACE->algorithm()->tiledAlgo().get()))) { + CKeybindManager::layoutmsg(PREV ? "cyclenext, b" : "cyclenext"); + return {}; + } + } + } + } + + if (args.contains("float") || args.contains("floating")) floatStatus = true; const auto VISIBLE = args.contains("visible") || args.contains("v"); - const auto PREV = args.contains("prev") || args.contains("p") || args.contains("last") || args.contains("l"); const auto NEXT = args.contains("next") || args.contains("n"); // prev is default in classic alt+tab const auto HIST = args.contains("hist") || args.contains("h"); const auto& w = HIST ? g_pCompositor->getWindowCycleHist(Desktop::focusState()->window(), true, floatStatus, VISIBLE, NEXT) : @@ -2296,11 +2054,11 @@ SDispatchResult CKeybindManager::focusWindow(std::string regexp) { if (!PWINDOW) return {.success = false, .error = "No such window found"}; - Debug::log(LOG, "Focusing to window name: {}", PWINDOW->m_title); + Log::logger->log(Log::DEBUG, "Focusing to window name: {}", PWINDOW->m_title); const auto PWORKSPACE = PWINDOW->m_workspace; if (!PWORKSPACE) { - Debug::log(ERR, "BUG THIS: null workspace in focusWindow"); + Log::logger->log(Log::ERR, "BUG THIS: null workspace in focusWindow"); return {.success = false, .error = "BUG THIS: null workspace in focusWindow"}; } @@ -2308,11 +2066,11 @@ SDispatchResult CKeybindManager::focusWindow(std::string regexp) { if (Desktop::focusState()->monitor() && Desktop::focusState()->monitor()->m_activeWorkspace != PWINDOW->m_workspace && Desktop::focusState()->monitor()->m_activeSpecialWorkspace != PWINDOW->m_workspace) { - Debug::log(LOG, "Fake executing workspace to move focus"); + Log::logger->log(Log::DEBUG, "Fake executing workspace to move focus"); changeworkspace(PWORKSPACE->getConfigName()); } - Desktop::focusState()->fullWindowFocus(PWINDOW, nullptr, false); + Desktop::focusState()->fullWindowFocus(PWINDOW, Desktop::FOCUS_REASON_KEYBIND, nullptr, false); PWINDOW->warpCursor(); @@ -2348,12 +2106,12 @@ SDispatchResult CKeybindManager::toggleSwallow(std::string args) { // Unswallow pWindow->m_swallowed->m_currentlySwallowed = false; pWindow->m_swallowed->setHidden(false); - g_pLayoutManager->getCurrentLayout()->onWindowCreated(pWindow->m_swallowed.lock()); + g_layoutManager->newTarget(pWindow->m_swallowed->layoutTarget(), pWindow->m_workspace->m_space); } else { // Reswallow pWindow->m_swallowed->m_currentlySwallowed = true; pWindow->m_swallowed->setHidden(true); - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow->m_swallowed.lock()); + g_layoutManager->removeTarget(pWindow->m_swallowed->layoutTarget()); } return {}; @@ -2362,23 +2120,23 @@ SDispatchResult CKeybindManager::toggleSwallow(std::string args) { SDispatchResult CKeybindManager::setSubmap(std::string submap) { if (submap == "reset" || submap.empty()) { m_currentSelectedSubmap.name = ""; - Debug::log(LOG, "Reset active submap to the default one."); + Log::logger->log(Log::DEBUG, "Reset active submap to the default one."); g_pEventManager->postEvent(SHyprIPCEvent{"submap", ""}); - EMIT_HOOK_EVENT("submap", m_currentSelectedSubmap.name); + Event::bus()->m_events.keybinds.submap.emit(m_currentSelectedSubmap.name); return {}; } for (const auto& k : g_pKeybindManager->m_keybinds) { if (k->submap.name == submap) { m_currentSelectedSubmap.name = submap; - Debug::log(LOG, "Changed keybind submap to {}", submap); + Log::logger->log(Log::DEBUG, "Changed keybind submap to {}", submap); g_pEventManager->postEvent(SHyprIPCEvent{"submap", submap}); - EMIT_HOOK_EVENT("submap", m_currentSelectedSubmap.name); + Event::bus()->m_events.keybinds.submap.emit(m_currentSelectedSubmap.name); return {}; } } - Debug::log(ERR, "Cannot set submap {}, submap doesn't exist (wasn't registered!)", submap); + Log::logger->log(Log::ERR, "Cannot set submap {}, submap doesn't exist (wasn't registered!)", submap); return {.success = false, .error = std::format("Cannot set submap {}, submap doesn't exist (wasn't registered!)", submap)}; } @@ -2388,12 +2146,12 @@ SDispatchResult CKeybindManager::pass(std::string regexp) { const auto PWINDOW = g_pCompositor->getWindowByRegex(regexp); if (!PWINDOW) { - Debug::log(ERR, "pass: window not found"); + Log::logger->log(Log::ERR, "pass: window not found"); return {.success = false, .error = "pass: window not found"}; } if (!g_pSeatManager->m_keyboard) { - Debug::log(ERR, "No kb in pass?"); + Log::logger->log(Log::ERR, "No kb in pass?"); return {.success = false, .error = "No kb in pass?"}; } @@ -2404,9 +2162,9 @@ SDispatchResult CKeybindManager::pass(std::string regexp) { // pass all mf shit if (!XWTOXW) { if (g_pKeybindManager->m_lastCode != 0) - g_pSeatManager->setKeyboardFocus(PWINDOW->m_wlSurface->resource()); + g_pSeatManager->setKeyboardFocus(PWINDOW->wlSurface()->resource()); else - g_pSeatManager->setPointerFocus(PWINDOW->m_wlSurface->resource(), {1, 1}); + g_pSeatManager->setPointerFocus(PWINDOW->wlSurface()->resource(), {1, 1}); } g_pSeatManager->sendKeyboardMods(g_pInputManager->getModsFromAllKBs(), 0, 0, 0); @@ -2462,7 +2220,7 @@ SDispatchResult CKeybindManager::sendshortcut(std::string args) { // args=[,WINDOW_RULES] const auto ARGS = CVarList(args, 3); if (ARGS.size() != 3) { - Debug::log(ERR, "sendshortcut: invalid args"); + Log::logger->log(Log::ERR, "sendshortcut: invalid args"); return {.success = false, .error = "sendshortcut: invalid args"}; } @@ -2480,7 +2238,7 @@ SDispatchResult CKeybindManager::sendshortcut(std::string args) { keycode = std::stoi(KEY.substr(6)); isMouse = true; if (keycode < 272) { - Debug::log(ERR, "sendshortcut: invalid mouse button"); + Log::logger->log(Log::ERR, "sendshortcut: invalid mouse button"); return {.success = false, .error = "sendshortcut: invalid mouse button"}; } } else { @@ -2495,7 +2253,7 @@ SDispatchResult CKeybindManager::sendshortcut(std::string args) { const auto KB = g_pSeatManager->m_keyboard; if (!KB) { - Debug::log(ERR, "sendshortcut: no kb"); + Log::logger->log(Log::ERR, "sendshortcut: no kb"); return {.success = false, .error = "sendshortcut: no kb"}; } @@ -2519,7 +2277,7 @@ SDispatchResult CKeybindManager::sendshortcut(std::string args) { } if (!keycode) { - Debug::log(ERR, "sendshortcut: key not found"); + Log::logger->log(Log::ERR, "sendshortcut: key not found"); return {.success = false, .error = "sendshortcut: key not found"}; } @@ -2528,7 +2286,7 @@ SDispatchResult CKeybindManager::sendshortcut(std::string args) { } if (!keycode) { - Debug::log(ERR, "sendshortcut: invalid key"); + Log::logger->log(Log::ERR, "sendshortcut: invalid key"); return {.success = false, .error = "sendshortcut: invalid key"}; } @@ -2542,25 +2300,25 @@ SDispatchResult CKeybindManager::sendshortcut(std::string args) { PWINDOW = g_pCompositor->getWindowByRegex(regexp); if (!PWINDOW) { - Debug::log(ERR, "sendshortcut: window not found"); + Log::logger->log(Log::ERR, "sendshortcut: window not found"); return {.success = false, .error = "sendshortcut: window not found"}; } if (!g_pSeatManager->m_keyboard) { - Debug::log(ERR, "No kb in sendshortcut?"); + Log::logger->log(Log::ERR, "No kb in sendshortcut?"); return {.success = false, .error = "No kb in sendshortcut?"}; } if (!isMouse) - g_pSeatManager->setKeyboardFocus(PWINDOW->m_wlSurface->resource()); + g_pSeatManager->setKeyboardFocus(PWINDOW->wlSurface()->resource()); else - g_pSeatManager->setPointerFocus(PWINDOW->m_wlSurface->resource(), {1, 1}); + g_pSeatManager->setPointerFocus(PWINDOW->wlSurface()->resource(), {1, 1}); } //copied the rest from pass and modified it // if wl -> xwl, activate destination if (PWINDOW && PWINDOW->m_isX11 && Desktop::focusState()->window() && !Desktop::focusState()->window()->m_isX11) - g_pXWaylandManager->activateSurface(PWINDOW->m_wlSurface->resource(), true); + g_pXWaylandManager->activateSurface(PWINDOW->wlSurface()->resource(), true); // if xwl -> xwl, send to current. Timing issues make this not work. if (PWINDOW && PWINDOW->m_isX11 && Desktop::focusState()->window() && Desktop::focusState()->window()->m_isX11) PWINDOW = nullptr; @@ -2614,9 +2372,9 @@ SDispatchResult CKeybindManager::sendshortcut(std::string args) { } SDispatchResult CKeybindManager::layoutmsg(std::string msg) { - SLayoutMessageHeader hd = {Desktop::focusState()->window()}; - g_pLayoutManager->getCurrentLayout()->layoutMessage(hd, msg); - + auto ret = g_layoutManager->layoutMsg(msg); + if (!ret) + return {.success = false, .error = ret.error()}; return {}; } @@ -2629,7 +2387,9 @@ SDispatchResult CKeybindManager::dpms(std::string arg) { if (arg.find_first_of(' ') != std::string::npos) port = arg.substr(arg.find_first_of(' ') + 1); - for (auto const& m : g_pCompositor->m_monitors) { + for (auto const& m : g_pCompositor->m_realMonitors) { + if (!m->m_enabled) + continue; if (!port.empty() && m->m_name != port) continue; @@ -2668,11 +2428,11 @@ SDispatchResult CKeybindManager::swapnext(std::string arg) { if (toSwap == PLASTWINDOW) toSwap = g_pCompositor->getWindowCycle(PLASTWINDOW, true, std::nullopt, false, NEED_PREV); - g_pLayoutManager->getCurrentLayout()->switchWindows(PLASTWINDOW, toSwap); + g_layoutManager->switchTargets(PLASTWINDOW->layoutTarget(), toSwap->layoutTarget(), false); PLASTWINDOW->m_lastCycledWindow = toSwap; - Desktop::focusState()->fullWindowFocus(PLASTWINDOW); + Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_KEYBIND); return {}; } @@ -2705,7 +2465,7 @@ SDispatchResult CKeybindManager::pinActive(std::string args) { PWINDOW = Desktop::focusState()->window(); if (!PWINDOW) { - Debug::log(ERR, "pin: window not found"); + Log::logger->log(Log::ERR, "pin: window not found"); return {.success = false, .error = "pin: window not found"}; } @@ -2717,20 +2477,23 @@ SDispatchResult CKeybindManager::pinActive(std::string args) { const auto PMONITOR = PWINDOW->m_monitor.lock(); if (!PMONITOR) { - Debug::log(ERR, "pin: monitor not found"); + Log::logger->log(Log::ERR, "pin: monitor not found"); return {.success = false, .error = "pin: window not found"}; } - PWINDOW->m_workspace = PMONITOR->m_activeWorkspace; + PWINDOW->layoutTarget()->assignToSpace(PMONITOR->m_activeWorkspace->m_space); PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_PINNED); const auto PWORKSPACE = PWINDOW->m_workspace; - PWORKSPACE->m_lastFocusedWindow = g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), RESERVED_EXTENTS | INPUT_EXTENTS); + PWORKSPACE->m_lastFocusedWindow = + g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS); g_pEventManager->postEvent(SHyprIPCEvent{"pin", std::format("{:x},{}", rc(PWINDOW.get()), sc(PWINDOW->m_pinned))}); - EMIT_HOOK_EVENT("pin", PWINDOW); + Event::bus()->m_events.window.pin.emit(PWINDOW); + + g_pHyprRenderer->damageWindow(PWINDOW, true); return {}; } @@ -2758,30 +2521,26 @@ SDispatchResult CKeybindManager::mouse(std::string args) { SDispatchResult CKeybindManager::changeMouseBindMode(const eMouseBindMode MODE) { if (MODE != MBIND_INVALID) { - if (!g_pInputManager->m_currentlyDraggedWindow.expired() || g_pInputManager->m_dragMode != MBIND_INVALID) + if (g_layoutManager->dragController()->target()) return {}; const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); - const PHLWINDOW PWINDOW = g_pCompositor->vectorToWindowUnified(MOUSECOORDS, RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING); + const PHLWINDOW PWINDOW = g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING); if (!PWINDOW) return SDispatchResult{.passEvent = true}; - if (!PWINDOW->isFullscreen() && MODE == MBIND_MOVE) - PWINDOW->checkInputOnDecos(INPUT_TYPE_DRAG_START, MOUSECOORDS); + if (!PWINDOW->isFullscreen() && MODE == MBIND_MOVE) { + if (PWINDOW->checkInputOnDecos(INPUT_TYPE_DRAG_START, MOUSECOORDS)) + return SDispatchResult{.passEvent = false}; + } - if (g_pInputManager->m_currentlyDraggedWindow.expired()) - g_pInputManager->m_currentlyDraggedWindow = PWINDOW; - - g_pInputManager->m_dragMode = MODE; - - g_pLayoutManager->getCurrentLayout()->onBeginDragWindow(); + g_layoutManager->beginDragTarget(PWINDOW->layoutTarget(), MODE); } else { - if (g_pInputManager->m_currentlyDraggedWindow.expired() || g_pInputManager->m_dragMode == MBIND_INVALID) + if (!g_layoutManager->dragController()->target()) return {}; - g_pLayoutManager->getCurrentLayout()->onEndDragWindow(); - g_pInputManager->m_dragMode = MODE; + g_layoutManager->endDragTarget(); } return {}; @@ -2791,6 +2550,8 @@ SDispatchResult CKeybindManager::bringActiveToTop(std::string args) { if (Desktop::focusState()->window() && Desktop::focusState()->window()->m_isFloating) g_pCompositor->changeWindowZOrder(Desktop::focusState()->window(), true); + g_pInputManager->simulateMouseMovement(); + return {}; } @@ -2803,7 +2564,7 @@ SDispatchResult CKeybindManager::alterZOrder(std::string args) { PWINDOW = Desktop::focusState()->window(); if (!PWINDOW) { - Debug::log(ERR, "alterZOrder: no window"); + Log::logger->log(Log::ERR, "alterZOrder: no window"); return {.success = false, .error = "alterZOrder: no window"}; } @@ -2812,7 +2573,7 @@ SDispatchResult CKeybindManager::alterZOrder(std::string args) { else if (POSITION == "bottom") g_pCompositor->changeWindowZOrder(PWINDOW, false); else { - Debug::log(ERR, "alterZOrder: bad position: {}", POSITION); + Log::logger->log(Log::ERR, "alterZOrder: bad position: {}", POSITION); return {.success = false, .error = "alterZOrder: bad position: {}"}; } @@ -2841,17 +2602,15 @@ SDispatchResult CKeybindManager::lockActiveGroup(std::string args) { if (!PWINDOW) return {.success = false, .error = "No window found"}; - if (!PWINDOW->m_groupData.pNextWindow.lock()) + if (!PWINDOW->m_group) return {.success = false, .error = "Not a group"}; - const auto PHEAD = PWINDOW->getGroupHead(); - if (args == "lock") - PHEAD->m_groupData.locked = true; + PWINDOW->m_group->setLocked(true); else if (args == "toggle") - PHEAD->m_groupData.locked = !PHEAD->m_groupData.locked; + PWINDOW->m_group->setLocked(!PWINDOW->m_group->locked()); else - PHEAD->m_groupData.locked = false; + PWINDOW->m_group->setLocked(false); PWINDOW->updateDecorationValues(); @@ -2859,25 +2618,23 @@ SDispatchResult CKeybindManager::lockActiveGroup(std::string args) { } void CKeybindManager::moveWindowIntoGroup(PHLWINDOW pWindow, PHLWINDOW pWindowInDirection) { - if (pWindow->m_groupData.deny) + if (!pWindowInDirection->m_group || pWindowInDirection->m_group->denied()) return; updateRelativeCursorCoords(); - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow); // This removes grouped property! - if (pWindow->m_monitor != pWindowInDirection->m_monitor) { pWindow->moveToWorkspace(pWindowInDirection->m_workspace); pWindow->m_monitor = pWindowInDirection->m_monitor; } - static auto USECURRPOS = CConfigValue("group:insert_after_current"); - (*USECURRPOS ? pWindowInDirection : pWindowInDirection->getGroupTail())->insertWindowToGroup(pWindow); + if (pWindow->m_group) + pWindow->m_group->remove(pWindow); + pWindowInDirection->m_group->add(pWindow); - pWindowInDirection->setGroupCurrent(pWindow); + pWindowInDirection->m_group->setCurrent(pWindow); pWindow->updateWindowDecos(); - g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow); - Desktop::focusState()->fullWindowFocus(pWindow); + Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND); pWindow->warpCursor(); g_pEventManager->postEvent(SHyprIPCEvent{"moveintogroup", std::format("{:x}", rc(pWindow.get()))}); @@ -2885,70 +2642,92 @@ void CKeybindManager::moveWindowIntoGroup(PHLWINDOW pWindow, PHLWINDOW pWindowIn void CKeybindManager::moveWindowOutOfGroup(PHLWINDOW pWindow, const std::string& dir) { static auto BFOCUSREMOVEDWINDOW = CConfigValue("group:focus_removed_window"); - const auto PWINDOWPREV = pWindow->getGroupPrevious(); - eDirection direction; - switch (dir[0]) { - case 't': - case 'u': direction = DIRECTION_UP; break; - case 'd': - case 'b': direction = DIRECTION_DOWN; break; - case 'l': direction = DIRECTION_LEFT; break; - case 'r': direction = DIRECTION_RIGHT; break; - default: direction = DIRECTION_DEFAULT; - } + if (!pWindow->m_group) + return; - updateRelativeCursorCoords(); + WP group = pWindow->m_group; - if (pWindow->m_groupData.pNextWindow.lock() == pWindow) { - pWindow->destroyGroup(); - } else { - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow); + const auto direction = !dir.empty() ? Math::fromChar(dir[0]) : Math::DIRECTION_DEFAULT; - const auto GROUPSLOCKEDPREV = g_pKeybindManager->m_groupsLocked; - g_pKeybindManager->m_groupsLocked = true; + pWindow->m_group->remove(pWindow, direction); - g_pLayoutManager->getCurrentLayout()->onWindowCreated(pWindow, direction); - - g_pKeybindManager->m_groupsLocked = GROUPSLOCKEDPREV; - } - - if (*BFOCUSREMOVEDWINDOW) { - Desktop::focusState()->fullWindowFocus(pWindow); + if (*BFOCUSREMOVEDWINDOW || !group) { + Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND); pWindow->warpCursor(); } else { - Desktop::focusState()->fullWindowFocus(PWINDOWPREV); - PWINDOWPREV->warpCursor(); + Desktop::focusState()->fullWindowFocus(group->current(), Desktop::FOCUS_REASON_KEYBIND); + group->current()->warpCursor(); } g_pEventManager->postEvent(SHyprIPCEvent{"moveoutofgroup", std::format("{:x}", rc(pWindow.get()))}); } SDispatchResult CKeybindManager::moveIntoGroup(std::string args) { - char arg = args[0]; - static auto PIGNOREGROUPLOCK = CConfigValue("binds:ignore_group_lock"); if (!*PIGNOREGROUPLOCK && g_pKeybindManager->m_groupsLocked) return {}; - if (!isDirection(args)) { - Debug::log(ERR, "Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg); - return {.success = false, .error = std::format("Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg)}; + Math::eDirection dir = Math::fromChar(args[0]); + if (dir == Math::DIRECTION_DEFAULT) { + Log::logger->log(Log::ERR, "Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0]); + return {.success = false, .error = std::format("Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0])}; } const auto PWINDOW = Desktop::focusState()->window(); - if (!PWINDOW || PWINDOW->m_groupData.deny) + if (!PWINDOW) return {}; - auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, arg); + auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, dir); - if (!PWINDOWINDIR || !PWINDOWINDIR->m_groupData.pNextWindow.lock()) + if (!PWINDOWINDIR || !PWINDOWINDIR->m_group) return {}; + const auto GROUP = PWINDOWINDIR->m_group; + // Do not move window into locked group if binds:ignore_group_lock is false - if (!*PIGNOREGROUPLOCK && (PWINDOWINDIR->getGroupHead()->m_groupData.locked || (PWINDOW->m_groupData.pNextWindow.lock() && PWINDOW->getGroupHead()->m_groupData.locked))) + if (!*PIGNOREGROUPLOCK && (GROUP->locked() || (PWINDOW->m_group && PWINDOW->m_group->locked()))) + return {}; + + moveWindowIntoGroup(PWINDOW, PWINDOWINDIR); + + return {}; +} + +SDispatchResult CKeybindManager::moveIntoOrCreateGroup(std::string args) { + 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 or create group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0]); + return {.success = false, .error = std::format("Cannot move into or create group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0])}; + } + + const auto PWINDOW = Desktop::focusState()->window(); + + if (!PWINDOW) + return {}; + + auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, dir); + + if (!PWINDOWINDIR) + return {}; + + if (!PWINDOWINDIR->m_group) { + if (PWINDOWINDIR->isFullscreen()) + return {}; + + PWINDOWINDIR->m_group = Desktop::View::CGroup::create({PWINDOWINDIR}); + } + + const auto GROUP = PWINDOWINDIR->m_group; + + if (!*PIGNOREGROUPLOCK && (GROUP->locked() || (PWINDOW->m_group && PWINDOW->m_group->locked()))) return {}; moveWindowIntoGroup(PWINDOW, PWINDOWINDIR); @@ -2972,7 +2751,7 @@ SDispatchResult CKeybindManager::moveOutOfGroup(std::string args) { if (!PWINDOW) return {.success = false, .error = "No window found"}; - if (!PWINDOW->m_groupData.pNextWindow.lock()) + if (!PWINDOW->m_group) return {.success = false, .error = "Window not in a group"}; moveWindowOutOfGroup(PWINDOW); @@ -2981,13 +2760,12 @@ SDispatchResult CKeybindManager::moveOutOfGroup(std::string args) { } SDispatchResult CKeybindManager::moveWindowOrGroup(std::string args) { - char arg = args[0]; + static auto PIGNOREGROUPLOCK = CConfigValue("binds:ignore_group_lock"); - static auto PIGNOREGROUPLOCK = CConfigValue("binds:ignore_group_lock"); - - if (!isDirection(args)) { - Debug::log(ERR, "Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg); - return {.success = false, .error = std::format("Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg)}; + Math::eDirection dir = Math::fromChar(args[0]); + if (dir == Math::DIRECTION_DEFAULT) { + Log::logger->log(Log::ERR, "Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0]); + return {.success = false, .error = std::format("Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0])}; } const auto PWINDOW = Desktop::focusState()->window(); @@ -2998,35 +2776,36 @@ SDispatchResult CKeybindManager::moveWindowOrGroup(std::string args) { return {}; if (!*PIGNOREGROUPLOCK && g_pKeybindManager->m_groupsLocked) { - g_pLayoutManager->getCurrentLayout()->moveWindowTo(PWINDOW, args); + g_layoutManager->moveInDirection(PWINDOW->layoutTarget(), args); return {}; } - const auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, arg); + const auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, dir); - const bool ISWINDOWGROUP = PWINDOW->m_groupData.pNextWindow; - const bool ISWINDOWGROUPLOCKED = ISWINDOWGROUP && PWINDOW->getGroupHead()->m_groupData.locked; - const bool ISWINDOWGROUPSINGLE = ISWINDOWGROUP && PWINDOW->m_groupData.pNextWindow.lock() == PWINDOW; + const bool ISWINDOWGROUP = PWINDOW->m_group; + const bool ISWINDOWGROUPLOCKED = ISWINDOWGROUP && PWINDOW->m_group->locked(); + const bool ISWINDOWGROUPSINGLE = ISWINDOWGROUP && PWINDOW->m_group->size() == 1; + const bool ISWINDOWGROUPDENIED = ISWINDOWGROUP && PWINDOW->m_group->denied(); updateRelativeCursorCoords(); // note: PWINDOWINDIR is not null implies !PWINDOW->m_isFloating - if (PWINDOWINDIR && PWINDOWINDIR->m_groupData.pNextWindow) { // target is group - if (!*PIGNOREGROUPLOCK && (PWINDOWINDIR->getGroupHead()->m_groupData.locked || ISWINDOWGROUPLOCKED || PWINDOW->m_groupData.deny)) { - g_pLayoutManager->getCurrentLayout()->moveWindowTo(PWINDOW, args); + if (PWINDOWINDIR && PWINDOWINDIR->m_group) { // target is group + if (!*PIGNOREGROUPLOCK && (PWINDOWINDIR->m_group->locked() || ISWINDOWGROUPLOCKED || ISWINDOWGROUPDENIED)) { + g_layoutManager->moveInDirection(PWINDOW->layoutTarget(), args); PWINDOW->warpCursor(); } else moveWindowIntoGroup(PWINDOW, PWINDOWINDIR); } else if (PWINDOWINDIR) { // target is regular window - if ((!*PIGNOREGROUPLOCK && ISWINDOWGROUPLOCKED) || !ISWINDOWGROUP || (ISWINDOWGROUPSINGLE && PWINDOW->m_groupRules & GROUP_SET_ALWAYS)) { - g_pLayoutManager->getCurrentLayout()->moveWindowTo(PWINDOW, args); + if ((!*PIGNOREGROUPLOCK && ISWINDOWGROUPLOCKED) || !ISWINDOWGROUP || (ISWINDOWGROUPSINGLE && PWINDOW->m_groupRules & Desktop::View::GROUP_SET_ALWAYS)) { + g_layoutManager->moveInDirection(PWINDOW->layoutTarget(), args); PWINDOW->warpCursor(); } else moveWindowOutOfGroup(PWINDOW, args); } else if ((*PIGNOREGROUPLOCK || !ISWINDOWGROUPLOCKED) && ISWINDOWGROUP) { // no target window moveWindowOutOfGroup(PWINDOW, args); } else if (!PWINDOWINDIR && !ISWINDOWGROUP) { // no target in dir and not in group - g_pLayoutManager->getCurrentLayout()->moveWindowTo(PWINDOW, args); + g_layoutManager->moveInDirection(PWINDOW->layoutTarget(), args); PWINDOW->warpCursor(); } @@ -3036,27 +2815,28 @@ SDispatchResult CKeybindManager::moveWindowOrGroup(std::string args) { } SDispatchResult CKeybindManager::setIgnoreGroupLock(std::string args) { - static auto PIGNOREGROUPLOCK = rc(g_pConfigManager->getConfigValuePtr("binds:ignore_group_lock")); + // FIXME: this is no longer possible like this. It's redundant anyways. Can be easily scripted / lua'd + // static auto PIGNOREGROUPLOCK = CConfigValue("binds:ignore_group_lock"); - if (args == "toggle") - **PIGNOREGROUPLOCK = !**PIGNOREGROUPLOCK; - else - **PIGNOREGROUPLOCK = args == "on"; + // if (args == "toggle") + // *PIGNOREGROUPLOCK = !*PIGNOREGROUPLOCK; + // else + // *PIGNOREGROUPLOCK = args == "on"; - g_pEventManager->postEvent(SHyprIPCEvent{"ignoregrouplock", std::to_string(**PIGNOREGROUPLOCK)}); + // g_pEventManager->postEvent(SHyprIPCEvent{"ignoregrouplock", std::to_string(**PIGNOREGROUPLOCK)}); return {}; } SDispatchResult CKeybindManager::denyWindowFromGroup(std::string args) { const auto PWINDOW = Desktop::focusState()->window(); - if (!PWINDOW || (PWINDOW && PWINDOW->m_groupData.pNextWindow.lock())) + if (!PWINDOW || (PWINDOW && PWINDOW->m_group)) return {}; if (args == "toggle") - PWINDOW->m_groupData.deny = !PWINDOW->m_groupData.deny; + PWINDOW->m_group->setDenied(!PWINDOW->m_group->denied()); else - PWINDOW->m_groupData.deny = args == "on"; + PWINDOW->m_group->setDenied(args == "on"); PWINDOW->updateDecorationValues(); @@ -3086,16 +2866,15 @@ SDispatchResult CKeybindManager::moveGroupWindow(std::string args) { if (!PLASTWINDOW) return {.success = false, .error = "No window found"}; - if (!PLASTWINDOW->m_groupData.pNextWindow.lock()) + if (!PLASTWINDOW->m_group) return {.success = false, .error = "Window not in a group"}; - if ((!BACK && PLASTWINDOW->m_groupData.pNextWindow->m_groupData.head) || (BACK && PLASTWINDOW->m_groupData.head)) { - std::swap(PLASTWINDOW->m_groupData.head, PLASTWINDOW->m_groupData.pNextWindow->m_groupData.head); - std::swap(PLASTWINDOW->m_groupData.locked, PLASTWINDOW->m_groupData.pNextWindow->m_groupData.locked); - } else - PLASTWINDOW->switchWithWindowInGroup(BACK ? PLASTWINDOW->getGroupPrevious() : PLASTWINDOW->m_groupData.pNextWindow.lock()); + const auto GROUP = PLASTWINDOW->m_group; - PLASTWINDOW->updateWindowDecos(); + if (BACK) + GROUP->swapWithLast(); + else + GROUP->swapWithNext(); return {}; } @@ -3138,7 +2917,7 @@ static void parsePropTrivial(Desktop::Types::COverridableVar& prop, const std prop = Desktop::Types::COverridableVar(std::stof(s), Desktop::Types::PRIORITY_SET_PROP); } else if constexpr (std::is_same_v) prop = Desktop::Types::COverridableVar(s, Desktop::Types::PRIORITY_SET_PROP); - } catch (...) { Debug::log(ERR, "Hyprctl: parsePropTrivial: failed to parse setprop for {}", s); } + } catch (...) { Log::logger->log(Log::ERR, "Hyprctl: parsePropTrivial: failed to parse setprop for {}", s); } } SDispatchResult CKeybindManager::setProp(std::string args) { @@ -3160,15 +2939,25 @@ SDispatchResult CKeybindManager::setProp(std::string args) { try { if (PROP == "max_size") { - PWINDOW->m_ruleApplicator->maxSizeOverride(Desktop::Types::COverridableVar(configStringToVector2D(VAL), Desktop::Types::PRIORITY_SET_PROP)); + const auto SIZE = PWINDOW->calculateExpression(VAL); + if (!SIZE) { + Log::logger->log(Log::ERR, "failed to parse {} as an expression", VAL); + throw "failed to parse expression"; + } + PWINDOW->m_ruleApplicator->maxSizeOverride(Desktop::Types::COverridableVar(*SIZE, Desktop::Types::PRIORITY_SET_PROP)); PWINDOW->clampWindowSize(std::nullopt, PWINDOW->m_ruleApplicator->maxSize().value()); PWINDOW->setHidden(false); } else if (PROP == "min_size") { - PWINDOW->m_ruleApplicator->minSizeOverride(Desktop::Types::COverridableVar(configStringToVector2D(VAL), Desktop::Types::PRIORITY_SET_PROP)); - PWINDOW->clampWindowSize(std::nullopt, PWINDOW->m_ruleApplicator->minSize().value()); + const auto SIZE = PWINDOW->calculateExpression(VAL); + if (!SIZE) { + Log::logger->log(Log::ERR, "failed to parse {} as an expression", VAL); + throw "failed to parse expression"; + } + PWINDOW->m_ruleApplicator->minSizeOverride(Desktop::Types::COverridableVar(*SIZE, Desktop::Types::PRIORITY_SET_PROP)); + PWINDOW->clampWindowSize(PWINDOW->m_ruleApplicator->minSize().value(), std::nullopt); PWINDOW->setHidden(false); } else if (PROP == "active_border_color" || PROP == "inactive_border_color") { - CGradientValueData colorData = {}; + Config::CGradientValueData colorData = {}; if (vars.size() > 4) { for (int i = 3; i < sc(vars.size()); ++i) { const auto TOKEN = vars[i]; @@ -3284,16 +3073,18 @@ SDispatchResult CKeybindManager::setProp(std::string args) { if (PWINDOW->m_ruleApplicator->noFocus().valueOrDefault() != noFocus) { // FIXME: what the fuck is going on here? -vax - Desktop::focusState()->rawWindowFocus(nullptr); - Desktop::focusState()->fullWindowFocus(PWINDOW); - Desktop::focusState()->fullWindowFocus(PLASTWINDOW); + Desktop::focusState()->rawWindowFocus(nullptr, Desktop::FOCUS_REASON_KEYBIND); + Desktop::focusState()->fullWindowFocus(PWINDOW, Desktop::FOCUS_REASON_KEYBIND); + Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_KEYBIND); } if (PROP == "no_vrr") - g_pConfigManager->ensureVRR(PWINDOW->m_monitor.lock()); + Config::monitorRuleMgr()->ensureVRR(); - for (auto const& m : g_pCompositor->m_monitors) - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id); + for (auto const& m : g_pCompositor->m_monitors) { + if (m->m_activeWorkspace) + m->m_activeWorkspace->m_space->recalculate(); + } return {}; } @@ -3302,7 +3093,7 @@ SDispatchResult CKeybindManager::forceIdle(std::string args) { std::optional duration = getPlusMinusKeywordResult(args, 0); if (!duration.has_value()) { - Debug::log(ERR, "Duration invalid in forceIdle!"); + Log::logger->log(Log::ERR, "Duration invalid in forceIdle!"); return {.success = false, .error = "Duration invalid in forceIdle!"}; } @@ -3315,14 +3106,14 @@ SDispatchResult CKeybindManager::sendkeystate(std::string args) { // args=[,WINDOW_RULES] const auto ARGS = CVarList(args, 4); if (ARGS.size() != 4) { - Debug::log(ERR, "sendkeystate: invalid args"); + Log::logger->log(Log::ERR, "sendkeystate: invalid args"); return {.success = false, .error = "sendkeystate: invalid args"}; } const auto STATE = ARGS[2]; if (STATE != "down" && STATE != "repeat" && STATE != "up") { - Debug::log(ERR, "sendkeystate: invalid state, must be 'down', 'repeat', or 'up'"); + Log::logger->log(Log::ERR, "sendkeystate: invalid state, must be 'down', 'repeat', or 'up'"); return {.success = false, .error = "sendkeystate: invalid state, must be 'down', 'repeat', or 'up'"}; } diff --git a/src/managers/KeybindManager.hpp b/src/managers/KeybindManager.hpp index b5b200db5..a51f9add6 100644 --- a/src/managers/KeybindManager.hpp +++ b/src/managers/KeybindManager.hpp @@ -3,6 +3,7 @@ #include "../defines.hpp" #include #include +#include #include #include #include @@ -11,7 +12,6 @@ #include "../helpers/time/Timer.hpp" class CInputManager; -class CConfigManager; class CPluginSystem; class IKeyboard; @@ -26,30 +26,32 @@ struct SSubmap { }; struct SKeybind { - std::string key = ""; - std::set sMkKeys = {}; - uint32_t keycode = 0; - bool catchAll = false; - uint32_t modmask = 0; - std::set sMkMods = {}; - std::string handler = ""; - std::string arg = ""; - bool locked = false; - SSubmap submap = {}; - std::string description = ""; - bool release = false; - bool repeat = false; - bool longPress = false; - bool mouse = false; - bool nonConsuming = false; - bool transparent = false; - bool ignoreMods = false; - bool multiKey = false; - bool hasDescription = false; - bool dontInhibit = false; - bool click = false; - bool drag = false; - bool submapUniversal = false; + std::string key = ""; + std::set sMkKeys = {}; + uint32_t keycode = 0; + bool catchAll = false; + uint32_t modmask = 0; + std::set sMkMods = {}; + std::string handler = ""; + std::string arg = ""; + bool locked = false; + SSubmap submap = {}; + std::string description = ""; + bool release = false; + bool repeat = false; + bool longPress = false; + bool mouse = false; + bool nonConsuming = false; + bool transparent = false; + bool ignoreMods = false; + bool multiKey = false; + bool hasDescription = false; + bool dontInhibit = false; + bool click = false; + bool drag = false; + bool submapUniversal = false; + bool deviceInclusive = false; + std::unordered_set devices = {}; // DO NOT INITIALIZE bool shadowed = false; @@ -88,14 +90,18 @@ enum eMultiKeyCase : uint8_t { MK_FULL_MATCH }; +namespace Config::Legacy { + class CConfigManager; +} + class CKeybindManager { public: CKeybindManager(); ~CKeybindManager(); bool onKeyEvent(std::any, SP); - bool onAxisEvent(const IPointer::SAxisEvent&); - bool onMouseEvent(const IPointer::SButtonEvent&); + bool onAxisEvent(const IPointer::SAxisEvent&, SP); + bool onMouseEvent(const IPointer::SButtonEvent&, SP); void resizeWithBorder(const IPointer::SButtonEvent&); void onSwitchEvent(const std::string&); void onSwitchOnEvent(const std::string&); @@ -145,7 +151,7 @@ class CKeybindManager { CTimer m_scrollTimer; - SDispatchResult handleKeybinds(const uint32_t, const SPressedKeyWithMods&, bool, SP); + SDispatchResult handleKeybinds(const uint32_t, const SPressedKeyWithMods&, bool, SP, SP); std::set m_mkKeys = {}; std::set m_mkMods = {}; @@ -164,9 +170,7 @@ class CKeybindManager { static void moveWindowOutOfGroup(PHLWINDOW pWindow, const std::string& dir = ""); static void moveWindowIntoGroup(PHLWINDOW pWindow, PHLWINDOW pWindowInDirection); - static void switchToWindow(PHLWINDOW PWINDOWTOCHANGETO, bool preserveFocusHistory = false, bool forceFSCycle = false); - static uint64_t spawnRawProc(std::string, PHLWORKSPACE pInitialWorkspace, const std::string& execRuleToken = ""); - static uint64_t spawnWithRules(std::string, PHLWORKSPACE pInitialWorkspace); + static void switchToWindow(PHLWINDOW PWINDOWTOCHANGETO, bool forceFSCycle = false); // -------------- Dispatchers -------------- // static SDispatchResult closeActive(std::string); @@ -194,10 +198,7 @@ class CKeybindManager { static SDispatchResult swapActive(std::string); static SDispatchResult toggleGroup(std::string); static SDispatchResult changeGroupActive(std::string); - static SDispatchResult alterSplitRatio(std::string); static SDispatchResult focusMonitor(std::string); - static SDispatchResult toggleSplit(std::string); - static SDispatchResult swapSplit(std::string); static SDispatchResult moveCursorToCorner(std::string); static SDispatchResult moveCursor(std::string); static SDispatchResult workspaceOpt(std::string); @@ -231,6 +232,7 @@ class CKeybindManager { static SDispatchResult lockGroups(std::string); static SDispatchResult lockActiveGroup(std::string); static SDispatchResult moveIntoGroup(std::string); + static SDispatchResult moveIntoOrCreateGroup(std::string); static SDispatchResult moveOutOfGroup(std::string); static SDispatchResult moveGroupWindow(std::string); static SDispatchResult moveWindowOrGroup(std::string); @@ -243,7 +245,7 @@ class CKeybindManager { friend class CCompositor; friend class CInputManager; - friend class CConfigManager; + friend class Config::Legacy::CConfigManager; friend class CWorkspace; friend class CPointerManager; }; diff --git a/src/managers/LayoutManager.cpp b/src/managers/LayoutManager.cpp deleted file mode 100644 index 449d17006..000000000 --- a/src/managers/LayoutManager.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include "LayoutManager.hpp" - -CLayoutManager::CLayoutManager() { - m_layouts.emplace_back(std::make_pair<>("dwindle", &m_dwindleLayout)); - m_layouts.emplace_back(std::make_pair<>("master", &m_masterLayout)); -} - -IHyprLayout* CLayoutManager::getCurrentLayout() { - return m_layouts[m_currentLayoutID].second; -} - -void CLayoutManager::switchToLayout(std::string layout) { - for (size_t i = 0; i < m_layouts.size(); ++i) { - if (m_layouts[i].first == layout) { - if (i == sc(m_currentLayoutID)) - return; - - getCurrentLayout()->onDisable(); - m_currentLayoutID = i; - getCurrentLayout()->onEnable(); - return; - } - } - - Debug::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)); - - Debug::log(LOG, "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"); - - Debug::log(LOG, "Removed a layout {} at {:x}", IT->first, rc(layout)); - - std::erase(m_layouts, *IT); - - return true; -} - -std::vector CLayoutManager::getAllLayoutNames() { - std::vector results(m_layouts.size()); - for (size_t i = 0; i < m_layouts.size(); ++i) - results[i] = m_layouts[i].first; - return results; -} diff --git a/src/managers/LayoutManager.hpp b/src/managers/LayoutManager.hpp deleted file mode 100644 index 80c522fb8..000000000 --- a/src/managers/LayoutManager.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "../layout/DwindleLayout.hpp" -#include "../layout/MasterLayout.hpp" - -class CLayoutManager { - public: - CLayoutManager(); - - IHyprLayout* getCurrentLayout(); - - void switchToLayout(std::string); - - bool addLayout(const std::string& name, IHyprLayout* layout); - bool removeLayout(IHyprLayout* layout); - std::vector getAllLayoutNames(); - - private: - enum eHyprLayouts : uint8_t { - LAYOUT_DWINDLE = 0, - LAYOUT_MASTER - }; - - int m_currentLayoutID = LAYOUT_DWINDLE; - - CHyprDwindleLayout m_dwindleLayout; - CHyprMasterLayout m_masterLayout; - std::vector> m_layouts; -}; - -inline UP g_pLayoutManager; diff --git a/src/managers/PointerManager.cpp b/src/managers/PointerManager.cpp index 52efab0b9..04deca9a7 100644 --- a/src/managers/PointerManager.cpp +++ b/src/managers/PointerManager.cpp @@ -1,45 +1,49 @@ #include "PointerManager.hpp" #include "../Compositor.hpp" #include "../config/ConfigValue.hpp" -#include "../config/ConfigManager.hpp" +#include "../config/legacy/ConfigManager.hpp" #include "../protocols/PointerGestures.hpp" #include "../protocols/RelativePointer.hpp" #include "../protocols/FractionalScale.hpp" #include "../protocols/IdleNotify.hpp" #include "../protocols/core/Compositor.hpp" #include "../protocols/core/Seat.hpp" +#include "debug/log/Logger.hpp" #include "eventLoop/EventLoopManager.hpp" +#include "../render/pass/ClearPassElement.hpp" #include "../render/pass/TexPassElement.hpp" #include "../managers/input/InputManager.hpp" -#include "../managers/HookSystemManager.hpp" #include "../render/Renderer.hpp" #include "../render/OpenGL.hpp" #include "../desktop/state/FocusState.hpp" #include "SeatManager.hpp" #include "../helpers/time/Time.hpp" +#include "../helpers/Drm.hpp" +#include "../event/EventBus.hpp" +#include #include #include #include +#include +#include #include using namespace Hyprutils::Utils; CPointerManager::CPointerManager() { - m_hooks.monitorAdded = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any data) { - auto PMONITOR = std::any_cast(data); - + m_hooks.monitorAdded = Event::bus()->m_events.monitor.added.listen([this](PHLMONITOR monitor) { onMonitorLayoutChange(); - PMONITOR->m_events.modeChanged.listenStatic([this] { g_pEventLoopManager->doLater([this]() { onMonitorLayoutChange(); }); }); - PMONITOR->m_events.disconnect.listenStatic([this] { g_pEventLoopManager->doLater([this]() { onMonitorLayoutChange(); }); }); - PMONITOR->m_events.destroy.listenStatic([this] { + monitor->m_events.modeChanged.listenStatic([this] { g_pEventLoopManager->doLater([this]() { onMonitorLayoutChange(); }); }); + monitor->m_events.disconnect.listenStatic([this] { g_pEventLoopManager->doLater([this]() { onMonitorLayoutChange(); }); }); + monitor->m_events.destroy.listenStatic([this] { if (g_pCompositor && !g_pCompositor->m_isShuttingDown) std::erase_if(m_monitorStates, [](const auto& other) { return other->monitor.expired(); }); }); }); - m_hooks.monitorPreRender = g_pHookSystem->hookDynamic("preMonitorCommit", [this](void* self, SCallbackInfo& info, std::any data) { - auto state = stateFor(std::any_cast(data)); + m_hooks.monitorPreRender = Event::bus()->m_events.monitor.preCommit.listen([this](PHLMONITOR monitor) { + auto state = stateFor(monitor); if (!state) return; @@ -72,8 +76,10 @@ void CPointerManager::lockSoftwareForMonitor(PHLMONITOR mon) { void CPointerManager::unlockSoftwareForMonitor(PHLMONITOR mon) { auto const state = stateFor(mon); state->softwareLocks--; - if (state->softwareLocks < 0) + if (state->softwareLocks < 0) { state->softwareLocks = 0; + Log::logger->log(Log::WARN, "Unlocking SW for monitor while it's not locked"); + } if (state->softwareLocks == 0) updateCursorBackend(); @@ -81,13 +87,22 @@ void CPointerManager::unlockSoftwareForMonitor(PHLMONITOR mon) { bool CPointerManager::softwareLockedFor(PHLMONITOR mon) { auto const state = stateFor(mon); - return state->softwareLocks > 0 || state->hardwareFailed; + return state->softwareLocks > 0 || (state->hardwareFailed && hasCursor() && g_pHyprRenderer->shouldRenderCursor()); +} + +bool CPointerManager::hasVisibleHWCursor(PHLMONITOR pMonitor) { + auto const state = stateFor(pMonitor); + return state->softwareLocks == 0 && !state->hardwareFailed && hasCursor() && g_pHyprRenderer->shouldRenderCursor(); } Vector2D CPointerManager::position() { return m_pointerPos; } +Vector2D CPointerManager::hotspot() { + return m_currentCursorImage.hotspot; +} + bool CPointerManager::hasCursor() { return m_currentCursorImage.pBuffer || m_currentCursorImage.surface; } @@ -107,6 +122,7 @@ void CPointerManager::setCursorBuffer(SP buf, const Vector2 m_currentCursorImage.scale = scale; updateCursorBackend(); damageIfSoftware(); + m_events.cursorChanged.emit(); } return; @@ -124,9 +140,10 @@ void CPointerManager::setCursorBuffer(SP buf, const Vector2 updateCursorBackend(); damageIfSoftware(); + m_events.cursorChanged.emit(); } -void CPointerManager::setCursorSurface(SP surf, const Vector2D& hotspot) { +void CPointerManager::setCursorSurface(SP surf, const Vector2D& hotspot) { damageIfSoftware(); if (surf == m_currentCursorImage.surface) { @@ -135,6 +152,7 @@ void CPointerManager::setCursorSurface(SP surf, const Vector2D& hots m_currentCursorImage.scale = surf && surf->resource() ? surf->resource()->m_current.scale : 1.F; updateCursorBackend(); damageIfSoftware(); + m_events.cursorChanged.emit(); } return; @@ -156,6 +174,7 @@ void CPointerManager::setCursorSurface(SP surf, const Vector2D& hots recheckEnteredOutputs(); updateCursorBackend(); damageIfSoftware(); + m_events.cursorChanged.emit(); }); if (surf->resource()->m_current.texture) { @@ -169,6 +188,7 @@ void CPointerManager::setCursorSurface(SP surf, const Vector2D& hots recheckEnteredOutputs(); updateCursorBackend(); damageIfSoftware(); + m_events.cursorChanged.emit(); } void CPointerManager::recheckEnteredOutputs() { @@ -243,7 +263,7 @@ void CPointerManager::resetCursorImage(bool apply) { for (auto const& ms : m_monitorStates) { if (!ms->monitor || !ms->monitor->m_enabled || !ms->monitor->m_dpmsStatus) { - Debug::log(TRACE, "Not updating hw cursors: disabled / dpms off display"); + Log::logger->log(Log::TRACE, "Not updating hw cursors: disabled / dpms off display"); continue; } @@ -253,6 +273,8 @@ void CPointerManager::resetCursorImage(bool apply) { ms->cursorFrontBuffer = nullptr; } } + + m_events.cursorChanged.emit(); } void CPointerManager::updateCursorBackend() { @@ -260,7 +282,7 @@ void CPointerManager::updateCursorBackend() { for (auto const& m : g_pCompositor->m_monitors) { if (!m->m_enabled || !m->m_dpmsStatus) { - Debug::log(TRACE, "Not updating hw cursors: disabled / dpms off display"); + Log::logger->log(Log::TRACE, "Not updating hw cursors: disabled / dpms off display"); continue; } @@ -274,8 +296,8 @@ void CPointerManager::updateCursorBackend() { continue; } - if (state->softwareLocks > 0 || g_pConfigManager->shouldUseSoftwareCursors(m) || !attemptHardwareCursor(state)) { - Debug::log(TRACE, "Output {} rejected hardware cursors, falling back to sw", m->m_name); + if (state->softwareLocks > 0 || m->shouldUseSoftwareCursors() || !attemptHardwareCursor(state)) { + Log::logger->log(Log::TRACE, "Output {} rejected hardware cursors, falling back to sw", m->m_name); state->box = getCursorBoxLogicalForMonitor(state->monitor.lock()); state->hardwareFailed = true; @@ -305,11 +327,11 @@ void CPointerManager::onCursorMoved() { auto CROSSES = !m->logicalBox().intersection(CURSORBOX).empty(); if (!CROSSES && state->cursorFrontBuffer) { - Debug::log(TRACE, "onCursorMoved for output {}: cursor left the viewport, removing it from the backend", m->m_name); + Log::logger->log(Log::TRACE, "onCursorMoved for output {}: cursor left the viewport, removing it from the backend", m->m_name); setHWCursorBuffer(state, nullptr); continue; } else if (CROSSES && !state->cursorFrontBuffer) { - Debug::log(TRACE, "onCursorMoved for output {}: cursor entered the output, but no front buffer, forcing recalc", m->m_name); + Log::logger->log(Log::TRACE, "onCursorMoved for output {}: cursor entered the output, but no front buffer, forcing recalc", m->m_name); recalc = true; } @@ -343,7 +365,7 @@ bool CPointerManager::attemptHardwareCursor(SP hiding"); + Log::logger->log(Log::TRACE, "[pointer] no texture for hw cursor -> hiding"); setHWCursorBuffer(state, nullptr); return true; } @@ -351,7 +373,7 @@ bool CPointerManager::attemptHardwareCursor(SPlog(Log::TRACE, "[pointer] hw cursor failed rendering"); setHWCursorBuffer(state, nullptr); return false; } @@ -359,7 +381,7 @@ bool CPointerManager::attemptHardwareCursor(SPlog(Log::TRACE, "[pointer] hw cursor failed applying, hiding"); setHWCursorBuffer(state, nullptr); return false; } else @@ -374,7 +396,7 @@ bool CPointerManager::setHWCursorBuffer(SP state, SPmonitor.lock()); - Debug::log(TRACE, "[pointer] hw transformed hotspot for {}: {}", state->monitor->m_name, HOTSPOT); + Log::logger->log(Log::TRACE, "[pointer] hw transformed hotspot for {}: {}", state->monitor->m_name, HOTSPOT); if (!state->monitor->m_output->setCursor(buf, HOTSPOT)) return false; @@ -389,7 +411,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; @@ -397,12 +419,14 @@ SP CPointerManager::renderHWCursorBuffer(SPisNvidia()); - if (maxSize == Vector2D{}) + if (maxSize == Vector2D{}) { + Log::logger->log(Log::TRACE, "hardware cursor has zero max size {}, current {}", maxSize, m_currentCursorImage.size); return nullptr; + } if (maxSize != Vector2D{-1, -1}) { if (cursorSize.x > maxSize.x || cursorSize.y > maxSize.y) { - Debug::log(TRACE, "hardware cursor too big! {} > {}", m_currentCursorImage.size, maxSize); + Log::logger->log(Log::TRACE, "hardware cursor too big! {} > {}", m_currentCursorImage.size, maxSize); return nullptr; } } else @@ -433,14 +457,14 @@ SP CPointerManager::renderHWCursorBuffer(SPmonitor->m_output->getBackend()->preferredAllocator()->drmFD() != g_pCompositor->m_drm.fd; + options.multigpu = state->monitor->isMultiGPU(); // 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) options.format = DRM_FORMAT_ARGB8888; if (!state->monitor->m_cursorSwapchain->reconfigure(options)) { - Debug::log(TRACE, "Failed to reconfigure cursor swapchain"); + Log::logger->log(Log::TRACE, "Failed to reconfigure cursor swapchain"); return nullptr; } } @@ -455,7 +479,7 @@ SP CPointerManager::renderHWCursorBuffer(SPmonitor->m_cursorSwapchain->next(nullptr); if (!buf) { - Debug::log(TRACE, "Failed to acquire a buffer from the cursor swapchain"); + Log::logger->log(Log::TRACE, "Failed to acquire a buffer from the cursor swapchain"); return nullptr; } @@ -470,12 +494,14 @@ SP CPointerManager::renderHWCursorBuffer(SPm_current.texture) { - Debug::log(TRACE, "Cursor CPU surface: format {}, expecting AR24", NFormatUtils::drmFormatName(SURFACE->m_current.texture->m_drmFormat)); + Log::logger->log(Log::TRACE, "Cursor CPU surface: format {}, expecting AR24", NFormatUtils::drmFormatName(SURFACE->m_current.texture->m_drmFormat)); + if (!SURFACE->m_current.texture->m_drmFormat) + SURFACE->m_current.texture->m_drmFormat = DRM_FORMAT_ARGB8888; // FIXME assumes DRM_FORMAT_ARGB8888 if (SURFACE->m_current.texture->m_drmFormat == DRM_FORMAT_ABGR8888) { - Debug::log(TRACE, "Cursor CPU surface format AB24, will flip. WARNING: this will break on big endian!"); + Log::logger->log(Log::TRACE, "Cursor CPU surface format AB24, will flip. WARNING: this will break on big endian!"); flipRB = true; } else if (SURFACE->m_current.texture->m_drmFormat != DRM_FORMAT_ARGB8888) { - Debug::log(TRACE, "Cursor CPU surface format rejected, falling back to sw"); + Log::logger->log(Log::TRACE, "Cursor CPU surface format rejected, falling back to sw"); return nullptr; } } @@ -493,7 +519,7 @@ SP CPointerManager::renderHWCursorBuffer(SPlog(Log::TRACE, "Cannot use dumb copy on dmabuf cursor buffers"); return nullptr; } } @@ -522,24 +548,23 @@ SP CPointerManager::renderHWCursorBuffer(SPm_size / (m_currentCursorImage.size / m_currentCursorImage.scale * state->monitor->m_scale); - cairo_matrix_scale(&matrixPre, SCALE.x, SCALE.y); + const auto SX = SCALE.x, SY = SCALE.y; + const auto BW = sc(DMABUF.size.x), BH = sc(DMABUF.size.y); - if (TR) { - cairo_matrix_rotate(&matrixPre, M_PI_2 * sc(TR)); - - // FIXME: this is wrong, and doesn't work for 5, 6 and 7. (flipped + rot) - // cba to do it rn, does anyone fucking use that?? - if (TR >= WL_OUTPUT_TRANSFORM_FLIPPED) { - cairo_matrix_scale(&matrixPre, -1, 1); - cairo_matrix_translate(&matrixPre, -DMABUF.size.x, 0); - } - - if (TR == 3 || TR == 7) - cairo_matrix_translate(&matrixPre, -DMABUF.size.x, 0); - else if (TR == 2 || TR == 6) - cairo_matrix_translate(&matrixPre, -DMABUF.size.x, -DMABUF.size.y); - else if (TR == 1 || TR == 5) - cairo_matrix_translate(&matrixPre, 0, -DMABUF.size.y); + // Cairo pattern matrix maps destination coords to source coords (inverse of visual transform). + // x_src = xx * x_dst + xy * y_dst + x0 + // y_src = yx * x_dst + yy * y_dst + y0 + // cairo_matrix_init(&m, xx, yx, xy, yy, x0, y0) + switch (TR) { + case WL_OUTPUT_TRANSFORM_NORMAL: + default: cairo_matrix_init(&matrixPre, SX, 0, 0, SY, 0, 0); break; + case WL_OUTPUT_TRANSFORM_90: cairo_matrix_init(&matrixPre, 0, SY, -SX, 0, SX * BW, 0); break; + case WL_OUTPUT_TRANSFORM_180: cairo_matrix_init(&matrixPre, -SX, 0, 0, -SY, SX * BW, SY * BH); break; + case WL_OUTPUT_TRANSFORM_270: cairo_matrix_init(&matrixPre, 0, -SY, SX, 0, 0, SY * BH); break; + case WL_OUTPUT_TRANSFORM_FLIPPED: cairo_matrix_init(&matrixPre, -SX, 0, 0, SY, SX * BW, 0); break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: cairo_matrix_init(&matrixPre, 0, SY, SX, 0, 0, 0); break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: cairo_matrix_init(&matrixPre, SX, 0, 0, -SY, 0, SY * BH); break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: cairo_matrix_init(&matrixPre, 0, -SY, -SX, 0, SX * BW, SY * BH); break; } cairo_pattern_set_matrix(PATTERNPRE, &matrixPre); @@ -561,30 +586,29 @@ SP CPointerManager::renderHWCursorBuffer(SPmakeEGLCurrent(); - g_pHyprOpenGL->m_renderData.pMonitor = state->monitor; + g_pHyprRenderer->m_renderData.pMonitor = state->monitor; auto RBO = g_pHyprRenderer->getOrCreateRenderbuffer(buf, state->monitor->m_cursorSwapchain->currentOptions().format); if (!RBO) { - Debug::log(TRACE, "Failed to create cursor RB with format {}, mod {}", buf->dmabuf().format, buf->dmabuf().modifier); + Log::logger->log(Log::TRACE, "Failed to create cursor RB with format {}, mod {}", buf->dmabuf().format, buf->dmabuf().modifier); return nullptr; } RBO->bind(); - 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}); + CRegion damageRegion = {0, 0, INT_MAX, INT_MAX}; + g_pHyprRenderer->beginFullFakeRender(state->monitor.lock(), damageRegion, RBO->getFB()); + g_pHyprRenderer->startRenderPass(); + g_pHyprRenderer->draw(CClearPassElement::SClearData{{0.F, 0.F, 0.F, 0.F}}); CBox xbox = {{}, Vector2D{m_currentCursorImage.size / m_currentCursorImage.scale * state->monitor->m_scale}.round()}; - Debug::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()); + Log::logger->log(Log::TRACE, "[pointer] monitor: {}, size: {}, hw buf: {}, scale: {:.2f}, monscale: {:.2f}, xbox: {}", state->monitor->m_name, m_currentCursorImage.size, + cursorSize, m_currentCursorImage.scale, state->monitor->m_scale, xbox.size()); - g_pHyprOpenGL->renderTexture(texture, xbox, {}); + g_pHyprRenderer->draw(CTexPassElement::SRenderData{.tex = texture, .box = xbox}, damageRegion); - g_pHyprOpenGL->end(); - g_pHyprOpenGL->m_renderData.pMonitor.reset(); - - g_pHyprRenderer->onRenderbufferDestroy(RBO.get()); + g_pHyprRenderer->endRender(); + g_pHyprRenderer->m_renderData.pMonitor.reset(); return buf; } @@ -637,7 +661,7 @@ void CPointerManager::renderSoftwareCursorsFor(PHLMONITOR pMonitor, const Time:: Vector2D CPointerManager::getCursorPosForMonitor(PHLMONITOR pMonitor) { return CBox{m_pointerPos - pMonitor->m_position, {0, 0}} - .transform(wlTransformToHyprutils(invertTransform(pMonitor->m_transform)), pMonitor->m_transformedSize.x / pMonitor->m_scale, + .transform(Math::wlTransformToHyprutils(Math::invertTransform(pMonitor->m_transform)), pMonitor->m_transformedSize.x / pMonitor->m_scale, pMonitor->m_transformedSize.y / pMonitor->m_scale) .pos() * pMonitor->m_scale; @@ -648,7 +672,7 @@ Vector2D CPointerManager::transformedHotspot(PHLMONITOR pMonitor) { return {}; // doesn't matter, we have no hw cursor, and this is only for hw cursors return CBox{m_currentCursorImage.hotspot * pMonitor->m_scale, {0, 0}} - .transform(wlTransformToHyprutils(invertTransform(pMonitor->m_transform)), pMonitor->m_cursorSwapchain->currentOptions().size.x, + .transform(Math::wlTransformToHyprutils(Math::invertTransform(pMonitor->m_transform)), pMonitor->m_cursorSwapchain->currentOptions().size.x, pMonitor->m_cursorSwapchain->currentOptions().size.y) .pos(); } @@ -738,17 +762,26 @@ Vector2D CPointerManager::closestValid(const Vector2D& pos) { } void CPointerManager::damageIfSoftware() { + if (g_pCompositor->m_unsafeState) + return; + auto b = getCursorBoxGlobal().expand(4); for (auto const& mw : m_monitorStates) { - if (mw->monitor.expired() || !mw->monitor->m_output) + auto monitor = mw->monitor.lock(); + if (!monitor || !monitor->m_output || monitor->isMirror()) continue; - if ((mw->softwareLocks > 0 || mw->hardwareFailed || g_pConfigManager->shouldUseSoftwareCursors(mw->monitor.lock())) && - b.overlaps({mw->monitor->m_position, mw->monitor->m_size})) { - g_pHyprRenderer->damageBox(b, mw->monitor->shouldSkipScheduleFrameOnMouseEvent()); - break; - } + auto usesSoftwareCursor = (mw->softwareLocks > 0 || mw->hardwareFailed || monitor->shouldUseSoftwareCursors()); + if (!usesSoftwareCursor) + continue; + + auto shouldAddDamage = !monitor->shouldSkipScheduleFrameOnMouseEvent() && b.overlaps({monitor->m_position, monitor->m_size}); + if (!shouldAddDamage) + continue; + + CBox damageBox = b.copy().translate(-monitor->m_position).scale(monitor->m_scale).round(); + monitor->addDamage(damageBox); } } @@ -872,13 +905,17 @@ void CPointerManager::onMonitorLayoutChange() { damageIfSoftware(); } -SP CPointerManager::getCurrentCursorTexture() { +const CPointerManager::SCursorImage& CPointerManager::currentCursorImage() { + return m_currentCursorImage; +} + +SP CPointerManager::getCurrentCursorTexture() { if (!m_currentCursorImage.pBuffer && (!m_currentCursorImage.surface || !m_currentCursorImage.surface->resource()->m_current.texture)) return nullptr; if (m_currentCursorImage.pBuffer) { if (!m_currentCursorImage.bufferTex) - m_currentCursorImage.bufferTex = makeShared(m_currentCursorImage.pBuffer, true); + m_currentCursorImage.bufferTex = g_pHyprRenderer->createTexture(m_currentCursorImage.pBuffer, true); return m_currentCursorImage.bufferTex; } @@ -915,26 +952,16 @@ void CPointerManager::attachPointer(SP pointer) { CKeybindManager::dpms("on"); }); - listener->button = pointer->m_pointerEvents.button.listen([](const IPointer::SButtonEvent& event) { - g_pInputManager->onMouseButton(event); + listener->button = pointer->m_pointerEvents.button.listen([weak = WP(pointer)](const IPointer::SButtonEvent& event) { + g_pInputManager->onMouseButton(event, weak.lock()); PROTO::idle->onActivity(); }); - listener->axis = pointer->m_pointerEvents.axis.listen([weak = WP(pointer)](const IPointer::SAxisEvent& event) { + listener->axis = pointer->m_pointerEvents.axis.listen([weak = WP(pointer)](const IPointer::SAxisEvent& event) { g_pInputManager->onMouseWheel(event, weak.lock()); PROTO::idle->onActivity(); }); - - listener->frame = pointer->m_pointerEvents.frame.listen([] { - bool shouldSkip = false; - if (!g_pSeatManager->m_mouse.expired() && g_pInputManager->isLocked()) { - auto PMONITOR = Desktop::focusState()->monitor().get(); - shouldSkip = PMONITOR && PMONITOR->shouldSkipScheduleFrameOnMouseEvent(); - } - g_pSeatManager->m_isPointerFrameSkipped = shouldSkip; - if (!g_pSeatManager->m_isPointerFrameSkipped) - g_pSeatManager->sendPointerFrame(); - }); + listener->frame = pointer->m_pointerEvents.frame.listen([] { g_pInputManager->onPointerFrame(); }); listener->swipeBegin = pointer->m_pointerEvents.swipeBegin.listen([](const IPointer::SSwipeBeginEvent& event) { g_pInputManager->onSwipeBegin(event); @@ -986,7 +1013,7 @@ void CPointerManager::attachPointer(SP pointer) { PROTO::idle->onActivity(); }); - Debug::log(LOG, "Attached pointer {} to global", pointer->m_hlName); + Log::logger->log(Log::DEBUG, "Attached pointer {} to global", pointer->m_hlName); } void CPointerManager::attachTouch(SP touch) { @@ -1027,7 +1054,7 @@ void CPointerManager::attachTouch(SP touch) { listener->frame = touch->m_touchEvents.frame.listen([] { g_pSeatManager->sendTouchFrame(); }); - Debug::log(LOG, "Attached touch {} to global", touch->m_hlName); + Log::logger->log(Log::DEBUG, "Attached touch {} to global", touch->m_hlName); } void CPointerManager::attachTablet(SP tablet) { @@ -1072,7 +1099,7 @@ void CPointerManager::attachTablet(SP tablet) { }); // clang-format on - Debug::log(LOG, "Attached tablet {} to global", tablet->m_hlName); + Log::logger->log(Log::DEBUG, "Attached tablet {} to global", tablet->m_hlName); } void CPointerManager::detachPointer(SP pointer) { @@ -1087,7 +1114,7 @@ void CPointerManager::detachTablet(SP tablet) { std::erase_if(m_tabletListeners, [tablet](const auto& e) { return e->tablet.expired() || e->tablet == tablet; }); } -void CPointerManager::damageCursor(PHLMONITOR pMonitor) { +void CPointerManager::damageCursor(PHLMONITOR pMonitor, bool skipFrameSchedule) { for (auto const& mw : m_monitorStates) { if (mw->monitor != pMonitor) continue; @@ -1097,7 +1124,7 @@ void CPointerManager::damageCursor(PHLMONITOR pMonitor) { if (b.empty()) return; - g_pHyprRenderer->damageBox(b); + g_pHyprRenderer->damageBox(b, skipFrameSchedule); return; } @@ -1106,22 +1133,3 @@ void CPointerManager::damageCursor(PHLMONITOR pMonitor) { Vector2D CPointerManager::cursorSizeLogical() { return m_currentCursorImage.size / m_currentCursorImage.scale; } - -void CPointerManager::storeMovement(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel) { - m_storedTime = time; - m_storedDelta += delta; - m_storedUnaccel += deltaUnaccel; -} - -void CPointerManager::setStoredMovement(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel) { - m_storedTime = time; - m_storedDelta = delta; - m_storedUnaccel = deltaUnaccel; -} - -void CPointerManager::sendStoredMovement() { - PROTO::relativePointer->sendRelativeMotion(m_storedTime * 1000, m_storedDelta, m_storedUnaccel); - m_storedTime = 0; - m_storedDelta = Vector2D{}; - m_storedUnaccel = Vector2D{}; -} diff --git a/src/managers/PointerManager.hpp b/src/managers/PointerManager.hpp index 62a5d18f2..41e8e32a0 100644 --- a/src/managers/PointerManager.hpp +++ b/src/managers/PointerManager.hpp @@ -4,14 +4,17 @@ #include "../devices/ITouch.hpp" #include "../devices/Tablet.hpp" #include "../helpers/math/Math.hpp" -#include "../desktop/WLSurface.hpp" +#include "../desktop/view/WLSurface.hpp" #include "../helpers/sync/SyncTimeline.hpp" #include "../helpers/time/Time.hpp" +#include "../helpers/signal/Signal.hpp" #include class CMonitor; class IHID; -class CTexture; +namespace Render { + class ITexture; +} AQUAMARINE_FORWARD(IBuffer); @@ -40,7 +43,7 @@ class CPointerManager { void warpAbsolute(Vector2D abs, SP dev); void setCursorBuffer(SP buf, const Vector2D& hotspot, const float& scale); - void setCursorSurface(SP buf, const Vector2D& hotspot); + void setCursorSurface(SP buf, const Vector2D& hotspot); void resetCursorImage(bool apply = true); void lockSoftwareForMonitor(PHLMONITOR pMonitor); @@ -48,6 +51,7 @@ class CPointerManager { void lockSoftwareAll(); void unlockSoftwareAll(); bool softwareLockedFor(PHLMONITOR pMonitor); + bool hasVisibleHWCursor(PHLMONITOR pMonitor); void renderSoftwareCursorsFor(PHLMONITOR pMonitor, const Time::steady_tp& now, CRegion& damage /* logical */, std::optional overridePos = {} /* monitor-local */, bool forceRender = false); @@ -55,17 +59,38 @@ class CPointerManager { // this is needed e.g. during screensharing where // the software cursors aren't locked during the cursor move, but they // are rendered later. - void damageCursor(PHLMONITOR pMonitor); + void damageCursor(PHLMONITOR pMonitor, bool skipFrameSchedule = false); // Vector2D position(); + Vector2D hotspot(); Vector2D cursorSizeLogical(); - void storeMovement(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel); - void setStoredMovement(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel); - void sendStoredMovement(); void recheckEnteredOutputs(); + // returns the thing in global coords + CBox getCursorBoxGlobal(); + + struct SCursorImage { + SP pBuffer; + SP bufferTex; + WP surface; + + Vector2D hotspot; + Vector2D size; + float scale = 1.F; + + CHyprSignalListener destroySurface; + CHyprSignalListener commitSurface; + }; + + const SCursorImage& currentCursorImage(); + SP getCurrentCursorTexture(); + + struct { + CSignalT<> cursorChanged; + } m_events; + private: void recheckPointerPosition(); void onMonitorLayoutChange(); @@ -81,13 +106,9 @@ class CPointerManager { // returns the thing in device coordinates. Is NOT offset by the hotspot, relies on set_cursor with hotspot. Vector2D getCursorPosForMonitor(PHLMONITOR pMonitor); // returns the thing in logical coordinates of the monitor - CBox getCursorBoxLogicalForMonitor(PHLMONITOR pMonitor); - // returns the thing in global coords - CBox getCursorBoxGlobal(); + CBox getCursorBoxLogicalForMonitor(PHLMONITOR pMonitor); - Vector2D transformedHotspot(PHLMONITOR pMonitor); - - SP getCurrentCursorTexture(); + Vector2D transformedHotspot(PHLMONITOR pMonitor); struct SPointerListener { CHyprSignalListener destroy; @@ -139,24 +160,9 @@ class CPointerManager { std::vector monitorBoxes; } m_currentMonitorLayout; - struct { - SP pBuffer; - SP bufferTex; - WP surface; + SCursorImage m_currentCursorImage; // TODO: support various sizes per-output so we can have pixel-perfect cursors - Vector2D hotspot; - Vector2D size; - float scale = 1.F; - - CHyprSignalListener destroySurface; - CHyprSignalListener commitSurface; - } m_currentCursorImage; // TODO: support various sizes per-output so we can have pixel-perfect cursors - - Vector2D m_pointerPos = {0, 0}; - - uint64_t m_storedTime = 0; - Vector2D m_storedDelta = {0, 0}; - Vector2D m_storedUnaccel = {0, 0}; + Vector2D m_pointerPos = {0, 0}; struct SMonitorPointerState { SMonitorPointerState(const PHLMONITOR& m) : monitor(m) {} @@ -177,12 +183,12 @@ class CPointerManager { std::vector> m_monitorStates; SP stateFor(PHLMONITOR mon); bool attemptHardwareCursor(SP state); - SP renderHWCursorBuffer(SP state, SP texture); + SP renderHWCursorBuffer(SP state, SP texture); bool setHWCursorBuffer(SP state, SP buf); struct { - SP monitorAdded; - SP monitorPreRender; + CHyprSignalListener monitorAdded; + CHyprSignalListener monitorPreRender; } m_hooks; }; diff --git a/src/managers/ProtocolManager.cpp b/src/managers/ProtocolManager.cpp index 0f27ffd66..a2b34ced3 100644 --- a/src/managers/ProtocolManager.cpp +++ b/src/managers/ProtocolManager.cpp @@ -50,6 +50,8 @@ #include "../protocols/SecurityContext.hpp" #include "../protocols/CTMControl.hpp" #include "../protocols/HyprlandSurface.hpp" +#include "../protocols/ImageCaptureSource.hpp" +#include "../protocols/ImageCopyCapture.hpp" #include "../protocols/core/Seat.hpp" #include "../protocols/core/DataDevice.hpp" #include "../protocols/core/Compositor.hpp" @@ -57,8 +59,6 @@ #include "../protocols/core/Output.hpp" #include "../protocols/core/Shm.hpp" #include "../protocols/ColorManagement.hpp" -#include "../protocols/XXColorManagement.hpp" -#include "../protocols/FrogColorManagement.hpp" #include "../protocols/ContentType.hpp" #include "../protocols/XDGTag.hpp" #include "../protocols/XDGBell.hpp" @@ -67,8 +67,10 @@ #include "../protocols/PointerWarp.hpp" #include "../protocols/Fifo.hpp" #include "../protocols/CommitTiming.hpp" +#include "../protocols/XDGForeignV2.hpp" #include "../helpers/Monitor.hpp" +#include "../event/EventBus.hpp" #include "../render/Renderer.hpp" #include "../Compositor.hpp" #include "content-type-v1.hpp" @@ -99,21 +101,21 @@ void CProtocolManager::onMonitorModeChange(PHLMONITOR pMonitor) { } if (PROTO::colorManagement && g_pCompositor->shouldChangePreferredImageDescription()) { - Debug::log(ERR, "FIXME: color management protocol is enabled, need a preferred image description id"); + Log::logger->log(Log::ERR, "FIXME: color management protocol is enabled, need a preferred image description id"); PROTO::colorManagement->onImagePreferredChanged(0); } } CProtocolManager::CProtocolManager() { - static const auto PENABLECM = CConfigValue("render:cm_enabled"); - static const auto PENABLEXXCM = CConfigValue("experimental:xx_color_management_v4"); - static const auto PDEBUGCM = CConfigValue("debug:full_cm_proto"); + static const auto PENABLECM = CConfigValue("render:cm_enabled"); + static const auto PDEBUGCM = CConfigValue("debug:full_cm_proto"); + static const auto PCMV1_2 = CConfigValue("experimental:wp_cm_1_2"); + + static const auto PENABLECT = CConfigValue("render:commit_timing_enabled"); // Outputs are a bit dumb, we have to agree. - static auto P = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any param) { - auto M = std::any_cast(param); - + static auto P = Event::bus()->m_events.monitor.added.listen([this](PHLMONITOR M) { // ignore mirrored outputs. I don't think this will ever be hit as mirrors are applied after // this event is emitted iirc. // also ignore the fallback @@ -130,8 +132,7 @@ CProtocolManager::CProtocolManager() { m_modeChangeListeners[M->m_name] = M->m_events.modeChanged.listen([this, M] { onMonitorModeChange(M); }); }); - static auto P2 = g_pHookSystem->hookDynamic("monitorRemoved", [this](void* self, SCallbackInfo& info, std::any param) { - auto M = std::any_cast(param); + static auto P2 = Event::bus()->m_events.monitor.removed.listen([this](PHLMONITOR M) { if (!PROTO::outputs.contains(M->m_name)) return; PROTO::outputs.at(M->m_name)->remove(); @@ -143,7 +144,7 @@ CProtocolManager::CProtocolManager() { PROTO::data = makeUnique(&wl_data_device_manager_interface, 3, "WLDataDevice"); PROTO::compositor = makeUnique(&wl_compositor_interface, 6, "WLCompositor"); PROTO::subcompositor = makeUnique(&wl_subcompositor_interface, 1, "WLSubcompositor"); - PROTO::shm = makeUnique(&wl_shm_interface, 1, "WLSHM"); + PROTO::shm = makeUnique(&wl_shm_interface, 2, "WLSHM"); // Extensions PROTO::viewport = makeUnique(&wp_viewporter_interface, 1, "Viewporter"); @@ -181,8 +182,6 @@ CProtocolManager::CProtocolManager() { PROTO::dataWlr = makeUnique(&zwlr_data_control_manager_v1_interface, 2, "DataDeviceWlr"); PROTO::primarySelection = makeUnique(&zwp_primary_selection_device_manager_v1_interface, 1, "PrimarySelection"); PROTO::xwaylandShell = makeUnique(&xwayland_shell_v1_interface, 1, "XWaylandShell"); - PROTO::screencopy = makeUnique(&zwlr_screencopy_manager_v1_interface, 3, "Screencopy"); - PROTO::toplevelExport = makeUnique(&hyprland_toplevel_export_manager_v1_interface, 2, "ToplevelExport"); PROTO::toplevelMapping = makeUnique(&hyprland_toplevel_mapping_manager_v1_interface, 1, "ToplevelMapping"); PROTO::globalShortcuts = makeUnique(&hyprland_global_shortcuts_manager_v1_interface, 1, "GlobalShortcuts"); PROTO::xdgDialog = makeUnique(&xdg_wm_dialog_v1_interface, 1, "XDGDialog"); @@ -197,15 +196,20 @@ CProtocolManager::CProtocolManager() { PROTO::extDataDevice = makeUnique(&ext_data_control_manager_v1_interface, 1, "ExtDataDevice"); PROTO::pointerWarp = makeUnique(&wp_pointer_warp_v1_interface, 1, "PointerWarp"); PROTO::fifo = makeUnique(&wp_fifo_manager_v1_interface, 1, "Fifo"); - PROTO::commitTiming = makeUnique(&wp_commit_timing_manager_v1_interface, 1, "CommitTiming"); + PROTO::xdgForeignExporter = makeUnique(&zxdg_exporter_v2_interface, 1, "XDGForeignExporter"); + PROTO::xdgForeignImporter = makeUnique(&zxdg_importer_v2_interface, 1, "XDGForeignImporter"); + + if (*PENABLECT) + PROTO::commitTiming = makeUnique(&wp_commit_timing_manager_v1_interface, 1, "CommitTiming"); + + // Screensharing Protocols + PROTO::screencopy = makeUnique(&zwlr_screencopy_manager_v1_interface, 3, "Screencopy"); + PROTO::toplevelExport = makeUnique(&hyprland_toplevel_export_manager_v1_interface, 2, "ToplevelExport"); + PROTO::imageCaptureSource = makeUnique(); // ctor inits actual protos, output and toplevel + PROTO::imageCopyCapture = makeUnique(&ext_image_copy_capture_manager_v1_interface, 1, "ImageCopyCapture"); if (*PENABLECM) - PROTO::colorManagement = makeUnique(&wp_color_manager_v1_interface, 1, "ColorManagement", *PDEBUGCM); - - if (*PENABLEXXCM && *PENABLECM) { - PROTO::xxColorManagement = makeUnique(&xx_color_manager_v4_interface, 1, "XXColorManagement"); - PROTO::frogColorManagement = makeUnique(&frog_color_management_factory_v1_interface, 1, "FrogColorManagement"); - } + PROTO::colorManagement = makeUnique(&wp_color_manager_v1_interface, *PCMV1_2 ? 2 : 1, "ColorManagement", *PDEBUGCM); // ! please read the top of this file before adding another protocol @@ -219,20 +223,20 @@ CProtocolManager::CProtocolManager() { else lease.reset(); - if (g_pHyprOpenGL->m_exts.EGL_ANDROID_native_fence_sync_ext && !PROTO::sync) { + if (g_pHyprRenderer->explicitSyncSupported() && !PROTO::sync) { if (g_pCompositor->supportsDrmSyncobjTimeline()) { PROTO::sync = makeUnique(&wp_linux_drm_syncobj_manager_v1_interface, 1, "DRMSyncobj"); - Debug::log(LOG, "DRM Syncobj Timeline support detected, enabling explicit sync protocol"); + Log::logger->log(Log::DEBUG, "DRM Syncobj Timeline support detected, enabling explicit sync protocol"); } else - Debug::log(WARN, "DRM Syncobj Timeline not supported, skipping explicit sync protocol"); + Log::logger->log(Log::WARN, "DRM Syncobj Timeline not supported, skipping explicit sync protocol"); } } - if (!g_pHyprOpenGL->getDRMFormats().empty()) { + if (!g_pHyprRenderer->getDRMFormats().empty()) { PROTO::mesaDRM = makeUnique(&wl_drm_interface, 2, "MesaDRM"); PROTO::linuxDma = makeUnique(&zwp_linux_dmabuf_v1_interface, 5, "LinuxDMABUF"); } else - Debug::log(WARN, "ProtocolManager: Not binding linux-dmabuf and MesaDRM: DMABUF not available"); + Log::logger->log(Log::WARN, "ProtocolManager: Not binding linux-dmabuf and MesaDRM: DMABUF not available"); } CProtocolManager::~CProtocolManager() { @@ -295,15 +299,16 @@ CProtocolManager::~CProtocolManager() { PROTO::hyprlandSurface.reset(); PROTO::contentType.reset(); PROTO::colorManagement.reset(); - PROTO::xxColorManagement.reset(); - PROTO::frogColorManagement.reset(); PROTO::xdgTag.reset(); PROTO::xdgBell.reset(); PROTO::extWorkspace.reset(); PROTO::extDataDevice.reset(); PROTO::pointerWarp.reset(); PROTO::fifo.reset(); + PROTO::xdgForeignExporter.reset(); + PROTO::xdgForeignImporter.reset(); PROTO::commitTiming.reset(); + PROTO::imageCaptureSource.reset(); for (auto& [_, lease] : PROTO::lease) { lease.reset(); @@ -346,9 +351,6 @@ bool CProtocolManager::isGlobalPrivileged(const wl_global* global) { PROTO::constraints->getGlobal(), PROTO::activation->getGlobal(), PROTO::idle->getGlobal(), - PROTO::ime->getGlobal(), - PROTO::virtualKeyboard->getGlobal(), - PROTO::virtualPointer->getGlobal(), PROTO::serverDecorationKDE->getGlobal(), PROTO::tablet->getGlobal(), PROTO::presentation->getGlobal(), @@ -361,6 +363,8 @@ bool CProtocolManager::isGlobalPrivileged(const wl_global* global) { PROTO::xdgBell->getGlobal(), PROTO::fifo->getGlobal(), PROTO::commitTiming->getGlobal(), + PROTO::xdgForeignExporter->getGlobal(), + PROTO::xdgForeignImporter->getGlobal(), PROTO::sync ? PROTO::sync->getGlobal() : nullptr, PROTO::mesaDRM ? PROTO::mesaDRM->getGlobal() : nullptr, PROTO::linuxDma ? PROTO::linuxDma->getGlobal() : nullptr, diff --git a/src/managers/SeatManager.cpp b/src/managers/SeatManager.cpp index 6d49c97eb..5c48eed5a 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/LayerSurface.hpp" +#include "../desktop/view/LayerSurface.hpp" #include "../managers/input/InputManager.hpp" -#include "../managers/HookSystemManager.hpp" #include "wlr-layer-shell-unstable-v1.hpp" #include #include @@ -114,7 +114,7 @@ void CSeatManager::setKeyboardFocus(SP surf) { return; if (!m_keyboard) { - Debug::log(ERR, "BUG THIS: setKeyboardFocus without a valid keyboard set"); + Log::logger->log(Log::ERR, "BUG THIS: setKeyboardFocus without a valid keyboard set"); return; } @@ -211,20 +211,22 @@ void CSeatManager::sendKeyboardMods(uint32_t depressed, uint32_t latched, uint32 } void CSeatManager::setPointerFocus(SP surf, const Vector2D& local) { + const bool dndActive = PROTO::data && PROTO::data->dndActive(); + if (m_state.pointerFocus == surf) return; - if (PROTO::data->dndActive() && surf) { + if (dndActive && surf) { if (m_state.dndPointerFocus == surf) return; - Debug::log(LOG, "[seatmgr] Refusing pointer focus during an active dnd, but setting dndPointerFocus"); + Log::logger->log(Log::DEBUG, "[seatmgr] Refusing pointer focus during an active dnd, but setting dndPointerFocus"); m_state.dndPointerFocus = surf; m_events.dndPointerFocusChange.emit(); return; } if (!m_mouse) { - Debug::log(ERR, "BUG THIS: setPointerFocus without a valid mouse set"); + Log::logger->log(Log::ERR, "BUG THIS: setPointerFocus without a valid mouse set"); return; } @@ -304,7 +306,7 @@ void CSeatManager::sendPointerMotion(uint32_t timeMs, const Vector2D& local) { } void CSeatManager::sendPointerButton(uint32_t timeMs, uint32_t key, wl_pointer_button_state state_) { - if (!m_state.pointerFocusResource || PROTO::data->dndActive()) + if (!m_state.pointerFocusResource || (PROTO::data && PROTO::data->dndActive())) return; for (auto const& s : m_seatResources) { @@ -517,7 +519,7 @@ void CSeatManager::refocusGrab() { // try to find a surf in focus first const auto MOUSE = g_pInputManager->getMouseCoordsInternal(); for (auto const& s : m_seatGrab->m_surfs) { - auto hlSurf = CWLSurface::fromResource(s.lock()); + auto hlSurf = Desktop::View::CWLSurface::fromResource(s.lock()); if (!hlSurf) continue; @@ -545,13 +547,13 @@ void CSeatManager::refocusGrab() { void CSeatManager::onSetCursor(SP seatResource, uint32_t serial, SP surf, const Vector2D& hotspot) { if (!m_state.pointerFocusResource || !seatResource || seatResource->client() != m_state.pointerFocusResource->client()) { - Debug::log(LOG, "[seatmgr] Rejecting a setCursor because the client ain't in focus"); + Log::logger->log(Log::DEBUG, "[seatmgr] Rejecting a setCursor because the client ain't in focus"); return; } // TODO: fix this. Probably should be done in the CWlPointer as the serial could be lost by us. // if (!serialValid(seatResource, serial)) { - // Debug::log(LOG, "[seatmgr] Rejecting a setCursor because the serial is invalid"); + // Log::logger->log(Log::DEBUG, "[seatmgr] Rejecting a setCursor because the serial is invalid"); // return; // } @@ -564,7 +566,7 @@ SP CSeatManager::seatResourceForClient(wl_client* client) { void CSeatManager::setCurrentSelection(SP source) { if (source == m_selection.currentSelection) { - Debug::log(WARN, "[seat] duplicated setCurrentSelection?"); + Log::logger->log(Log::WARN, "[seat] duplicated setCurrentSelection?"); return; } @@ -590,7 +592,7 @@ void CSeatManager::setCurrentSelection(SP source) { void CSeatManager::setCurrentPrimarySelection(SP source) { if (source == m_selection.currentPrimarySelection) { - Debug::log(WARN, "[seat] duplicated setCurrentPrimarySelection?"); + Log::logger->log(Log::WARN, "[seat] duplicated setCurrentPrimarySelection?"); return; } @@ -618,8 +620,9 @@ void CSeatManager::setGrab(SP grab) { if (m_seatGrab) { auto oldGrab = m_seatGrab; - // Try to find the parent window from the grab + // Try to find the parent window or layer surface from the grab PHLWINDOW parentWindow; + PHLLS parentLayer; if (oldGrab && oldGrab->m_surfs.size()) { // Try to find the surface that had focus when the grab ended SP focusedSurf; @@ -640,13 +643,16 @@ void CSeatManager::setGrab(SP grab) { focusedSurf = oldGrab->m_surfs.front().lock(); if (focusedSurf) { - auto hlSurface = CWLSurface::fromResource(focusedSurf); + auto hlSurface = Desktop::View::CWLSurface::fromResource(focusedSurf); if (hlSurface) { - auto popup = hlSurface->getPopup(); + auto popup = Desktop::View::CPopup::fromView(hlSurface->view()); if (popup) { auto t1Owner = popup->getT1Owner(); - if (t1Owner) - parentWindow = t1Owner->getWindow(); + if (t1Owner) { + parentWindow = Desktop::View::CWindow::fromView(t1Owner->view()); + if (!parentWindow) + parentLayer = Desktop::View::CLayerSurface::fromView(t1Owner->view()); + } } } } @@ -654,35 +660,39 @@ void CSeatManager::setGrab(SP grab) { m_seatGrab.reset(); - static auto PFOLLOWMOUSE = CConfigValue("input:follow_mouse"); - if (*PFOLLOWMOUSE == 0 || *PFOLLOWMOUSE == 2 || *PFOLLOWMOUSE == 3) { - const auto PMONITOR = g_pCompositor->getMonitorFromCursor(); + if (parentLayer && parentLayer->m_layerSurface->m_current.interactivity != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE) { + Desktop::focusState()->rawSurfaceFocus(parentLayer->wlSurface()->resource()); + } else { + static auto PFOLLOWMOUSE = CConfigValue("input:follow_mouse"); + if (*PFOLLOWMOUSE == 0 || *PFOLLOWMOUSE == 2 || *PFOLLOWMOUSE == 3) { + const auto PMONITOR = g_pCompositor->getMonitorFromCursor(); - // If this was a popup grab, focus its parent window to maintain context - if (validMapped(parentWindow)) { - Desktop::focusState()->rawWindowFocus(parentWindow); - Debug::log(LOG, "[seatmgr] Refocused popup parent window {} (follow_mouse={})", parentWindow->m_title, *PFOLLOWMOUSE); + // If this was a popup grab, focus its parent window to maintain context + if (validMapped(parentWindow)) { + Desktop::focusState()->rawWindowFocus(parentWindow, Desktop::FOCUS_REASON_FFM); + Log::logger->log(Log::DEBUG, "[seatmgr] Refocused popup parent window {} (follow_mouse={})", parentWindow->m_title, *PFOLLOWMOUSE); + } else + g_pInputManager->refocusLastWindow(PMONITOR); } else - g_pInputManager->refocusLastWindow(PMONITOR); - } else - g_pInputManager->refocus(); + g_pInputManager->refocus(); + } - auto currentFocus = m_state.keyboardFocus.lock(); - auto refocus = !currentFocus; + auto currentFocus = m_state.keyboardFocus.lock(); + auto refocus = !currentFocus; - SP surf; - PHLLS layer; + SP surf; + PHLLS layer; if (!refocus) { - surf = CWLSurface::fromResource(currentFocus); - layer = surf ? surf->getLayer() : nullptr; + surf = Desktop::View::CWLSurface::fromResource(currentFocus); + layer = surf ? Desktop::View::CLayerSurface::fromView(surf->view()) : nullptr; } if (!refocus && !layer) { - auto popup = surf ? surf->getPopup() : nullptr; + auto popup = surf ? Desktop::View::CPopup::fromView(surf->view()) : nullptr; if (popup) { auto parent = popup->getT1Owner(); - layer = parent->getLayer(); + layer = Desktop::View::CLayerSurface::fromView(parent->view()); } } @@ -693,7 +703,7 @@ void CSeatManager::setGrab(SP grab) { auto candidate = Desktop::focusState()->window(); if (candidate) - Desktop::focusState()->rawWindowFocus(candidate); + Desktop::focusState()->rawWindowFocus(candidate, Desktop::FOCUS_REASON_FFM); } if (oldGrab->m_onEnd) diff --git a/src/managers/SeatManager.hpp b/src/managers/SeatManager.hpp index fe11f9308..21736e3ae 100644 --- a/src/managers/SeatManager.hpp +++ b/src/managers/SeatManager.hpp @@ -127,9 +127,6 @@ class CSeatManager { void setGrab(SP grab); // nullptr removes SP m_seatGrab; - bool m_isPointerFrameSkipped = false; - bool m_isPointerFrameCommit = false; - private: struct SSeatResourceContainer { SSeatResourceContainer(SP); diff --git a/src/managers/SessionLockManager.cpp b/src/managers/SessionLockManager.cpp index f460e17e7..e729ae954 100644 --- a/src/managers/SessionLockManager.cpp +++ b/src/managers/SessionLockManager.cpp @@ -5,6 +5,7 @@ #include "../protocols/SessionLock.hpp" #include "../render/Renderer.hpp" #include "../desktop/state/FocusState.hpp" +#include "../desktop/view/SessionLock.hpp" #include "./managers/SeatManager.hpp" #include "./managers/input/InputManager.hpp" #include "./managers/eventLoop/EventLoopManager.hpp" @@ -51,7 +52,7 @@ void CSessionLockManager::onNewSessionLock(SP pLock) { static auto PALLOWRELOCK = CConfigValue("misc:allow_session_lock_restore"); if (PROTO::sessionLock->isLocked() && !*PALLOWRELOCK) { - LOGM(LOG, "Cannot re-lock, misc:allow_session_lock_restore is disabled"); + LOGM(Log::DEBUG, "Cannot re-lock, misc:allow_session_lock_restore is disabled"); pLock->sendDenied(); return; } @@ -59,7 +60,7 @@ void CSessionLockManager::onNewSessionLock(SP pLock) { if (m_sessionLock && !clientDenied() && !clientLocked()) return; // Not allowing to relock in case the old lock is still in a limbo - LOGM(LOG, "Session got locked by {:x}", (uintptr_t)pLock.get()); + LOGM(Log::DEBUG, "Session got locked by {:x}", (uintptr_t)pLock.get()); m_sessionLock = makeUnique(); m_sessionLock->lock = pLock; @@ -68,9 +69,11 @@ void CSessionLockManager::onNewSessionLock(SP pLock) { m_sessionLock->listeners.newSurface = pLock->m_events.newLockSurface.listen([this](const SP& surface) { const auto PMONITOR = surface->monitor(); - const auto NEWSURFACE = m_sessionLock->vSessionLockSurfaces.emplace_back(makeUnique(surface)).get(); + const auto NEWSURFACE = m_sessionLock->vSessionLockSurfaces.emplace_back(makeShared(surface)); NEWSURFACE->iMonitorID = PMONITOR->m_id; PROTO::fractional->sendScale(surface->surface(), PMONITOR->m_scale); + + g_pCompositor->m_otherViews.emplace_back(Desktop::View::CSessionLock::create(surface)); }); m_sessionLock->listeners.unlock = pLock->m_events.unlockAndDestroy.listen([this] { @@ -120,7 +123,7 @@ void CSessionLockManager::onNewSessionLock(SP pLock) { return; } - LOGM(WARN, "Kicking lockscreen client, because it failed to render to all outputs within 5 seconds"); + LOGM(Log::WARN, "Kicking lockscreen client, because it failed to render to all outputs within 5 seconds"); g_pSessionLockManager->m_sessionLock->lock->sendDenied(); g_pSessionLockManager->m_sessionLock->hasSentDenied = true; }, diff --git a/src/managers/SessionLockManager.hpp b/src/managers/SessionLockManager.hpp index 2938a851a..efcaf09a7 100644 --- a/src/managers/SessionLockManager.hpp +++ b/src/managers/SessionLockManager.hpp @@ -33,7 +33,7 @@ struct SSessionLock { CTimer lockTimer; SP sendDeniedTimer; - std::vector> vSessionLockSurfaces; + std::vector> vSessionLockSurfaces; struct { CHyprSignalListener newSurface; diff --git a/src/managers/VersionKeeperManager.cpp b/src/managers/VersionKeeperManager.cpp index 93f820f46..b1d167fac 100644 --- a/src/managers/VersionKeeperManager.cpp +++ b/src/managers/VersionKeeperManager.cpp @@ -1,5 +1,5 @@ #include "VersionKeeperManager.hpp" -#include "../debug/Log.hpp" +#include "../debug/log/Logger.hpp" #include "../macros.hpp" #include "../version.h" #include "../helpers/MiscFunctions.hpp" @@ -34,20 +34,20 @@ CVersionKeeperManager::CVersionKeeperManager() { return; } - if (!isVersionOlderThanRunning(*LASTVER)) { - Debug::log(LOG, "CVersionKeeperManager: Read version {} matches or is older than running.", *LASTVER); + if (!isMajorVersionOlderThanRunning(*LASTVER)) { + Log::logger->log(Log::DEBUG, "CVersionKeeperManager: Read version {} matches or is older than running major.", *LASTVER); return; } NFsUtils::writeToFile(*DATAROOT + "/" + VERSION_FILE_NAME, HYPRLAND_VERSION); if (*PNONOTIFY) { - Debug::log(LOG, "CVersionKeeperManager: updated, but update news is disabled in the config :("); + Log::logger->log(Log::DEBUG, "CVersionKeeperManager: updated, but update news is disabled in the config :("); return; } if (!NFsUtils::executableExistsInPath("hyprland-update-screen")) { - Debug::log(ERR, "CVersionKeeperManager: hyprland-update-screen doesn't seem to exist, skipping notif about update..."); + Log::logger->log(Log::ERR, "CVersionKeeperManager: hyprland-update-screen doesn't seem to exist, skipping notif about update..."); return; } @@ -59,25 +59,21 @@ CVersionKeeperManager::CVersionKeeperManager() { }); } -bool CVersionKeeperManager::isVersionOlderThanRunning(const std::string& ver) { +bool CVersionKeeperManager::isMajorVersionOlderThanRunning(const std::string& ver) { const CVarList verStrings(ver, 0, '.', true); const int V1 = configStringToInt(verStrings[0]).value_or(0); const int V2 = configStringToInt(verStrings[1]).value_or(0); - const int V3 = configStringToInt(verStrings[2]).value_or(0); static const CVarList runningStrings(HYPRLAND_VERSION, 0, '.', true); static const int R1 = configStringToInt(runningStrings[0]).value_or(0); static const int R2 = configStringToInt(runningStrings[1]).value_or(0); - static const int R3 = configStringToInt(runningStrings[2]).value_or(0); if (R1 > V1) return true; if (R2 > V2) return true; - if (R3 > V3) - return true; return false; } diff --git a/src/managers/VersionKeeperManager.hpp b/src/managers/VersionKeeperManager.hpp index 158218799..11bfb7dfa 100644 --- a/src/managers/VersionKeeperManager.hpp +++ b/src/managers/VersionKeeperManager.hpp @@ -10,7 +10,7 @@ class CVersionKeeperManager { bool fired(); private: - bool isVersionOlderThanRunning(const std::string& ver); + bool isMajorVersionOlderThanRunning(const std::string& ver); bool m_fired = false; }; diff --git a/src/managers/WelcomeManager.cpp b/src/managers/WelcomeManager.cpp new file mode 100644 index 000000000..6faf58c38 --- /dev/null +++ b/src/managers/WelcomeManager.cpp @@ -0,0 +1,37 @@ +#include "WelcomeManager.hpp" +#include "../Compositor.hpp" +#include "../debug/log/Logger.hpp" +#include "../config/ConfigValue.hpp" +#include "../helpers/fs/FsUtils.hpp" + +#include + +using namespace Hyprutils::OS; + +CWelcomeManager::CWelcomeManager() { + static auto PAUTOGEN = CConfigValue("autogenerated"); + + if (!*PAUTOGEN) { + Log::logger->log(Log::DEBUG, "[welcome] skipping, not autogen"); + 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; + } + + m_fired = true; + + CProcess welcome("hyprland-welcome", {}); + welcome.runAsync(); +} + +bool CWelcomeManager::fired() { + return m_fired; +} diff --git a/src/managers/WelcomeManager.hpp b/src/managers/WelcomeManager.hpp new file mode 100644 index 000000000..1f0535eb0 --- /dev/null +++ b/src/managers/WelcomeManager.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "../helpers/memory/Memory.hpp" + +class CWelcomeManager { + public: + CWelcomeManager(); + + // whether the welcome screen was shown this boot. + bool fired(); + + private: + bool m_fired = false; +}; + +inline UP g_pWelcomeManager; \ No newline at end of file diff --git a/src/managers/XCursorManager.cpp b/src/managers/XCursorManager.cpp index 5cde1dac5..90cd2a322 100644 --- a/src/managers/XCursorManager.cpp +++ b/src/managers/XCursorManager.cpp @@ -13,10 +13,11 @@ extern "C" { #include "config/ConfigValue.hpp" #include "helpers/CursorShapes.hpp" #include "../managers/CursorManager.hpp" -#include "debug/Log.hpp" +#include "debug/log/Logger.hpp" #include "XCursorManager.hpp" #include #include +#include // clang-format off static std::vector HYPR_XCURSOR_PIXELS = { @@ -121,7 +122,7 @@ void CXCursorManager::loadTheme(std::string const& name, int size, float scale) auto paths = themePaths(m_themeName); if (paths.empty()) { - Debug::log(ERR, "XCursor librarypath is empty loading standard XCursors"); + Log::logger->log(Log::ERR, "XCursor librarypath is empty loading standard XCursors"); m_cursors = loadStandardCursors(m_themeName, m_lastLoadSize); } else { for (auto const& p : paths) { @@ -129,12 +130,12 @@ void CXCursorManager::loadTheme(std::string const& name, int size, float scale) auto dirCursors = loadAllFromDir(p, m_lastLoadSize); std::ranges::copy_if(dirCursors, std::back_inserter(m_cursors), [this](auto const& p) { return std::ranges::none_of(m_cursors, [&p](auto const& dp) { return dp->shape == p->shape; }); }); - } catch (std::exception& e) { Debug::log(ERR, "XCursor path {} can't be loaded: threw error {}", p, e.what()); } + } catch (std::exception& e) { Log::logger->log(Log::ERR, "XCursor path {} can't be loaded: threw error {}", p, e.what()); } } } if (m_cursors.empty()) { - Debug::log(ERR, "XCursor failed finding any shapes in theme \"{}\".", m_themeName); + Log::logger->log(Log::ERR, "XCursor failed finding any shapes in theme \"{}\".", m_themeName); m_defaultCursor = m_hyprCursor; return; } @@ -147,12 +148,12 @@ void CXCursorManager::loadTheme(std::string const& name, int size, float scale) auto it = std::ranges::find_if(m_cursors, [&legacyName](auto const& c) { return c->shape == legacyName; }); if (it == m_cursors.end()) { - Debug::log(LOG, "XCursor failed to find a legacy shape with name {}, skipping", legacyName); + Log::logger->log(Log::DEBUG, "XCursor failed to find a legacy shape with name {}, skipping", legacyName); continue; } if (std::ranges::any_of(m_cursors, [&shape](auto const& dp) { return dp->shape == shape; })) { - Debug::log(LOG, "XCursor already has a shape {} loaded, skipping", shape); + Log::logger->log(Log::DEBUG, "XCursor already has a shape {} loaded, skipping", shape); continue; } @@ -179,7 +180,7 @@ SP CXCursorManager::getShape(std::string const& shape, int size, floa return c; } - Debug::log(WARN, "XCursor couldn't find shape {} , using default cursor instead", shape); + Log::logger->log(Log::WARN, "XCursor couldn't find shape {} , using default cursor instead", shape); return m_defaultCursor; } @@ -221,7 +222,7 @@ std::set CXCursorManager::themePaths(std::string const& theme) { std::string line; std::vector themes; - Debug::log(LOG, "XCursor parsing index.theme {}", indexTheme); + Log::logger->log(Log::DEBUG, "XCursor parsing index.theme {}", indexTheme); while (std::getline(infile, line)) { if (line.empty()) @@ -290,12 +291,12 @@ std::set CXCursorManager::themePaths(std::string const& theme) { std::stringstream ss(path); std::string line; - Debug::log(LOG, "XCursor scanning theme {}", t); + Log::logger->log(Log::DEBUG, "XCursor scanning theme {}", t); while (std::getline(ss, line, ':')) { auto p = expandTilde(line + "/" + t + "/cursors"); if (std::filesystem::exists(p) && std::filesystem::is_directory(p)) { - Debug::log(LOG, "XCursor using theme path {}", p); + Log::logger->log(Log::DEBUG, "XCursor using theme path {}", p); paths.insert(p); } @@ -303,7 +304,7 @@ std::set CXCursorManager::themePaths(std::string const& theme) { if (std::filesystem::exists(inherit) && std::filesystem::is_regular_file(inherit)) { auto inheritThemes = getInheritThemes(inherit); for (auto const& i : inheritThemes) { - Debug::log(LOG, "XCursor theme {} inherits {}", t, i); + Log::logger->log(Log::DEBUG, "XCursor theme {} inherits {}", t, i); inherits.insert(i); } } @@ -496,11 +497,11 @@ std::vector> CXCursorManager::loadStandardCursors(std::string cons auto xImages = XcursorShapeLoadImages(i << 1 /* wtf xcursor? */, name.c_str(), size); if (!xImages) { - Debug::log(WARN, "XCursor failed to find a shape with name {}, trying size 24.", shape); + Log::logger->log(Log::WARN, "XCursor failed to find a shape with name {}, trying size 24.", shape); xImages = XcursorShapeLoadImages(i << 1 /* wtf xcursor? */, name.c_str(), 24); if (!xImages) { - Debug::log(WARN, "XCursor failed to find a shape with name {}, skipping", shape); + Log::logger->log(Log::WARN, "XCursor failed to find a shape with name {}, skipping", shape); continue; } } @@ -528,7 +529,7 @@ std::vector> CXCursorManager::loadAllFromDir(std::string const& pa for (const auto& entry : std::filesystem::directory_iterator(path)) { std::error_code e1, e2; if ((!entry.is_regular_file(e1) && !entry.is_symlink(e2)) || e1 || e2) { - Debug::log(WARN, "XCursor failed to load shape {}: {}", entry.path().stem().string(), e1 ? e1.message() : e2.message()); + Log::logger->log(Log::WARN, "XCursor failed to load shape {}: {}", entry.path().stem().string(), e1 ? e1.message() : e2.message()); continue; } @@ -542,11 +543,11 @@ std::vector> CXCursorManager::loadAllFromDir(std::string const& pa auto xImages = XcursorFileLoadImages(f.get(), size); if (!xImages) { - Debug::log(WARN, "XCursor failed to load image {}, trying size 24.", full); + Log::logger->log(Log::WARN, "XCursor failed to load image {}, trying size 24.", full); xImages = XcursorFileLoadImages(f.get(), 24); if (!xImages) { - Debug::log(WARN, "XCursor failed to load image {}, skipping", full); + Log::logger->log(Log::WARN, "XCursor failed to load image {}, skipping", full); continue; } } @@ -578,7 +579,7 @@ void CXCursorManager::syncGsettings() { auto* gSettingsSchemaSource = g_settings_schema_source_get_default(); if (!gSettingsSchemaSource) { - Debug::log(WARN, "GSettings default schema source does not exist, can't sync GSettings"); + Log::logger->log(Log::WARN, "GSettings default schema source does not exist, can't sync GSettings"); return false; } @@ -596,14 +597,14 @@ void CXCursorManager::syncGsettings() { using SettingValue = std::variant; auto setValue = [&checkParamExists](std::string const& paramName, const SettingValue& paramValue, std::string const& category) { if (!checkParamExists(paramName, category)) { - Debug::log(WARN, "GSettings parameter doesn't exist {} in {}", paramName, category); + Log::logger->log(Log::WARN, "GSettings parameter doesn't exist {} in {}", paramName, category); return; } auto* gsettings = g_settings_new(category.c_str()); if (!gsettings) { - Debug::log(WARN, "GSettings failed to allocate new settings with category {}", category); + Log::logger->log(Log::WARN, "GSettings failed to allocate new settings with category {}", category); return; } diff --git a/src/managers/XWaylandManager.cpp b/src/managers/XWaylandManager.cpp index 38afe4acc..95fa085b5 100644 --- a/src/managers/XWaylandManager.cpp +++ b/src/managers/XWaylandManager.cpp @@ -1,7 +1,6 @@ #include "XWaylandManager.hpp" #include "../Compositor.hpp" #include "../desktop/state/FocusState.hpp" -#include "../events/Events.hpp" #include "../config/ConfigValue.hpp" #include "../helpers/Monitor.hpp" #include "../protocols/XDGShell.hpp" @@ -22,22 +21,22 @@ CHyprXWaylandManager::~CHyprXWaylandManager() { } SP CHyprXWaylandManager::getWindowSurface(PHLWINDOW pWindow) { - return pWindow ? pWindow->m_wlSurface->resource() : nullptr; + return pWindow ? pWindow->wlSurface()->resource() : nullptr; } void CHyprXWaylandManager::activateSurface(SP pSurface, bool activate) { if (!pSurface) return; - auto HLSurface = CWLSurface::fromResource(pSurface); + auto HLSurface = Desktop::View::CWLSurface::fromResource(pSurface); if (!HLSurface) { - Debug::log(TRACE, "CHyprXWaylandManager::activateSurface on non-desktop surface, ignoring"); + Log::logger->log(Log::TRACE, "CHyprXWaylandManager::activateSurface on non-desktop surface, ignoring"); return; } - const auto PWINDOW = HLSurface->getWindow(); + const auto PWINDOW = Desktop::View::CWindow::fromView(HLSurface->view()); if (!PWINDOW) { - Debug::log(TRACE, "CHyprXWaylandManager::activateSurface on non-window surface, ignoring"); + Log::logger->log(Log::TRACE, "CHyprXWaylandManager::activateSurface on non-window surface, ignoring"); return; } @@ -89,6 +88,14 @@ CBox CHyprXWaylandManager::getGeometryForWindow(PHLWINDOW pWindow) { else if (pWindow->m_xdgSurface) 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 oldSize = box.size(); + box.w = std::clamp(box.w, MINSIZE.x, MAXSIZE.x); + box.h = std::clamp(box.h, MINSIZE.y, MAXSIZE.y); + box.translate((oldSize - box.size()) / 2.F); + return box; } @@ -122,7 +129,8 @@ bool CHyprXWaylandManager::shouldBeFloated(PHLWINDOW pWindow, bool pending) { const auto SIZEHINTS = pWindow->m_xwaylandSurface->m_sizeHints.get(); if (pWindow->m_xwaylandSurface->m_transient || pWindow->m_xwaylandSurface->m_parent || - (SIZEHINTS && (SIZEHINTS->min_width == SIZEHINTS->max_width) && (SIZEHINTS->min_height == SIZEHINTS->max_height))) + (SIZEHINTS && SIZEHINTS->min_width > 0 && SIZEHINTS->min_height > 0 && SIZEHINTS->max_width > 0 && SIZEHINTS->max_height > 0 && + (SIZEHINTS->min_width == SIZEHINTS->max_width) && (SIZEHINTS->min_height == SIZEHINTS->max_height))) return true; } else { if (!pWindow->m_xdgSurface || !pWindow->m_xdgSurface->m_toplevel) @@ -166,18 +174,24 @@ void CHyprXWaylandManager::setWindowFullscreen(PHLWINDOW pWindow, bool fullscree } Vector2D CHyprXWaylandManager::waylandToXWaylandCoords(const Vector2D& coord) { + return waylandToXWaylandCoords(coord, nullptr); +} + +Vector2D CHyprXWaylandManager::waylandToXWaylandCoords(const Vector2D& coord, PHLMONITOR preferredMonitor) { static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); - PHLMONITOR pMonitor = nullptr; - double bestDistance = __FLT_MAX__; - for (const auto& m : g_pCompositor->m_monitors) { - const auto SIZ = *PXWLFORCESCALEZERO ? m->m_transformedSize : m->m_size; + PHLMONITOR pMonitor = preferredMonitor; + if (!pMonitor) { + double bestDistance = __FLT_MAX__; + for (const auto& m : g_pCompositor->m_monitors) { + const auto SIZ = *PXWLFORCESCALEZERO ? m->m_transformedSize : m->m_size; - double distance = vecToRectDistanceSquared(coord, {m->m_position.x, m->m_position.y}, {m->m_position.x + SIZ.x - 1, m->m_position.y + SIZ.y - 1}); + double distance = vecToRectDistanceSquared(coord, {m->m_position.x, m->m_position.y}, {m->m_position.x + SIZ.x - 1, m->m_position.y + SIZ.y - 1}); - if (distance < bestDistance) { - bestDistance = distance; - pMonitor = m; + if (distance < bestDistance) { + bestDistance = distance; + pMonitor = m; + } } } @@ -196,20 +210,26 @@ Vector2D CHyprXWaylandManager::waylandToXWaylandCoords(const Vector2D& coord) { } Vector2D CHyprXWaylandManager::xwaylandToWaylandCoords(const Vector2D& coord) { + return xwaylandToWaylandCoords(coord, nullptr); +} + +Vector2D CHyprXWaylandManager::xwaylandToWaylandCoords(const Vector2D& coord, PHLMONITOR preferredMonitor) { static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); - PHLMONITOR pMonitor = nullptr; - double bestDistance = __FLT_MAX__; - for (const auto& m : g_pCompositor->m_monitors) { - const auto SIZ = *PXWLFORCESCALEZERO ? m->m_transformedSize : m->m_size; + PHLMONITOR pMonitor = preferredMonitor; + if (!pMonitor) { + double bestDistance = __FLT_MAX__; + for (const auto& m : g_pCompositor->m_monitors) { + const auto SIZ = *PXWLFORCESCALEZERO ? m->m_transformedSize : m->m_size; - double distance = - vecToRectDistanceSquared(coord, {m->m_xwaylandPosition.x, m->m_xwaylandPosition.y}, {m->m_xwaylandPosition.x + SIZ.x - 1, m->m_xwaylandPosition.y + SIZ.y - 1}); + double distance = + vecToRectDistanceSquared(coord, {m->m_xwaylandPosition.x, m->m_xwaylandPosition.y}, {m->m_xwaylandPosition.x + SIZ.x - 1, m->m_xwaylandPosition.y + SIZ.y - 1}); - if (distance < bestDistance) { - bestDistance = distance; - pMonitor = m; + if (distance < bestDistance) { + bestDistance = distance; + pMonitor = m; + } } } diff --git a/src/managers/XWaylandManager.hpp b/src/managers/XWaylandManager.hpp index 59eee4c58..1d45bb300 100644 --- a/src/managers/XWaylandManager.hpp +++ b/src/managers/XWaylandManager.hpp @@ -1,10 +1,9 @@ #pragma once #include "../defines.hpp" +#include "../desktop/DesktopTypes.hpp" #include -class CWindow; // because clangd -using PHLWINDOW = SP; class CWLSurfaceResource; class CHyprXWaylandManager { @@ -21,7 +20,9 @@ class CHyprXWaylandManager { bool shouldBeFloated(PHLWINDOW, bool pending = false); void checkBorders(PHLWINDOW); Vector2D xwaylandToWaylandCoords(const Vector2D&); + Vector2D xwaylandToWaylandCoords(const Vector2D&, PHLMONITOR); Vector2D waylandToXWaylandCoords(const Vector2D&); + Vector2D waylandToXWaylandCoords(const Vector2D&, PHLMONITOR); }; -inline UP g_pXWaylandManager; \ No newline at end of file +inline UP g_pXWaylandManager; diff --git a/src/managers/animation/AnimationManager.cpp b/src/managers/animation/AnimationManager.cpp index c304fc56a..85d6197e8 100644 --- a/src/managers/animation/AnimationManager.cpp +++ b/src/managers/animation/AnimationManager.cpp @@ -1,16 +1,16 @@ #include "AnimationManager.hpp" #include "../../Compositor.hpp" -#include "../HookSystemManager.hpp" #include "../../config/ConfigManager.hpp" #include "../../desktop/DesktopTypes.hpp" #include "../../helpers/AnimatedVariable.hpp" #include "../../macros.hpp" #include "../../config/ConfigValue.hpp" -#include "../../desktop/Window.hpp" -#include "../../desktop/LayerSurface.hpp" +#include "../../desktop/view/Window.hpp" +#include "../../desktop/view/LayerSurface.hpp" #include "../eventLoop/EventLoopManager.hpp" #include "../../helpers/varlist/VarList.hpp" #include "../../render/Renderer.hpp" +#include "../../event/EventBus.hpp" #include #include @@ -66,74 +66,77 @@ static void updateColorVariable(CAnimatedVariable& av, const float P av.value() = {lerped, lerp(av.begun().a, av.goal().a, POINTY)}; } +static SAnimationContext& getContext(Hyprutils::Animation::CBaseAnimatedVariable* pAV) { + switch (pAV->m_Type) { + case AVARTYPE_FLOAT: return dc*>(pAV)->m_Context; + case AVARTYPE_VECTOR: return dc*>(pAV)->m_Context; + case AVARTYPE_COLOR: return dc*>(pAV)->m_Context; + default: UNREACHABLE(); + } +} + +static void damageWindowForPolicies(PHLWINDOW pWindow, bool entire, bool border, bool shadow) { + if (entire) + g_pHyprRenderer->damageWindow(pWindow); // damageWindow already damages all decorations + else { + if (border) { + const auto PDECO = pWindow->getDecorationByType(DECORATION_BORDER); + PDECO->damageEntire(); + } + if (shadow) { + const auto PDECO = pWindow->getDecorationByType(DECORATION_SHADOW); + PDECO->damageEntire(); + } + } +} + +static void preDamageWorkspace(PHLWORKSPACE pWorkspace, PHLMONITOR pMonitor) { + // don't damage the whole monitor on workspace change, unless it's a special workspace, because dim/blur etc + if (pWorkspace->m_isSpecialWorkspace) + g_pHyprRenderer->damageMonitor(pMonitor); + + // TODO: just make this into a damn callback already vax... + for (auto const& w : g_pCompositor->m_windows) { + if (!w->m_isMapped || w->isHidden() || w->m_workspace != pWorkspace) + continue; + + if (w->m_isFloating && !w->m_pinned) { + // still doing the full damage hack for floating because sometimes when the window + // goes through multiple monitors the last rendered frame is missing damage somehow?? + const CBox windowBoxNoOffset = w->getFullWindowBoundingBox(); + const CBox monitorBox = {pMonitor->m_position, pMonitor->m_size}; + if (windowBoxNoOffset.intersection(monitorBox) != windowBoxNoOffset) // on edges between multiple monitors + g_pHyprRenderer->damageWindow(w, true); + } + + if (pWorkspace->m_isSpecialWorkspace) + g_pHyprRenderer->damageWindow(w, true); // hack for special too because it can cross multiple monitors + } + + // damage any workspace window that is on any monitor + for (auto const& w : g_pCompositor->m_windows) { + if (!validMapped(w) || w->m_workspace != pWorkspace || w->m_pinned) + continue; + + g_pHyprRenderer->damageWindow(w); + } +} + template static void handleUpdate(CAnimatedVariable& av, bool warp) { - PHLWINDOW PWINDOW = av.m_Context.pWindow.lock(); - PHLWORKSPACE PWORKSPACE = av.m_Context.pWorkspace.lock(); - PHLLS PLAYER = av.m_Context.pLayer.lock(); - PHLMONITOR PMONITOR = nullptr; - bool animationsDisabled = warp; + bool animationsDisabled = warp; - if (PWINDOW) { - if (av.m_Context.eDamagePolicy == AVARDAMAGE_ENTIRE) - g_pHyprRenderer->damageWindow(PWINDOW); - else if (av.m_Context.eDamagePolicy == AVARDAMAGE_BORDER) { - const auto PDECO = PWINDOW->getDecorationByType(DECORATION_BORDER); - PDECO->damageEntire(); - } else if (av.m_Context.eDamagePolicy == AVARDAMAGE_SHADOW) { - const auto PDECO = PWINDOW->getDecorationByType(DECORATION_SHADOW); - PDECO->damageEntire(); - } - - PMONITOR = PWINDOW->m_monitor.lock(); - if (!PMONITOR) + if (auto w = av.m_Context.pWindow.lock()) { + if (!w->m_monitor.lock()) return; - - animationsDisabled = PWINDOW->m_ruleApplicator->noAnim().valueOr(animationsDisabled); - } else if (PWORKSPACE) { - PMONITOR = PWORKSPACE->m_monitor.lock(); - if (!PMONITOR) + animationsDisabled = w->m_ruleApplicator->noAnim().valueOr(animationsDisabled); + } else if (auto ws = av.m_Context.pWorkspace.lock()) { + if (!ws->m_monitor.lock()) return; - - // don't damage the whole monitor on workspace change, unless it's a special workspace, because dim/blur etc - if (PWORKSPACE->m_isSpecialWorkspace) - g_pHyprRenderer->damageMonitor(PMONITOR); - - // TODO: just make this into a damn callback already vax... - for (auto const& w : g_pCompositor->m_windows) { - if (!w->m_isMapped || w->isHidden() || w->m_workspace != PWORKSPACE) - continue; - - if (w->m_isFloating && !w->m_pinned) { - // still doing the full damage hack for floating because sometimes when the window - // goes through multiple monitors the last rendered frame is missing damage somehow?? - const CBox windowBoxNoOffset = w->getFullWindowBoundingBox(); - const CBox monitorBox = {PMONITOR->m_position, PMONITOR->m_size}; - if (windowBoxNoOffset.intersection(monitorBox) != windowBoxNoOffset) // on edges between multiple monitors - g_pHyprRenderer->damageWindow(w, true); - } - - if (PWORKSPACE->m_isSpecialWorkspace) - g_pHyprRenderer->damageWindow(w, true); // hack for special too because it can cross multiple monitors - } - - // damage any workspace window that is on any monitor - for (auto const& w : g_pCompositor->m_windows) { - if (!validMapped(w) || w->m_workspace != PWORKSPACE || w->m_pinned) - continue; - - g_pHyprRenderer->damageWindow(w); - } - } else if (PLAYER) { - // "some fucking layers miss 1 pixel???" -- vaxry - CBox expandBox = CBox{PLAYER->m_realPosition->value(), PLAYER->m_realSize->value()}; - expandBox.expand(5); - g_pHyprRenderer->damageBox(expandBox); - - PMONITOR = g_pCompositor->getMonitorFromVector(PLAYER->m_realPosition->goal() + PLAYER->m_realSize->goal() / 2.F); - if (!PMONITOR) + } else if (auto ls = av.m_Context.pLayer.lock()) { + if (!g_pCompositor->getMonitorFromVector(ls->m_realPosition->goal() + ls->m_realSize->goal() / 2.F)) return; - animationsDisabled = animationsDisabled || PLAYER->m_ruleApplicator->noanim().valueOrDefault(); + animationsDisabled = animationsDisabled || ls->m_ruleApplicator->noanim().valueOrDefault(); } const auto SPENT = av.getPercent(); @@ -147,59 +150,6 @@ static void handleUpdate(CAnimatedVariable& av, bool warp) { updateVariable(av, POINTY, WARP); av.onUpdate(); - - switch (av.m_Context.eDamagePolicy) { - case AVARDAMAGE_ENTIRE: { - if (PWINDOW) { - PWINDOW->updateWindowDecos(); - g_pHyprRenderer->damageWindow(PWINDOW); - } else if (PWORKSPACE) { - for (auto const& w : g_pCompositor->m_windows) { - if (!validMapped(w) || w->m_workspace != PWORKSPACE) - continue; - - w->updateWindowDecos(); - - // damage any workspace window that is on any monitor - if (!w->m_pinned) - g_pHyprRenderer->damageWindow(w); - } - } else if (PLAYER) { - if (PLAYER->m_layer <= 1) - g_pHyprOpenGL->markBlurDirtyForMonitor(PMONITOR); - - // some fucking layers miss 1 pixel??? - CBox expandBox = CBox{PLAYER->m_realPosition->value(), PLAYER->m_realSize->value()}; - expandBox.expand(5); - g_pHyprRenderer->damageBox(expandBox); - } - break; - } - case AVARDAMAGE_BORDER: { - RASSERT(PWINDOW, "Tried to AVARDAMAGE_BORDER a non-window AVAR!"); - - const auto PDECO = PWINDOW->getDecorationByType(DECORATION_BORDER); - PDECO->damageEntire(); - - break; - } - case AVARDAMAGE_SHADOW: { - RASSERT(PWINDOW, "Tried to AVARDAMAGE_SHADOW a non-window AVAR!"); - - const auto PDECO = PWINDOW->getDecorationByType(DECORATION_SHADOW); - - PDECO->damageEntire(); - - break; - } - default: { - break; - } - } - - // manually schedule a frame - if (PMONITOR) - g_pCompositor->scheduleFrameForMonitor(PMONITOR, Aquamarine::IOutput::AQ_SCHEDULE_ANIMATION); } void CHyprAnimationManager::tick() { @@ -209,13 +159,110 @@ void CHyprAnimationManager::tick() { static auto PANIMENABLED = CConfigValue("animations:enabled"); - for (size_t i = 0; i < m_vActiveAnimatedVariables.size(); i++) { - const auto PAV = m_vActiveAnimatedVariables[i].lock(); + if (m_vActiveAnimatedVariables.empty()) { + tickDone(); + return; + } + + const auto CPY = m_vActiveAnimatedVariables; + + // batch damage per owner to avoid redundant damage calls, otherwise + // we could be damaging many many times too much + struct SDamageOwner { + PHLWINDOW window; + PHLWORKSPACE workspace; + PHLLS layer; + PHLMONITOR monitor; + bool entire = false; + bool border = false; + bool shadow = false; + }; + + std::vector owners; + owners.reserve(4); + + // Collect per-owner damage policies + for (const auto& PAV : CPY) { if (!PAV) continue; - // for disabled anims just warp - bool warp = !*PANIMENABLED || !PAV->enabled(); + const auto& ctx = getContext(PAV.get()); + + SDamageOwner* owner = nullptr; + + if (auto w = ctx.pWindow.lock()) { + for (auto& o : owners) { + if (o.window == w) { + owner = &o; + break; + } + } + if (!owner) { + auto monitor = w->m_monitor.lock(); + if (!monitor) + continue; + owners.emplace_back(SDamageOwner{.window = w, .monitor = monitor}); + owner = &owners.back(); + } + } else if (auto ws = ctx.pWorkspace.lock()) { + for (auto& o : owners) { + if (o.workspace == ws) { + owner = &o; + break; + } + } + if (!owner) { + auto monitor = ws->m_monitor.lock(); + if (!monitor) + continue; + owners.emplace_back(SDamageOwner{.workspace = ws, .monitor = monitor}); + owner = &owners.back(); + } + } else if (auto ls = ctx.pLayer.lock()) { + for (auto& o : owners) { + if (o.layer == ls) { + owner = &o; + break; + } + } + if (!owner) { + auto monitor = g_pCompositor->getMonitorFromVector(ls->m_realPosition->goal() + ls->m_realSize->goal() / 2.F); + if (!monitor) + continue; + owners.emplace_back(SDamageOwner{.layer = ls, .monitor = monitor}); + owner = &owners.back(); + } + } else + continue; + + switch (ctx.eDamagePolicy) { + case AVARDAMAGE_ENTIRE: owner->entire = true; break; + case AVARDAMAGE_BORDER: owner->border = true; break; + case AVARDAMAGE_SHADOW: owner->shadow = true; break; + default: break; + } + } + + // pre-damage each owner once (old state) + for (const auto& owner : owners) { + if (owner.window) + damageWindowForPolicies(owner.window, owner.entire, owner.border, owner.shadow); + else if (owner.workspace) + preDamageWorkspace(owner.workspace, owner.monitor); + else if (owner.layer) { + CBox expandBox = CBox{owner.layer->m_realPosition->value(), owner.layer->m_realSize->value()}; + expandBox.expand(5); + g_pHyprRenderer->damageBox(expandBox); + } + } + + // update all variable values + for (const auto& PAV : CPY) { + if (!PAV) + continue; + + const auto LOCK = PAV.lock(); + bool warp = !*PANIMENABLED || !PAV->enabled(); switch (PAV->m_Type) { case AVARTYPE_FLOAT: { @@ -237,6 +284,34 @@ void CHyprAnimationManager::tick() { } } + // post-damage each owner once (new state) + schedule frames + for (const auto& owner : owners) { + if (owner.window) + damageWindowForPolicies(owner.window, owner.entire, owner.border, owner.shadow); + else if (owner.workspace) { + if (owner.entire) { + for (auto const& w : g_pCompositor->m_windows) { + if (!validMapped(w) || w->m_workspace != owner.workspace || w->m_pinned) + continue; + + g_pHyprRenderer->damageWindow(w); + } + } + } else if (owner.layer) { + if (owner.entire) { + if (owner.layer->m_layer <= 1) + owner.monitor->m_blurFBDirty = true; + + CBox expandBox = CBox{owner.layer->m_realPosition->value(), owner.layer->m_realSize->value()}; + expandBox.expand(5); + g_pHyprRenderer->damageBox(expandBox); + } + } + + if (!owner.monitor->inFullscreenMode()) + g_pCompositor->scheduleFrameForMonitor(owner.monitor, Aquamarine::IOutput::AQ_SCHEDULE_ANIMATION); + } + tickDone(); } @@ -246,8 +321,8 @@ void CHyprAnimationManager::frameTick() { if (!shouldTickForNext()) return; - if (!g_pCompositor->m_sessionActive || !g_pHookSystem || g_pCompositor->m_unsafeState || - !std::ranges::any_of(g_pCompositor->m_monitors, [](const auto& mon) { return mon->m_enabled && mon->m_output; })) + if UNLIKELY (!g_pCompositor->m_sessionActive || g_pCompositor->m_unsafeState || + !std::ranges::any_of(g_pCompositor->m_monitors, [](const auto& mon) { return mon->m_enabled && mon->m_output; })) return; if (!m_lastTickValid || m_lastTickTimer.getMillis() >= 1.0f) { @@ -255,7 +330,7 @@ void CHyprAnimationManager::frameTick() { m_lastTickValid = true; tick(); - EMIT_HOOK_EVENT("tick", nullptr); + Event::bus()->m_events.tick.emit(); } if (shouldTickForNext()) @@ -280,6 +355,11 @@ void CHyprAnimationManager::onTicked() { m_tickScheduled = false; } +void CHyprAnimationManager::resetTickState() { + m_lastTickValid = false; + m_tickScheduled = false; +} + std::string CHyprAnimationManager::styleValidInConfigVar(const std::string& config, const std::string& style) { if (config.starts_with("window")) { if (style.starts_with("slide") || style == "gnome" || style == "gnomed") diff --git a/src/managers/animation/AnimationManager.hpp b/src/managers/animation/AnimationManager.hpp index 2f411879b..35bb1e8a6 100644 --- a/src/managers/animation/AnimationManager.hpp +++ b/src/managers/animation/AnimationManager.hpp @@ -18,17 +18,18 @@ 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) { constexpr const eAnimatedVarType EAVTYPE = typeToeAnimatedVarType; - const auto PAV = makeShared>(); + pav = makeUnique>(); - PAV->create(EAVTYPE, sc(this), PAV, v); - PAV->setConfig(pConfig); - PAV->m_Context.eDamagePolicy = policy; - - pav = std::move(PAV); + pav->create2(EAVTYPE, sc(this), pav, v); + pav->setConfig(pConfig); + pav->m_Context.eDamagePolicy = policy; } template diff --git a/src/managers/animation/DesktopAnimationManager.cpp b/src/managers/animation/DesktopAnimationManager.cpp index 8a3405544..309614090 100644 --- a/src/managers/animation/DesktopAnimationManager.cpp +++ b/src/managers/animation/DesktopAnimationManager.cpp @@ -1,13 +1,24 @@ #include "DesktopAnimationManager.hpp" -#include "../../desktop/LayerSurface.hpp" -#include "../../desktop/Window.hpp" +#include +#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 "../../config/shared/animation/AnimationTree.hpp" + +#include "../../helpers/Monitor.hpp" #include "../../Compositor.hpp" +#include "desktop/DesktopTypes.hpp" #include "wlr-layer-shell-unstable-v1.hpp" +#include + +using namespace Hyprutils::String; + void CDesktopAnimationManager::startAnimation(PHLWINDOW pWindow, eAnimationType type, bool force) { const bool CLOSE = type == ANIMATION_TYPE_OUT; @@ -19,13 +30,13 @@ void CDesktopAnimationManager::startAnimation(PHLWINDOW pWindow, eAnimationType } if (!CLOSE) { - pWindow->m_realPosition->setConfig(g_pConfigManager->getAnimationPropertyConfig("windowsIn")); - pWindow->m_realSize->setConfig(g_pConfigManager->getAnimationPropertyConfig("windowsIn")); - pWindow->m_alpha->setConfig(g_pConfigManager->getAnimationPropertyConfig("fadeIn")); + pWindow->m_realPosition->setConfig(Config::animationTree()->getAnimationPropertyConfig("windowsIn")); + pWindow->m_realSize->setConfig(Config::animationTree()->getAnimationPropertyConfig("windowsIn")); + pWindow->m_alpha->setConfig(Config::animationTree()->getAnimationPropertyConfig("fadeIn")); } else { - pWindow->m_realPosition->setConfig(g_pConfigManager->getAnimationPropertyConfig("windowsOut")); - pWindow->m_realSize->setConfig(g_pConfigManager->getAnimationPropertyConfig("windowsOut")); - pWindow->m_alpha->setConfig(g_pConfigManager->getAnimationPropertyConfig("fadeOut")); + pWindow->m_realPosition->setConfig(Config::animationTree()->getAnimationPropertyConfig("windowsOut")); + pWindow->m_realSize->setConfig(Config::animationTree()->getAnimationPropertyConfig("windowsOut")); + pWindow->m_alpha->setConfig(Config::animationTree()->getAnimationPropertyConfig("fadeOut")); } std::string ANIMSTYLE = pWindow->m_realPosition->getStyle(); @@ -97,13 +108,13 @@ void CDesktopAnimationManager::startAnimation(PHLLS ls, eAnimationType type, boo *ls->m_alpha = 0.F; if (IN) { - ls->m_realPosition->setConfig(g_pConfigManager->getAnimationPropertyConfig("layersIn")); - ls->m_realSize->setConfig(g_pConfigManager->getAnimationPropertyConfig("layersIn")); - ls->m_alpha->setConfig(g_pConfigManager->getAnimationPropertyConfig("fadeLayersIn")); + ls->m_realPosition->setConfig(Config::animationTree()->getAnimationPropertyConfig("layersIn")); + ls->m_realSize->setConfig(Config::animationTree()->getAnimationPropertyConfig("layersIn")); + ls->m_alpha->setConfig(Config::animationTree()->getAnimationPropertyConfig("fadeLayersIn")); } else { - ls->m_realPosition->setConfig(g_pConfigManager->getAnimationPropertyConfig("layersOut")); - ls->m_realSize->setConfig(g_pConfigManager->getAnimationPropertyConfig("layersOut")); - ls->m_alpha->setConfig(g_pConfigManager->getAnimationPropertyConfig("fadeLayersOut")); + ls->m_realPosition->setConfig(Config::animationTree()->getAnimationPropertyConfig("layersOut")); + ls->m_realSize->setConfig(Config::animationTree()->getAnimationPropertyConfig("layersOut")); + ls->m_alpha->setConfig(Config::animationTree()->getAnimationPropertyConfig("fadeLayersOut")); } const auto ANIMSTYLE = ls->m_ruleApplicator->animationStyle().valueOr(ls->m_realPosition->getStyle()); @@ -228,19 +239,20 @@ void CDesktopAnimationManager::startAnimation(PHLLS ls, eAnimationType type, boo } } -void CDesktopAnimationManager::startAnimation(PHLWORKSPACE ws, eAnimationType type, bool left, bool instant) { +void CDesktopAnimationManager::startAnimation(PHLWORKSPACE ws, eAnimationType type, bool left, bool instant, std::optional style) { const bool IN = type == ANIMATION_TYPE_IN; if (!instant) { const std::string ANIMNAME = std::format("{}{}", ws->m_isSpecialWorkspace ? "specialWorkspace" : "workspaces", IN ? "In" : "Out"); - ws->m_alpha->setConfig(g_pConfigManager->getAnimationPropertyConfig(ANIMNAME)); - ws->m_renderOffset->setConfig(g_pConfigManager->getAnimationPropertyConfig(ANIMNAME)); + ws->m_alpha->setConfig(Config::animationTree()->getAnimationPropertyConfig(ANIMNAME)); + ws->m_renderOffset->setConfig(Config::animationTree()->getAnimationPropertyConfig(ANIMNAME)); } static auto PWORKSPACEGAP = CConfigValue("general:gaps_workspaces"); const auto PMONITOR = ws->m_monitor.lock(); - const auto ANIMSTYLE = ws->m_alpha->getStyle(); - float movePerc = 100.f; + const auto ANIMSTYLE = style.value_or(ws->m_alpha->getStyle()); + + float movePerc = 100.f; // inverted for some reason. TODO: fix the cause bool vert = ANIMSTYLE.starts_with("slidevert") || ANIMSTYLE.starts_with("slidefadevert"); @@ -279,7 +291,7 @@ void CDesktopAnimationManager::startAnimation(PHLWORKSPACE ws, eAnimationType ty if (percstr.ends_with('%')) { try { movePerc = std::stoi(percstr.substr(0, percstr.length() - 1)); - } catch (std::exception& e) { Debug::log(ERR, "Error in startAnim: invalid percentage"); } + } catch (std::exception& e) { Log::logger->log(Log::ERR, "Error in startAnim: invalid percentage"); } } if (ANIMSTYLE.starts_with("slidefade")) { @@ -406,31 +418,26 @@ void CDesktopAnimationManager::animationSlide(PHLWINDOW pWindow, std::string for } const auto MIDPOINT = GOALPOS + GOALSIZE / 2.f; + const auto MONBOX = PMONITOR->logicalBox(); - // check sides it touches - const bool DISPLAYLEFT = STICKS(pWindow->m_position.x, PMONITOR->m_position.x + PMONITOR->m_reservedTopLeft.x); - const bool DISPLAYRIGHT = STICKS(pWindow->m_position.x + pWindow->m_size.x, PMONITOR->m_position.x + PMONITOR->m_size.x - PMONITOR->m_reservedBottomRight.x); - const bool DISPLAYTOP = STICKS(pWindow->m_position.y, PMONITOR->m_position.y + PMONITOR->m_reservedTopLeft.y); - const bool DISPLAYBOTTOM = STICKS(pWindow->m_position.y + pWindow->m_size.y, PMONITOR->m_position.y + PMONITOR->m_size.y - PMONITOR->m_reservedBottomRight.y); + // find the closest edge to midpoint + // CSS style, top right bottom left + std::array distances = { + MIDPOINT.y - MONBOX.y, // + MONBOX.x + MONBOX.w - MIDPOINT.x, // + MONBOX.y + MONBOX.h - MIDPOINT.y, // + MIDPOINT.x - MONBOX.x, // + }; - if (DISPLAYBOTTOM && DISPLAYTOP) { - if (DISPLAYLEFT && DISPLAYRIGHT) { - posOffset = GOALPOS + Vector2D(0.0, GOALSIZE.y); - } else if (DISPLAYLEFT) { - posOffset = GOALPOS - Vector2D(GOALSIZE.x, 0.0); - } else { - posOffset = GOALPOS + Vector2D(GOALSIZE.x, 0.0); - } - } else if (DISPLAYTOP) { - posOffset = GOALPOS - Vector2D(0.0, GOALSIZE.y); - } else if (DISPLAYBOTTOM) { - posOffset = GOALPOS + Vector2D(0.0, GOALSIZE.y); - } else { - if (MIDPOINT.y > PMONITOR->m_position.y + PMONITOR->m_size.y / 2.f) - posOffset = Vector2D(GOALPOS.x, PMONITOR->m_position.y + PMONITOR->m_size.y); - else - posOffset = Vector2D(GOALPOS.x, PMONITOR->m_position.y - GOALSIZE.y); - } + const auto MIN_DIST = std::min({distances[0], distances[1], distances[2], distances[3]}); + if (MIN_DIST == distances[2]) + posOffset = Vector2D(GOALPOS.x, PMONITOR->m_position.y + PMONITOR->m_size.y); + else if (MIN_DIST == distances[3]) + posOffset = GOALPOS - Vector2D(GOALSIZE.x, 0.0); + else if (MIN_DIST == distances[1]) + posOffset = GOALPOS + Vector2D(GOALSIZE.x, 0.0); + else + posOffset = Vector2D(GOALPOS.x, PMONITOR->m_position.y - GOALSIZE.y); if (!close) pWindow->m_realPosition->setValue(posOffset); @@ -471,7 +478,7 @@ void CDesktopAnimationManager::setFullscreenFadeAnimation(PHLWORKSPACE ws, eAnim *w->m_alpha = 1.F; else if (!w->isFullscreen()) { const bool CREATED_OVER_FS = w->m_createdOverFullscreen; - const bool IS_IN_GROUP_OF_FS = FSWINDOW && FSWINDOW->hasInGroup(w); + const bool IS_IN_GROUP_OF_FS = FSWINDOW && FSWINDOW->m_group && FSWINDOW->m_group->has(w); *w->m_alpha = !CREATED_OVER_FS && !IS_IN_GROUP_OF_FS ? 0.f : 1.f; } } @@ -481,12 +488,19 @@ void CDesktopAnimationManager::setFullscreenFadeAnimation(PHLWORKSPACE ws, eAnim if (ws->m_id == PMONITOR->activeWorkspaceID() || ws->m_id == PMONITOR->activeSpecialWorkspaceID()) { for (auto const& ls : PMONITOR->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { - if (!ls->m_fadingOut) + if (!ls->m_fadingOut && !ls->m_aboveFullscreen) *ls->m_alpha = FULLSCREEN && ws->m_fullscreenMode == FSMODE_FULLSCREEN ? 0.f : 1.f; } } } +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 f27f09d24..92aadb9f2 100644 --- a/src/managers/animation/DesktopAnimationManager.hpp +++ b/src/managers/animation/DesktopAnimationManager.hpp @@ -3,6 +3,7 @@ #include "../../helpers/memory/Memory.hpp" #include "../../desktop/DesktopTypes.hpp" #include +#include class CDesktopAnimationManager { public: @@ -13,9 +14,10 @@ class CDesktopAnimationManager { void startAnimation(PHLWINDOW w, eAnimationType type, bool force = false); void startAnimation(PHLLS ls, eAnimationType type, bool instant = false); - void startAnimation(PHLWORKSPACE ws, eAnimationType type, bool left = true, bool instant = false); + void startAnimation(PHLWORKSPACE ws, eAnimationType type, bool left = true, bool instant = false, std::optional style = std::nullopt); 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 1426e424b..03a8d7c92 100644 --- a/src/managers/eventLoop/EventLoopManager.cpp +++ b/src/managers/eventLoop/EventLoopManager.cpp @@ -1,9 +1,10 @@ #include "EventLoopManager.hpp" -#include "../../debug/Log.hpp" +#include "../../debug/log/Logger.hpp" #include "../../Compositor.hpp" -#include "../../config/ConfigWatcher.hpp" +#include "../../config/shared/inotify/ConfigWatcher.hpp" #include +#include #include #include @@ -26,10 +27,7 @@ CEventLoopManager::~CEventLoopManager() { wl_event_source_remove(eventSourceData.eventSource); } - for (auto const& w : m_readableWaiters) { - if (w->source != nullptr) - wl_event_source_remove(w->source); - } + m_readableWaiters.clear(); if (m_wayland.eventSource) wl_event_source_remove(m_wayland.eventSource); @@ -40,29 +38,45 @@ 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; + if (read(fd, &expirations, sizeof(expirations)) < 0) + Log::logger->log(Log::ERR, "timerWrite: read failed on fd {}: {}", fd, strerror(errno)); + } + g_pEventLoopManager->onTimerFire(); - return 1; + return 0; } static int aquamarineFDWrite(int fd, uint32_t mask, void* data) { auto POLLFD = sc(data); POLLFD->onSignal(); - return 1; + return 0; } static int configWatcherWrite(int fd, uint32_t mask, void* data) { - g_pConfigWatcher->onInotifyEvent(); + Config::watcher()->onInotifyEvent(); return 0; } 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)) { - Debug::log(ERR, "handleWaiterFD: readable waiter error"); + Log::logger->log(Log::ERR, "handleWaiterFD: readable waiter error"); + g_pEventLoopManager->onFdReadableFail(waiter); return 0; } if (mask & WL_EVENT_READABLE) - g_pEventLoopManager->onFdReadable(sc(data)); + g_pEventLoopManager->onFdReadable(waiter); return 0; } @@ -74,6 +88,11 @@ void CEventLoopManager::onFdReadable(SReadableWaiter* waiter) { if (it == m_readableWaiters.end()) return; + if (waiter->source) { // remove even_source if fn() somehow causes a reentry + wl_event_source_remove(waiter->source); + waiter->source = nullptr; + } + UP taken = std::move(*it); m_readableWaiters.erase(it); @@ -81,10 +100,20 @@ 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); - if (const auto& FD = g_pConfigWatcher->getInotifyFD(); FD.isValid()) + if (const auto& FD = Config::watcher()->getInotifyFD(); FD.isValid()) m_configWatcherInotifySource = wl_event_loop_add_fd(m_wayland.loop, FD.get(), WL_EVENT_READABLE, configWatcherWrite, nullptr); syncPollFDs(); @@ -96,7 +125,7 @@ void CEventLoopManager::enterLoop() { wl_display_run(m_wayland.display); - Debug::log(LOG, "Kicked off the event loop! :("); + Log::logger->log(Log::DEBUG, "Kicked off the event loop! :("); } void CEventLoopManager::onTimerFire() { @@ -182,12 +211,12 @@ void CEventLoopManager::doLater(const std::function& fn) { m_wayland.loop, [](void* data) { auto IDLE = sc(data); - auto cpy = IDLE->fns; + auto fns = std::move(IDLE->fns); IDLE->fns.clear(); IDLE->eventSource = nullptr; - for (auto const& c : cpy) { - if (c) - c(); + for (auto& f : fns) { + if (f) + f(); } }, &m_idle); diff --git a/src/managers/eventLoop/EventLoopManager.hpp b/src/managers/eventLoop/EventLoopManager.hpp index 7a3b43143..7999dc595 100644 --- a/src/managers/eventLoop/EventLoopManager.hpp +++ b/src/managers/eventLoop/EventLoopManager.hpp @@ -65,6 +65,7 @@ class CEventLoopManager { // takes ownership of fd void doOnReadable(Hyprutils::OS::CFileDescriptor fd, std::function&& fn); void onFdReadable(SReadableWaiter* waiter); + void onFdReadableFail(SReadableWaiter* waiter); private: // Manages the event sources after AQ pollFDs change. diff --git a/src/managers/input/IdleInhibitor.cpp b/src/managers/input/IdleInhibitor.cpp index 851e917a6..02eefc1d3 100644 --- a/src/managers/input/IdleInhibitor.cpp +++ b/src/managers/input/IdleInhibitor.cpp @@ -8,17 +8,17 @@ void CInputManager::newIdleInhibitor(std::any inhibitor) { const auto PINHIBIT = m_idleInhibitors.emplace_back(makeUnique()).get(); PINHIBIT->inhibitor = std::any_cast>(inhibitor); - Debug::log(LOG, "New idle inhibitor registered for surface {:x}", rc(PINHIBIT->inhibitor->m_surface.get())); + Log::logger->log(Log::DEBUG, "New idle inhibitor registered for surface {:x}", rc(PINHIBIT->inhibitor->m_surface.get())); PINHIBIT->inhibitor->m_listeners.destroy = PINHIBIT->inhibitor->m_resource->m_events.destroy.listen([this, PINHIBIT] { std::erase_if(m_idleInhibitors, [PINHIBIT](const auto& other) { return other.get() == PINHIBIT; }); recheckIdleInhibitorStatus(); }); - auto WLSurface = CWLSurface::fromResource(PINHIBIT->inhibitor->m_surface.lock()); + auto WLSurface = Desktop::View::CWLSurface::fromResource(PINHIBIT->inhibitor->m_surface.lock()); if (!WLSurface) { - Debug::log(LOG, "Inhibitor has no HL Surface attached to it, likely meaning it's a non-desktop element. Assuming it's visible."); + Log::logger->log(Log::DEBUG, "Inhibitor has no HL Surface attached to it, likely meaning it's a non-desktop element. Assuming it's visible."); PINHIBIT->nonDesktop = true; recheckIdleInhibitorStatus(); return; @@ -38,12 +38,12 @@ void CInputManager::recheckIdleInhibitorStatus() { return; } - auto WLSurface = CWLSurface::fromResource(ii->inhibitor->m_surface.lock()); + auto WLSurface = Desktop::View::CWLSurface::fromResource(ii->inhibitor->m_surface.lock()); - if (!WLSurface) + if (!WLSurface || !WLSurface->view()) continue; - if (WLSurface->visible()) { + if (WLSurface->view()->aliveAndVisible()) { PROTO::idle->setInhibit(true); return; } @@ -78,17 +78,17 @@ bool CInputManager::isWindowInhibiting(const PHLWINDOW& w, bool onlyHl) { continue; bool isInhibiting = false; - w->m_wlSurface->resource()->breadthfirst( + w->wlSurface()->resource()->breadthfirst( [&ii](SP surf, const Vector2D& pos, void* data) { if (ii->inhibitor->m_surface != surf) return; - auto WLSurface = CWLSurface::fromResource(surf); + auto WLSurface = Desktop::View::CWLSurface::fromResource(surf); - if (!WLSurface) + if (!WLSurface || !WLSurface->view()) return; - if (WLSurface->visible()) + if (WLSurface->view()->aliveAndVisible()) *sc(data) = true; }, &isInhibiting); diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index bc631ecd8..c4df7c53b 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -6,9 +6,8 @@ #include #include #include "../../config/ConfigValue.hpp" -#include "../../config/ConfigManager.hpp" -#include "../../desktop/Window.hpp" -#include "../../desktop/LayerSurface.hpp" +#include "../../config/legacy/ConfigManager.hpp" +#include "../../desktop/view/WLSurface.hpp" #include "../../desktop/state/FocusState.hpp" #include "../../protocols/CursorShape.hpp" #include "../../protocols/IdleInhibit.hpp" @@ -36,18 +35,24 @@ #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" #include +#include +#include + +using namespace Hyprutils::String; CInputManager::CInputManager() { m_listeners.setCursorShape = PROTO::cursorShape->m_events.setShape.listen([this](const CCursorShapeProtocol::SSetShapeEvent& event) { @@ -57,7 +62,7 @@ CInputManager::CInputManager() { if (wl_resource_get_client(event.pMgr->resource()) != g_pSeatManager->m_state.pointerFocusResource->client()) return; - Debug::log(LOG, "cursorImage request: shape {} -> {}", sc(event.shape), event.shapeName); + Log::logger->log(Log::DEBUG, "cursorImage request: shape {} -> {}", sc(event.shape), event.shapeName); m_cursorSurfaceInfo.wlSurface->unassign(); m_cursorSurfaceInfo.vHotspot = {}; @@ -95,7 +100,7 @@ CInputManager::CInputManager() { g_pHyprRenderer->setCursorFromName(shape); }); - m_cursorSurfaceInfo.wlSurface = CWLSurface::create(); + m_cursorSurfaceInfo.wlSurface = Desktop::View::CWLSurface::create(); } CInputManager::~CInputManager() { @@ -131,26 +136,23 @@ 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); m_lastCursorMovement.reset(); - m_lastInputTouch = false; + m_lastInputTouch = false; + m_lastInputTablet = false; if (e.mouse) m_lastMousePos = getMouseCoordsInternal(); + + g_pSeatManager->sendPointerFrame(); } void CInputManager::onMouseWarp(IPointer::SMotionAbsoluteEvent e) { @@ -160,7 +162,8 @@ void CInputManager::onMouseWarp(IPointer::SMotionAbsoluteEvent e) { m_lastCursorMovement.reset(); - m_lastInputTouch = false; + m_lastInputTouch = false; + m_lastInputTablet = false; } void CInputManager::simulateMouseMovement() { @@ -172,15 +175,29 @@ void CInputManager::sendMotionEventsToFocused() { if (!Desktop::focusState()->surface() || isConstrained()) return; - // todo: this sucks ass - const auto PWINDOW = g_pCompositor->getWindowFromSurface(Desktop::focusState()->surface()); - const auto PLS = g_pCompositor->getLayerSurfaceFromSurface(Desktop::focusState()->surface()); + const auto SURF = Desktop::focusState()->surface(); - const auto LOCAL = getMouseCoordsInternal() - (PWINDOW ? PWINDOW->m_realPosition->goal() : (PLS ? Vector2D{PLS->m_geometry.x, PLS->m_geometry.y} : Vector2D{})); + if (!SURF) + return; + + const auto HLSurf = Desktop::View::CWLSurface::fromResource(SURF); + + if (!HLSurf || !HLSurf->view()) + return; + + const auto VIEW = HLSurf->view(); + + if (!VIEW->aliveAndVisible()) + return; + + const auto BOX = HLSurf->getSurfaceBoxGlobal(); + + if (!BOX) + return; m_emptyFocusCursorSet = false; - g_pSeatManager->setPointerFocus(Desktop::focusState()->surface(), LOCAL); + g_pSeatManager->setPointerFocus(Desktop::focusState()->surface(), getMouseCoordsInternal().floor() - BOX->pos()); } void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, std::optional overridePos) { @@ -219,12 +236,17 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st Vector2D surfacePos = Vector2D(-1337, -1337); PHLWINDOW pFoundWindow; PHLLS pFoundLayerSurface; + const auto FOCUS_REASON = refocus ? Desktop::FOCUS_REASON_CLICK : Desktop::FOCUS_REASON_FFM; - EMIT_HOOK_EVENT_CANCELLABLE("mouseMove", MOUSECOORDSFLOORED); + Event::SCallbackInfo info; + Event::bus()->m_events.input.mouse.move.emit(MOUSECOORDSFLOORED, info); + if (info.cancelled) + return; m_lastCursorPosFloored = MOUSECOORDSFLOORED; - const auto PMONITOR = isLocked() && Desktop::focusState()->monitor() ? Desktop::focusState()->monitor() : g_pCompositor->getMonitorFromCursor(); + // use mouseCoords specifically in case touch sent overridePos, otherwise touch doesn't work on non-focused monitor + const auto PMONITOR = isLocked() && Desktop::focusState()->monitor() ? Desktop::focusState()->monitor() : g_pCompositor->getMonitorFromVector(mouseCoords); // this can happen if there are no displays hooked up to Hyprland if (PMONITOR == nullptr) @@ -233,14 +255,17 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st if (PMONITOR->m_cursorZoom->value() != 1.f) g_pHyprRenderer->damageMonitor(PMONITOR); - bool skipFrameSchedule = PMONITOR->shouldSkipScheduleFrameOnMouseEvent(); + bool skipFrameSchedule = PMONITOR->shouldSkipScheduleFrameOnMouseEvent(); - if (!PMONITOR->m_solitaryClient.lock() && g_pHyprRenderer->shouldRenderCursor() && g_pPointerManager->softwareLockedFor(PMONITOR->m_self.lock()) && !skipFrameSchedule) + const auto solitary = PMONITOR->m_solitaryClient.lock(); + const auto self = PMONITOR->m_self.lock(); + + if (!solitary && g_pHyprRenderer->shouldRenderCursor() && g_pPointerManager->softwareLockedFor(self) && !skipFrameSchedule) g_pCompositor->scheduleFrameForMonitor(PMONITOR, Aquamarine::IOutput::AQ_SCHEDULE_CURSOR_MOVE); // constraints - if (!g_pSeatManager->m_mouse.expired() && isConstrained()) { - const auto SURF = CWLSurface::fromResource(Desktop::focusState()->surface()); + if (!overridePos.has_value() && !g_pSeatManager->m_mouse.expired() && isConstrained()) { + const auto SURF = Desktop::View::CWLSurface::fromResource(Desktop::focusState()->surface()); const auto CONSTRAINT = SURF ? SURF->constraint() : nullptr; if (CONSTRAINT) { @@ -251,7 +276,8 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st const auto RG = CONSTRAINT->logicConstraintRegion(); const auto CLOSEST = RG.closestPoint(mouseCoords); const auto BOX = SURF->getSurfaceBoxGlobal(); - const auto CLOSESTLOCAL = (CLOSEST - (BOX.has_value() ? BOX->pos() : Vector2D{})) * (SURF->getWindow() ? SURF->getWindow()->m_X11SurfaceScaledBy : 1.0); + const auto WINDOW = Desktop::View::CWindow::fromView(SURF->view()); + const auto CLOSESTLOCAL = (CLOSEST - (BOX.has_value() ? BOX->pos() : Vector2D{})) * (WINDOW ? WINDOW->m_X11SurfaceScaledBy : 1.0); g_pCompositor->warpCursorTo(CLOSEST, true); g_pSeatManager->sendPointerMotion(time, CLOSESTLOCAL); @@ -261,14 +287,15 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st return; } else - Debug::log(ERR, "BUG THIS: Null SURF/CONSTRAINT in mouse refocus. Ignoring constraints. {:x} {:x}", rc(SURF.get()), rc(CONSTRAINT.get())); + Log::logger->log(Log::ERR, "BUG THIS: Null SURF/CONSTRAINT in mouse refocus. Ignoring constraints. {:x} {:x}", rc(SURF.get()), + rc(CONSTRAINT.get())); } if (PMONITOR != Desktop::focusState()->monitor() && (*PMOUSEFOCUSMON || refocus) && m_forcedFocus.expired()) Desktop::focusState()->rawMonitorFocus(PMONITOR); // check for windows that have focus priority like our permission popups - pFoundWindow = g_pCompositor->vectorToWindowUnified(mouseCoords, FOCUS_PRIORITY); + pFoundWindow = g_pCompositor->vectorToWindowUnified(mouseCoords, Desktop::View::FOCUS_PRIORITY); if (pFoundWindow) foundSurface = g_pCompositor->vectorWindowToSurface(mouseCoords, pFoundWindow, surfaceCoords); @@ -313,13 +340,13 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st if (forcedFocus && !foundSurface) { pFoundWindow = forcedFocus; surfacePos = pFoundWindow->m_realPosition->value(); - foundSurface = pFoundWindow->m_wlSurface->resource(); + foundSurface = pFoundWindow->wlSurface()->resource(); } // if we are holding a pointer button, // and we're not dnd-ing, don't refocus. Keep focus on last surface. - if (!PROTO::data->dndActive() && !m_currentlyHeldButtons.empty() && Desktop::focusState()->surface() && Desktop::focusState()->surface()->m_mapped && - g_pSeatManager->m_state.pointerFocus && !m_hardInput) { + if (!overridePos.has_value() && !PROTO::data->dndActive() && !m_currentlyHeldButtons.empty() && Desktop::focusState()->surface() && + Desktop::focusState()->surface()->m_mapped && g_pSeatManager->m_state.pointerFocus && !m_hardInput) { foundSurface = g_pSeatManager->m_state.pointerFocus.lock(); // IME popups aren't desktop-like elements @@ -330,17 +357,21 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st m_focusHeldByButtons = true; m_refocusHeldByButtons = refocus; } else { - auto HLSurface = CWLSurface::fromResource(foundSurface); + auto HLSurface = Desktop::View::CWLSurface::fromResource(foundSurface); if (HLSurface) { const auto BOX = HLSurface->getSurfaceBoxGlobal(); if (BOX) { - const auto PWINDOW = HLSurface->getWindow(); + const auto PWINDOW = Desktop::View::CWindow::fromView(HLSurface->view()); + const auto LS = Desktop::View::CLayerSurface::fromView(HLSurface->view()); surfacePos = BOX->pos(); - pFoundLayerSurface = HLSurface->getLayer(); - if (!pFoundLayerSurface) - pFoundWindow = !PWINDOW || PWINDOW->isHidden() ? Desktop::focusState()->window() : PWINDOW; + + if (PWINDOW) + pFoundWindow = PWINDOW; + else if (LS) + pFoundLayerSurface = LS; + } else // reset foundSurface, find one normally foundSurface = nullptr; } else // reset foundSurface, find one normally @@ -348,7 +379,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st } } - g_pLayoutManager->getCurrentLayout()->onMouseMove(getMouseCoordsInternal()); + g_layoutManager->moveMouse(getMouseCoordsInternal()); // forced above all if (!g_pInputManager->m_exclusiveLSes.empty()) { @@ -356,7 +387,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st foundSurface = g_pCompositor->vectorToLayerSurface(mouseCoords, &g_pInputManager->m_exclusiveLSes, &surfaceCoords, &pFoundLayerSurface); if (!foundSurface) { - foundSurface = (*g_pInputManager->m_exclusiveLSes.begin())->m_surface->resource(); + foundSurface = (*g_pInputManager->m_exclusiveLSes.begin())->wlSurface()->resource(); surfacePos = (*g_pInputManager->m_exclusiveLSes.begin())->m_realPosition->goal(); } } @@ -382,28 +413,50 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st foundSurface = g_pCompositor->vectorToLayerSurface(mouseCoords, &PMONITOR->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], &surfaceCoords, &pFoundLayerSurface); // then, we check if the workspace doesn't have a fullscreen window - const auto PWORKSPACE = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace; - const auto PWINDOWIDEAL = g_pCompositor->vectorToWindowUnified(mouseCoords, RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING); - if (PWORKSPACE->m_hasFullscreenWindow && !foundSurface && PWORKSPACE->m_fullscreenMode == FSMODE_FULLSCREEN) { - pFoundWindow = PWORKSPACE->getFullscreenWindow(); + const auto PWORKSPACE = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace; - if (!pFoundWindow) { - // what the fuck, somehow happens occasionally?? - PWORKSPACE->m_hasFullscreenWindow = false; - return; + bool windowIdealQueried = false; + PHLWINDOW pWindowIdeal; + const auto getWindowIdeal = [&]() -> const PHLWINDOW& { + if (!windowIdealQueried) { + pWindowIdeal = g_pCompositor->vectorToWindowUnified( + mouseCoords, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING | Desktop::View::FOLLOW_MOUSE_CHECK); + windowIdealQueried = true; } - if (PWINDOWIDEAL && - ((PWINDOWIDEAL->m_isFloating && (PWINDOWIDEAL->m_createdOverFullscreen || PWINDOWIDEAL->m_pinned)) /* floating over fullscreen or pinned */ - || (PMONITOR->m_activeSpecialWorkspace == PWINDOWIDEAL->m_workspace) /* on an open special workspace */)) - pFoundWindow = PWINDOWIDEAL; + return pWindowIdeal; + }; - if (!pFoundWindow->m_isX11) { - foundSurface = g_pCompositor->vectorWindowToSurface(mouseCoords, pFoundWindow, surfaceCoords); - surfacePos = Vector2D(-1337, -1337); - } else { - foundSurface = pFoundWindow->m_wlSurface->resource(); - surfacePos = pFoundWindow->m_realPosition->value(); + if (PWORKSPACE->m_hasFullscreenWindow && PWORKSPACE->m_fullscreenMode == FSMODE_FULLSCREEN) { + const auto IS_LS_UNFOCUSABLE = pFoundLayerSurface && + (pFoundLayerSurface->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP || + (pFoundLayerSurface->m_layer == ZWLR_LAYER_SHELL_V1_LAYER_TOP && !pFoundLayerSurface->m_aboveFullscreen)); + + if (IS_LS_UNFOCUSABLE) { + foundSurface = nullptr; + pFoundLayerSurface = nullptr; + + pFoundWindow = PWORKSPACE->getFullscreenWindow(); + + if (!pFoundWindow) { + // what the fuck, somehow happens occasionally?? + PWORKSPACE->m_hasFullscreenWindow = false; + return; + } + + const auto& PWINDOWIDEAL = getWindowIdeal(); + if (PWINDOWIDEAL && + ((PWINDOWIDEAL->m_isFloating && (PWINDOWIDEAL->m_createdOverFullscreen || PWINDOWIDEAL->m_pinned)) /* floating over fullscreen or pinned */ + || (PMONITOR->m_activeSpecialWorkspace == PWINDOWIDEAL->m_workspace) /* on an open special workspace */)) + pFoundWindow = PWINDOWIDEAL; + + if (!pFoundWindow->m_isX11) { + foundSurface = g_pCompositor->vectorWindowToSurface(mouseCoords, pFoundWindow, surfaceCoords); + surfacePos = Vector2D(-1337, -1337); + } else { + foundSurface = pFoundWindow->wlSurface()->resource(); + surfacePos = pFoundWindow->m_realPosition->value(); + } } } @@ -412,8 +465,9 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st if (PWORKSPACE->m_hasFullscreenWindow && PWORKSPACE->m_fullscreenMode == FSMODE_MAXIMIZED) { if (!foundSurface) { if (PMONITOR->m_activeSpecialWorkspace) { + const auto& PWINDOWIDEAL = getWindowIdeal(); if (pFoundWindow != PWINDOWIDEAL) - pFoundWindow = g_pCompositor->vectorToWindowUnified(mouseCoords, RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING); + pFoundWindow = PWINDOWIDEAL; if (pFoundWindow && !pFoundWindow->onSpecialWorkspace()) { pFoundWindow = PWORKSPACE->getFullscreenWindow(); @@ -426,8 +480,9 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st } if (!foundSurface) { + const auto& PWINDOWIDEAL = getWindowIdeal(); if (pFoundWindow != PWINDOWIDEAL) - pFoundWindow = g_pCompositor->vectorToWindowUnified(mouseCoords, RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING); + pFoundWindow = PWINDOWIDEAL; if (!(pFoundWindow && (pFoundWindow->m_isFloating && (pFoundWindow->m_createdOverFullscreen || pFoundWindow->m_pinned)))) pFoundWindow = PWORKSPACE->getFullscreenWindow(); @@ -436,19 +491,20 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st } } else { + const auto& PWINDOWIDEAL = getWindowIdeal(); if (pFoundWindow != PWINDOWIDEAL) - pFoundWindow = g_pCompositor->vectorToWindowUnified(mouseCoords, RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING); + pFoundWindow = PWINDOWIDEAL; } if (pFoundWindow) { if (!pFoundWindow->m_isX11) { foundSurface = g_pCompositor->vectorWindowToSurface(mouseCoords, pFoundWindow, surfaceCoords); if (!foundSurface) { - foundSurface = pFoundWindow->m_wlSurface->resource(); + foundSurface = pFoundWindow->wlSurface()->resource(); surfacePos = pFoundWindow->m_realPosition->value(); } } else { - foundSurface = pFoundWindow->m_wlSurface->resource(); + foundSurface = pFoundWindow->wlSurface()->resource(); surfacePos = pFoundWindow->m_realPosition->value(); } } @@ -461,7 +517,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st if (!foundSurface) foundSurface = g_pCompositor->vectorToLayerSurface(mouseCoords, &PMONITOR->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &surfaceCoords, &pFoundLayerSurface); - if (g_pPointerManager->softwareLockedFor(PMONITOR->m_self.lock()) > 0 && !skipFrameSchedule) + if (g_pPointerManager->softwareLockedFor(self) > 0 && !skipFrameSchedule) g_pCompositor->scheduleFrameForMonitor(Desktop::focusState()->monitor(), Aquamarine::IOutput::AQ_SCHEDULE_CURSOR_MOVE); // FIXME: This will be disabled during DnD operations because we do not exactly follow the spec @@ -474,7 +530,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st // we need to grab the last surface. foundSurface = g_pSeatManager->m_state.pointerFocus.lock(); - auto HLSurface = CWLSurface::fromResource(foundSurface); + auto HLSurface = Desktop::View::CWLSurface::fromResource(foundSurface); if (HLSurface) { const auto BOX = HLSurface->getSurfaceBoxGlobal(); @@ -494,7 +550,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st g_pSeatManager->setPointerFocus(nullptr, {}); if (refocus || !Desktop::focusState()->window()) // if we are forcing a refocus, and we don't find a surface, clear the kb focus too! - Desktop::focusState()->rawWindowFocus(nullptr); + Desktop::focusState()->rawWindowFocus(nullptr, FOCUS_REASON); return; } @@ -503,13 +559,6 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st Vector2D surfaceLocal = surfacePos == Vector2D(-1337, -1337) ? surfaceCoords : mouseCoords - surfacePos; - if (pFoundWindow && !pFoundWindow->m_isX11 && surfacePos != Vector2D(-1337, -1337)) { - // calc for oversized windows... fucking bullshit. - CBox geom = pFoundWindow->m_xdgSurface->m_current.geometry; - - surfaceLocal = mouseCoords - surfacePos + geom.pos(); - } - if (pFoundWindow && pFoundWindow->m_isX11) // for x11 force scale zero surfaceLocal = surfaceLocal * pFoundWindow->m_X11SurfaceScaledBy; @@ -529,12 +578,12 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st m_foundSurfaceToFocus = foundSurface; } - if (m_currentlyDraggedWindow.lock() && pFoundWindow != m_currentlyDraggedWindow) { + if (g_layoutManager->dragController()->target() && pFoundWindow != g_layoutManager->dragController()->target()) { g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal); return; } - if (pFoundWindow && foundSurface == pFoundWindow->m_wlSurface->resource() && !m_cursorImageOverridden) { + if (pFoundWindow && foundSurface == pFoundWindow->wlSurface()->resource() && !m_cursorImageOverridden) { const auto BOX = pFoundWindow->getWindowMainSurfaceBox(); if (VECNOTINRECT(mouseCoords, BOX.x, BOX.y, BOX.x + BOX.width, BOX.y + BOX.height)) g_pHyprRenderer->setCursorFromName("left_ptr"); @@ -561,7 +610,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st ((pFoundWindow->m_isFloating && *PFLOATBEHAVIOR == 2) || (Desktop::focusState()->window()->m_isFloating != pFoundWindow->m_isFloating && *PFLOATBEHAVIOR != 0))) { // enter if change floating style if (FOLLOWMOUSE != 3 && allowKeyboardRefocus) - Desktop::focusState()->rawWindowFocus(pFoundWindow, foundSurface); + Desktop::focusState()->rawWindowFocus(pFoundWindow, FOCUS_REASON, foundSurface); g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal); } else if (FOLLOWMOUSE == 2 || FOLLOWMOUSE == 3) g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal); @@ -578,8 +627,11 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st m_lastFocusOnLS = false; return; // don't enter any new surfaces } else { - if (allowKeyboardRefocus && ((FOLLOWMOUSE != 3 && (*PMOUSEREFOCUS || m_lastMouseFocus.lock() != pFoundWindow)) || refocus)) { - if (m_lastMouseFocus.lock() != pFoundWindow || Desktop::focusState()->window() != pFoundWindow || Desktop::focusState()->surface() != foundSurface || refocus) { + auto lastFocus = m_lastMouseFocus.lock(); + + if (allowKeyboardRefocus && ((FOLLOWMOUSE != 3 && (*PMOUSEREFOCUS || lastFocus != pFoundWindow)) || refocus)) { + if (lastFocus != pFoundWindow || Desktop::focusState()->window() != pFoundWindow || Desktop::focusState()->surface() != foundSurface || refocus) { + m_lastMouseFocus = pFoundWindow; // TODO: this looks wrong. When over a popup, it constantly is switching. @@ -589,7 +641,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st const bool hasNoFollowMouse = pFoundWindow && pFoundWindow->m_ruleApplicator->noFollowMouse().valueOrDefault(); if (refocus || !hasNoFollowMouse) - Desktop::focusState()->rawWindowFocus(pFoundWindow, foundSurface); + Desktop::focusState()->rawWindowFocus(pFoundWindow, FOCUS_REASON, foundSurface); } } else Desktop::focusState()->rawSurfaceFocus(foundSurface, pFoundWindow); @@ -598,7 +650,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st } if (g_pSeatManager->m_state.keyboardFocus == nullptr) - Desktop::focusState()->rawWindowFocus(pFoundWindow, foundSurface); + Desktop::focusState()->rawWindowFocus(pFoundWindow, FOCUS_REASON, foundSurface); m_lastFocusOnLS = false; } else { @@ -616,12 +668,17 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st m_lastFocusOnLS = true; } - g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal); - g_pSeatManager->sendPointerMotion(time, surfaceLocal); + if (!overridePos.has_value()) { + g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal); + g_pSeatManager->sendPointerMotion(time, surfaceLocal); + } } -void CInputManager::onMouseButton(IPointer::SButtonEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("mouseButton", e); +void CInputManager::onMouseButton(IPointer::SButtonEvent e, SP mouse) { + Event::SCallbackInfo info; + Event::bus()->m_events.input.mouse.button.emit(e, info); + if (info.cancelled) + return; if (e.mouse) recheckMouseWarpOnMouseInput(); @@ -637,7 +694,7 @@ void CInputManager::onMouseButton(IPointer::SButtonEvent e) { } switch (m_clickBehavior) { - case CLICKMODE_DEFAULT: processMouseDownNormal(e); break; + case CLICKMODE_DEFAULT: processMouseDownNormal(e, mouse); break; case CLICKMODE_KILL: processMouseDownKill(e); break; default: break; } @@ -651,10 +708,12 @@ void CInputManager::onMouseButton(IPointer::SButtonEvent e) { m_focusHeldByButtons = false; m_refocusHeldByButtons = false; } + + g_pSeatManager->sendPointerFrame(); } void CInputManager::processMouseRequest(const CSeatManager::SSetCursorEvent& event) { - Debug::log(LOG, "cursorImage request: surface {:x}", rc(event.surf.get())); + Log::logger->log(Log::DEBUG, "cursorImage request: surface {:x}", rc(event.surf.get())); if (event.surf != m_cursorSurfaceInfo.wlSurface->resource()) { m_cursorSurfaceInfo.wlSurface->unassign(); @@ -703,13 +762,13 @@ eClickBehaviorMode CInputManager::getClickMode() { void CInputManager::setClickMode(eClickBehaviorMode mode) { switch (mode) { case CLICKMODE_DEFAULT: - Debug::log(LOG, "SetClickMode: DEFAULT"); + Log::logger->log(Log::DEBUG, "SetClickMode: DEFAULT"); m_clickBehavior = CLICKMODE_DEFAULT; g_pHyprRenderer->setCursorFromName("left_ptr", true); break; case CLICKMODE_KILL: - Debug::log(LOG, "SetClickMode: KILL"); + Log::logger->log(Log::DEBUG, "SetClickMode: KILL"); m_clickBehavior = CLICKMODE_KILL; // remove constraints @@ -723,11 +782,11 @@ void CInputManager::setClickMode(eClickBehaviorMode mode) { } } -void CInputManager::processMouseDownNormal(const IPointer::SButtonEvent& e) { +void CInputManager::processMouseDownNormal(const IPointer::SButtonEvent& e, SP mouse) { // notify the keybind manager static auto PPASSMOUSE = CConfigValue("binds:pass_mouse_when_bound"); - const auto PASS = g_pKeybindManager->onMouseEvent(e); + const auto PASS = g_pKeybindManager->onMouseEvent(e, mouse); static auto PFOLLOWMOUSE = CConfigValue("input:follow_mouse"); static auto PRESIZEONBORDER = CConfigValue("general:resize_on_border"); static auto PBORDERSIZE = CConfigValue("general:border_size"); @@ -738,7 +797,7 @@ void CInputManager::processMouseDownNormal(const IPointer::SButtonEvent& e) { return; const auto mouseCoords = g_pInputManager->getMouseCoordsInternal(); - const auto w = g_pCompositor->vectorToWindowUnified(mouseCoords, ALLOW_FLOATING | RESERVED_EXTENTS | INPUT_EXTENTS); + const auto w = g_pCompositor->vectorToWindowUnified(mouseCoords, Desktop::View::ALLOW_FLOATING | Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS); if (w && !m_lastFocusOnLS && !g_pSessionLockManager->isSessionLocked() && w->checkInputOnDecos(INPUT_TYPE_BUTTON, mouseCoords, e)) return; @@ -779,10 +838,12 @@ void CInputManager::processMouseDownNormal(const IPointer::SButtonEvent& e) { if (!g_pSeatManager->m_state.pointerFocus) break; - auto HLSurf = CWLSurface::fromResource(g_pSeatManager->m_state.pointerFocus.lock()); + auto HLSurf = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.pointerFocus.lock()); - if (HLSurf && HLSurf->getWindow()) - g_pCompositor->changeWindowZOrder(HLSurf->getWindow(), true); + // pointerFocus can target a surface without a Desktop::View (e.g. IME popups), so view() may be null. + const auto PVIEW = HLSurf ? HLSurf->view() : nullptr; + if (PVIEW && PVIEW->type() == Desktop::View::VIEW_TYPE_WINDOW) + g_pCompositor->changeWindowZOrder(dynamicPointerCast(PVIEW), true); break; } @@ -805,13 +866,17 @@ void CInputManager::processMouseDownNormal(const IPointer::SButtonEvent& e) { void CInputManager::processMouseDownKill(const IPointer::SButtonEvent& e) { switch (e.state) { case WL_POINTER_BUTTON_STATE_PRESSED: { - const auto PWINDOW = g_pCompositor->vectorToWindowUnified(getMouseCoordsInternal(), RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING); + const auto PWINDOW = + g_pCompositor->vectorToWindowUnified(getMouseCoordsInternal(), Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING); if (!PWINDOW) { - Debug::log(ERR, "Cannot kill invalid window!"); + Log::logger->log(Log::ERR, "Cannot kill invalid window!"); 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; @@ -838,20 +903,22 @@ void CInputManager::onMouseWheel(IPointer::SAxisEvent e, SP pointer) { if (pointer && pointer->m_scrollFactor.has_value()) factor = *pointer->m_scrollFactor; - const auto EMAP = std::unordered_map{{"event", e}}; - EMIT_HOOK_EVENT_CANCELLABLE("mouseAxis", EMAP); + Event::SCallbackInfo info; + Event::bus()->m_events.input.mouse.axis.emit(e, info); + if (info.cancelled) + return; if (e.mouse) recheckMouseWarpOnMouseInput(); - bool passEvent = g_pKeybindManager->onAxisEvent(e); + bool passEvent = g_pKeybindManager->onAxisEvent(e, pointer); if (!passEvent) return; if (!m_lastFocusOnLS) { const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); - const auto PWINDOW = g_pCompositor->vectorToWindowUnified(MOUSECOORDS, RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING); + const auto PWINDOW = g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING); if (PWINDOW) { if (PWINDOW->checkInputOnDecos(INPUT_TYPE_AXIS, MOUSECOORDS, e)) @@ -926,6 +993,23 @@ void CInputManager::onMouseWheel(IPointer::SAxisEvent e, SP pointer) { int32_t deltaDiscrete = std::abs(discrete) != 0 && std::abs(discrete) < 1 ? std::copysign(1, discrete) : std::round(discrete); g_pSeatManager->sendPointerAxis(e.timeMs, e.axis, delta, deltaDiscrete, value120, e.source, WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL); + + const bool deferPointerFrame = e.source == WL_POINTER_AXIS_SOURCE_FINGER || e.source == WL_POINTER_AXIS_SOURCE_CONTINUOUS; + if (deferPointerFrame) { + m_pointerAxisFramePending = true; + return; + } + + m_pointerAxisFramePending = false; + g_pSeatManager->sendPointerFrame(); +} + +void CInputManager::onPointerFrame() { + if (!m_pointerAxisFramePending) + return; + + m_pointerAxisFramePending = false; + g_pSeatManager->sendPointerFrame(); } Vector2D CInputManager::getMouseCoordsInternal() { @@ -937,7 +1021,7 @@ void CInputManager::newKeyboard(SP keeb) { setupKeyboard(PNEWKEYBOARD); - Debug::log(LOG, "New keyboard created, pointers Hypr: {:x}", rc(PNEWKEYBOARD.get())); + Log::logger->log(Log::DEBUG, "New keyboard created, pointers Hypr: {:x}", rc(PNEWKEYBOARD.get())); } void CInputManager::newKeyboard(SP keyboard) { @@ -945,7 +1029,7 @@ void CInputManager::newKeyboard(SP keyboard) { setupKeyboard(PNEWKEYBOARD); - Debug::log(LOG, "New keyboard created, pointers Hypr: {:x} and AQ: {:x}", rc(PNEWKEYBOARD.get()), rc(keyboard.get())); + Log::logger->log(Log::DEBUG, "New keyboard created, pointers Hypr: {:x} and AQ: {:x}", rc(PNEWKEYBOARD.get()), rc(keyboard.get())); } void CInputManager::newVirtualKeyboard(SP keyboard) { @@ -953,7 +1037,7 @@ void CInputManager::newVirtualKeyboard(SP keyboard) setupKeyboard(PNEWKEYBOARD); - Debug::log(LOG, "New virtual keyboard created at {:x}", rc(PNEWKEYBOARD.get())); + Log::logger->log(Log::DEBUG, "New virtual keyboard created at {:x}", rc(PNEWKEYBOARD.get())); } void CInputManager::setupKeyboard(SP keeb) { @@ -964,7 +1048,7 @@ void CInputManager::setupKeyboard(SP keeb) { try { keeb->m_hlName = getNameForNewDevice(keeb->m_deviceName); } catch (std::exception& e) { - Debug::log(ERR, "Keyboard had no name???"); // logic error + Log::logger->log(Log::ERR, "Keyboard had no name???"); // logic error } keeb->m_events.destroy.listenStatic([this, keeb = keeb.get()] { @@ -974,7 +1058,7 @@ void CInputManager::setupKeyboard(SP keeb) { return; destroyKeyboard(PKEEB); - Debug::log(LOG, "Destroyed keyboard {:x}", rc(keeb)); + Log::logger->log(Log::DEBUG, "Destroyed keyboard {:x}", rc(keeb)); }); keeb->m_keyboardEvents.key.listenStatic([this, keeb = keeb.get()](const IKeyboard::SKeyEvent& event) { @@ -1011,7 +1095,7 @@ void CInputManager::setupKeyboard(SP keeb) { } g_pEventManager->postEvent(SHyprIPCEvent{"activelayout", PKEEB->m_hlName + "," + LAYOUT}); - EMIT_HOOK_EVENT("activeLayout", (std::vector{PKEEB, LAYOUT})); + Event::bus()->m_events.input.keyboard.layout.emit(PKEEB, LAYOUT); }); disableAllKeyboards(false); @@ -1037,39 +1121,44 @@ void CInputManager::setKeyboardLayout() { void CInputManager::applyConfigToKeyboard(SP pKeyboard) { auto devname = pKeyboard->m_hlName; - const auto HASCONFIG = g_pConfigManager->deviceConfigExists(devname); + const auto HASCONFIG = Config::mgr()->deviceConfigExists(devname); - Debug::log(LOG, "ApplyConfigToKeyboard for \"{}\", hasconfig: {}", devname, sc(HASCONFIG)); + Log::logger->log(Log::DEBUG, "ApplyConfigToKeyboard for \"{}\", hasconfig: {}", devname, sc(HASCONFIG)); - const auto REPEATRATE = g_pConfigManager->getDeviceInt(devname, "repeat_rate", "input:repeat_rate"); - const auto REPEATDELAY = g_pConfigManager->getDeviceInt(devname, "repeat_delay", "input:repeat_delay"); + const auto REPEATRATE = Config::mgr()->getDeviceInt(devname, "repeat_rate", "input:repeat_rate"); + const auto REPEATDELAY = Config::mgr()->getDeviceInt(devname, "repeat_delay", "input:repeat_delay"); - const auto NUMLOCKON = g_pConfigManager->getDeviceInt(devname, "numlock_by_default", "input:numlock_by_default"); - const auto RESOLVEBINDSBYSYM = g_pConfigManager->getDeviceInt(devname, "resolve_binds_by_sym", "input:resolve_binds_by_sym"); + const auto NUMLOCKON = Config::mgr()->getDeviceInt(devname, "numlock_by_default", "input:numlock_by_default"); + const auto RESOLVEBINDSBYSYM = Config::mgr()->getDeviceInt(devname, "resolve_binds_by_sym", "input:resolve_binds_by_sym"); - const auto FILEPATH = g_pConfigManager->getDeviceString(devname, "kb_file", "input:kb_file"); - const auto RULES = g_pConfigManager->getDeviceString(devname, "kb_rules", "input:kb_rules"); - const auto MODEL = g_pConfigManager->getDeviceString(devname, "kb_model", "input:kb_model"); - const auto LAYOUT = g_pConfigManager->getDeviceString(devname, "kb_layout", "input:kb_layout"); - const auto VARIANT = g_pConfigManager->getDeviceString(devname, "kb_variant", "input:kb_variant"); - const auto OPTIONS = g_pConfigManager->getDeviceString(devname, "kb_options", "input:kb_options"); + const auto FILEPATH = Config::mgr()->getDeviceString(devname, "kb_file", "input:kb_file"); + const auto RULES = Config::mgr()->getDeviceString(devname, "kb_rules", "input:kb_rules"); + const auto MODEL = Config::mgr()->getDeviceString(devname, "kb_model", "input:kb_model"); + const auto LAYOUT = Config::mgr()->getDeviceString(devname, "kb_layout", "input:kb_layout"); + const auto VARIANT = Config::mgr()->getDeviceString(devname, "kb_variant", "input:kb_variant"); + const auto OPTIONS = Config::mgr()->getDeviceString(devname, "kb_options", "input:kb_options"); - const auto ENABLED = HASCONFIG ? g_pConfigManager->getDeviceInt(devname, "enabled") : true; - const auto ALLOWBINDS = HASCONFIG ? g_pConfigManager->getDeviceInt(devname, "keybinds") : true; + const auto ENABLED = HASCONFIG ? Config::mgr()->getDeviceInt(devname, "enabled") : true; + const auto ALLOWBINDS = HASCONFIG ? Config::mgr()->getDeviceInt(devname, "keybinds") : true; pKeyboard->m_enabled = ENABLED; pKeyboard->m_resolveBindsBySym = RESOLVEBINDSBYSYM; pKeyboard->m_allowBinds = ALLOWBINDS; const auto PERM = g_pDynamicPermissionManager->clientPermissionModeWithString(-1, pKeyboard->m_hlName, PERMISSION_TYPE_KEYBOARD); + if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) { + + // disallow while pending + pKeyboard->m_allowed = false; + const auto PROMISE = g_pDynamicPermissionManager->promiseFor(-1, pKeyboard->m_hlName, PERMISSION_TYPE_KEYBOARD); if (!PROMISE) - Debug::log(ERR, "BUG THIS: No promise for client permission for keyboard"); + Log::logger->log(Log::ERR, "BUG THIS: No promise for client permission for keyboard"); else { PROMISE->then([k = WP{pKeyboard}](SP> r) { if (r->hasError()) { - Debug::log(ERR, "BUG THIS: No permission returned for keyboard"); + Log::logger->log(Log::ERR, "BUG THIS: No permission returned for keyboard"); return; } @@ -1086,7 +1175,7 @@ void CInputManager::applyConfigToKeyboard(SP pKeyboard) { if (NUMLOCKON == pKeyboard->m_numlockOn && REPEATDELAY == pKeyboard->m_repeatDelay && REPEATRATE == pKeyboard->m_repeatRate && RULES == pKeyboard->m_currentRules.rules && MODEL == pKeyboard->m_currentRules.model && LAYOUT == pKeyboard->m_currentRules.layout && VARIANT == pKeyboard->m_currentRules.variant && OPTIONS == pKeyboard->m_currentRules.options && FILEPATH == pKeyboard->m_xkbFilePath) { - Debug::log(LOG, "Not applying config to keyboard, it did not change."); + Log::logger->log(Log::DEBUG, "Not applying config to keyboard, it did not change."); return; } } catch (std::exception& e) { @@ -1103,10 +1192,10 @@ void CInputManager::applyConfigToKeyboard(SP pKeyboard) { const auto LAYOUTSTR = pKeyboard->getActiveLayout(); g_pEventManager->postEvent(SHyprIPCEvent{"activelayout", pKeyboard->m_hlName + "," + LAYOUTSTR}); - EMIT_HOOK_EVENT("activeLayout", (std::vector{pKeyboard, LAYOUTSTR})); + Event::bus()->m_events.input.keyboard.layout.emit(pKeyboard, LAYOUTSTR); - Debug::log(LOG, "Set the keyboard layout to {} and variant to {} for keyboard \"{}\"", pKeyboard->m_currentRules.layout, pKeyboard->m_currentRules.variant, - pKeyboard->m_hlName); + 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); } void CInputManager::newVirtualMouse(SP mouse) { @@ -1114,7 +1203,7 @@ void CInputManager::newVirtualMouse(SP mouse) { setupMouse(PMOUSE); - Debug::log(LOG, "New virtual mouse created"); + Log::logger->log(Log::DEBUG, "New virtual mouse created"); } void CInputManager::newMouse(SP mouse) { @@ -1122,7 +1211,7 @@ void CInputManager::newMouse(SP mouse) { setupMouse(mouse); - Debug::log(LOG, "New mouse created, pointer Hypr: {:x}", rc(mouse.get())); + Log::logger->log(Log::DEBUG, "New mouse created, pointer Hypr: {:x}", rc(mouse.get())); } void CInputManager::newMouse(SP mouse) { @@ -1130,7 +1219,7 @@ void CInputManager::newMouse(SP mouse) { setupMouse(PMOUSE); - Debug::log(LOG, "New mouse created, pointer AQ: {:x}", rc(mouse.get())); + Log::logger->log(Log::DEBUG, "New mouse created, pointer AQ: {:x}", rc(mouse.get())); } void CInputManager::setupMouse(SP mauz) { @@ -1139,15 +1228,15 @@ void CInputManager::setupMouse(SP mauz) { try { mauz->m_hlName = getNameForNewDevice(mauz->m_deviceName); } catch (std::exception& e) { - Debug::log(ERR, "Mouse had no name???"); // logic error + Log::logger->log(Log::ERR, "Mouse had no name???"); // logic error } if (mauz->aq() && mauz->aq()->getLibinputHandle()) { const auto LIBINPUTDEV = mauz->aq()->getLibinputHandle(); - Debug::log(LOG, "New mouse has libinput sens {:.2f} ({:.2f}) with accel profile {} ({})", libinput_device_config_accel_get_speed(LIBINPUTDEV), - libinput_device_config_accel_get_default_speed(LIBINPUTDEV), sc(libinput_device_config_accel_get_profile(LIBINPUTDEV)), - sc(libinput_device_config_accel_get_default_profile(LIBINPUTDEV))); + Log::logger->log(Log::DEBUG, "New mouse has libinput sens {:.2f} ({:.2f}) with accel profile {} ({})", libinput_device_config_accel_get_speed(LIBINPUTDEV), + libinput_device_config_accel_get_default_speed(LIBINPUTDEV), sc(libinput_device_config_accel_get_profile(LIBINPUTDEV)), + sc(libinput_device_config_accel_get_default_profile(LIBINPUTDEV))); } g_pPointerManager->attachPointer(mauz); @@ -1167,10 +1256,10 @@ void CInputManager::setPointerConfigs() { for (auto const& m : m_pointers) { auto devname = m->m_hlName; - const auto HASCONFIG = g_pConfigManager->deviceConfigExists(devname); + const auto HASCONFIG = Config::mgr()->deviceConfigExists(devname); if (HASCONFIG) { - const auto ENABLED = g_pConfigManager->getDeviceInt(devname, "enabled"); + const auto ENABLED = Config::mgr()->getDeviceInt(devname, "enabled"); if (ENABLED && !m->m_connected) { g_pPointerManager->attachPointer(m); m->m_connected = true; @@ -1180,8 +1269,8 @@ void CInputManager::setPointerConfigs() { } } - if (g_pConfigManager->deviceConfigExplicitlySet(devname, "scroll_factor")) - m->m_scrollFactor = std::clamp(g_pConfigManager->getDeviceFloat(devname, "scroll_factor", "input:scroll_factor"), 0.F, 100.F); + if (Config::mgr()->deviceConfigExplicitlySet(devname, "scroll_factor")) + m->m_scrollFactor = std::clamp(Config::mgr()->getDeviceFloat(devname, "scroll_factor", "input:scroll_factor"), 0.F, 100.F); else m->m_scrollFactor = std::nullopt; @@ -1192,32 +1281,32 @@ void CInputManager::setPointerConfigs() { const auto ISTOUCHPAD = libinput_device_has_capability(LIBINPUTDEV, LIBINPUT_DEVICE_CAP_POINTER) && libinput_device_get_size(LIBINPUTDEV, &touchw, &touchh) == 0; // pointer with size is a touchpad - if (g_pConfigManager->getDeviceInt(devname, "clickfinger_behavior", "input:touchpad:clickfinger_behavior") == 0) // toggle software buttons or clickfinger + if (Config::mgr()->getDeviceInt(devname, "clickfinger_behavior", "input:touchpad:clickfinger_behavior") == 0) // toggle software buttons or clickfinger libinput_device_config_click_set_method(LIBINPUTDEV, LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS); else libinput_device_config_click_set_method(LIBINPUTDEV, LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER); - if (g_pConfigManager->getDeviceInt(devname, "left_handed", "input:left_handed") == 0) + if (Config::mgr()->getDeviceInt(devname, "left_handed", "input:left_handed") == 0) libinput_device_config_left_handed_set(LIBINPUTDEV, 0); else libinput_device_config_left_handed_set(LIBINPUTDEV, 1); if (libinput_device_config_middle_emulation_is_available(LIBINPUTDEV)) { // middleclick on r+l mouse button pressed - if (g_pConfigManager->getDeviceInt(devname, "middle_button_emulation", "input:touchpad:middle_button_emulation") == 1) + if (Config::mgr()->getDeviceInt(devname, "middle_button_emulation", "input:touchpad:middle_button_emulation") == 1) libinput_device_config_middle_emulation_set_enabled(LIBINPUTDEV, LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED); else libinput_device_config_middle_emulation_set_enabled(LIBINPUTDEV, LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED); - const auto TAP_MAP = g_pConfigManager->getDeviceString(devname, "tap_button_map", "input:touchpad:tap_button_map"); + const auto TAP_MAP = Config::mgr()->getDeviceString(devname, "tap_button_map", "input:touchpad:tap_button_map"); if (TAP_MAP.empty() || TAP_MAP == "lrm") libinput_device_config_tap_set_button_map(LIBINPUTDEV, LIBINPUT_CONFIG_TAP_MAP_LRM); else if (TAP_MAP == "lmr") libinput_device_config_tap_set_button_map(LIBINPUTDEV, LIBINPUT_CONFIG_TAP_MAP_LMR); else - Debug::log(WARN, "Tap button mapping unknown"); + Log::logger->log(Log::WARN, "Tap button mapping unknown"); } - const auto SCROLLMETHOD = g_pConfigManager->getDeviceString(devname, "scroll_method", "input:scroll_method"); + const auto SCROLLMETHOD = Config::mgr()->getDeviceString(devname, "scroll_method", "input:scroll_method"); if (SCROLLMETHOD.empty()) { libinput_device_config_scroll_set_method(LIBINPUTDEV, libinput_device_config_scroll_get_default_method(LIBINPUTDEV)); } else if (SCROLLMETHOD == "no_scroll") { @@ -1229,56 +1318,55 @@ void CInputManager::setPointerConfigs() { } else if (SCROLLMETHOD == "on_button_down") { libinput_device_config_scroll_set_method(LIBINPUTDEV, LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN); } else { - Debug::log(WARN, "Scroll method unknown"); + Log::logger->log(Log::WARN, "Scroll method unknown"); } - if (g_pConfigManager->getDeviceInt(devname, "tap-and-drag", "input:touchpad:tap-and-drag") == 0) + if (Config::mgr()->getDeviceInt(devname, "tap-and-drag", "input:touchpad:tap-and-drag") == 0) libinput_device_config_tap_set_drag_enabled(LIBINPUTDEV, LIBINPUT_CONFIG_DRAG_DISABLED); else libinput_device_config_tap_set_drag_enabled(LIBINPUTDEV, LIBINPUT_CONFIG_DRAG_ENABLED); - const auto TAP_DRAG_LOCK = g_pConfigManager->getDeviceInt(devname, "drag_lock", "input:touchpad:drag_lock"); + const auto TAP_DRAG_LOCK = Config::mgr()->getDeviceInt(devname, "drag_lock", "input:touchpad:drag_lock"); if (TAP_DRAG_LOCK >= 0 && TAP_DRAG_LOCK <= 2) { libinput_device_config_tap_set_drag_lock_enabled(LIBINPUTDEV, sc(TAP_DRAG_LOCK)); } if (libinput_device_config_tap_get_finger_count(LIBINPUTDEV)) // this is for tapping (like on a laptop) libinput_device_config_tap_set_enabled(LIBINPUTDEV, - g_pConfigManager->getDeviceInt(devname, "tap-to-click", "input:touchpad:tap-to-click") == 1 ? LIBINPUT_CONFIG_TAP_ENABLED : - LIBINPUT_CONFIG_TAP_DISABLED); + Config::mgr()->getDeviceInt(devname, "tap-to-click", "input:touchpad:tap-to-click") == 1 ? LIBINPUT_CONFIG_TAP_ENABLED : + LIBINPUT_CONFIG_TAP_DISABLED); if (libinput_device_config_scroll_has_natural_scroll(LIBINPUTDEV)) { if (ISTOUCHPAD) - libinput_device_config_scroll_set_natural_scroll_enabled(LIBINPUTDEV, - g_pConfigManager->getDeviceInt(devname, "natural_scroll", "input:touchpad:natural_scroll")); + libinput_device_config_scroll_set_natural_scroll_enabled(LIBINPUTDEV, Config::mgr()->getDeviceInt(devname, "natural_scroll", "input:touchpad:natural_scroll")); else - libinput_device_config_scroll_set_natural_scroll_enabled(LIBINPUTDEV, g_pConfigManager->getDeviceInt(devname, "natural_scroll", "input:natural_scroll")); + libinput_device_config_scroll_set_natural_scroll_enabled(LIBINPUTDEV, Config::mgr()->getDeviceInt(devname, "natural_scroll", "input:natural_scroll")); } if (libinput_device_config_3fg_drag_get_finger_count(LIBINPUTDEV) >= 3) { - const auto DRAG_3FG_STATE = sc(g_pConfigManager->getDeviceInt(devname, "drag_3fg", "input:touchpad:drag_3fg")); + const auto DRAG_3FG_STATE = sc(Config::mgr()->getDeviceInt(devname, "drag_3fg", "input:touchpad:drag_3fg")); libinput_device_config_3fg_drag_set_enabled(LIBINPUTDEV, DRAG_3FG_STATE); } if (libinput_device_config_dwt_is_available(LIBINPUTDEV)) { - const auto DWT = sc(g_pConfigManager->getDeviceInt(devname, "disable_while_typing", "input:touchpad:disable_while_typing") != 0); + const auto DWT = sc(Config::mgr()->getDeviceInt(devname, "disable_while_typing", "input:touchpad:disable_while_typing") != 0); libinput_device_config_dwt_set_enabled(LIBINPUTDEV, DWT); } - const auto LIBINPUTSENS = std::clamp(g_pConfigManager->getDeviceFloat(devname, "sensitivity", "input:sensitivity"), -1.f, 1.f); + const auto LIBINPUTSENS = std::clamp(Config::mgr()->getDeviceFloat(devname, "sensitivity", "input:sensitivity"), -1.f, 1.f); libinput_device_config_accel_set_speed(LIBINPUTDEV, LIBINPUTSENS); if (libinput_device_config_rotation_is_available(LIBINPUTDEV)) { - const auto ROTATION = std::clamp(g_pConfigManager->getDeviceInt(devname, "rotation", "input:rotation"), 0, 359); + const auto ROTATION = std::clamp(Config::mgr()->getDeviceInt(devname, "rotation", "input:rotation"), 0, 359); libinput_device_config_rotation_set_angle(LIBINPUTDEV, ROTATION); } - m->m_flipX = g_pConfigManager->getDeviceInt(devname, "flip_x", "input:touchpad:flip_x") != 0; - m->m_flipY = g_pConfigManager->getDeviceInt(devname, "flip_y", "input:touchpad:flip_y") != 0; + m->m_flipX = Config::mgr()->getDeviceInt(devname, "flip_x", "input:touchpad:flip_x") != 0; + m->m_flipY = Config::mgr()->getDeviceInt(devname, "flip_y", "input:touchpad:flip_y") != 0; - const auto ACCELPROFILE = g_pConfigManager->getDeviceString(devname, "accel_profile", "input:accel_profile"); - const auto SCROLLPOINTS = g_pConfigManager->getDeviceString(devname, "scroll_points", "input:scroll_points"); + const auto ACCELPROFILE = Config::mgr()->getDeviceString(devname, "accel_profile", "input:accel_profile"); + const auto SCROLLPOINTS = Config::mgr()->getDeviceString(devname, "scroll_points", "input:scroll_points"); if (ACCELPROFILE.empty()) { libinput_device_config_accel_set_profile(LIBINPUTDEV, libinput_device_config_accel_get_default_profile(LIBINPUTDEV)); @@ -1308,27 +1396,27 @@ void CInputManager::setPointerConfigs() { } libinput_config_accel_set_points(CONFIG, LIBINPUT_ACCEL_TYPE_SCROLL, scrollStep, scrollPoints.size(), scrollPoints.data()); - } catch (std::exception& e) { Debug::log(ERR, "Invalid values in scroll_points"); } + } catch (std::exception& e) { Log::logger->log(Log::ERR, "Invalid values in scroll_points"); } } libinput_config_accel_set_points(CONFIG, LIBINPUT_ACCEL_TYPE_MOTION, accelStep, accelPoints.size(), accelPoints.data()); libinput_device_config_accel_apply(LIBINPUTDEV, CONFIG); libinput_config_accel_destroy(CONFIG); - } catch (std::exception& e) { Debug::log(ERR, "Invalid values in custom accel profile"); } + } catch (std::exception& e) { Log::logger->log(Log::ERR, "Invalid values in custom accel profile"); } } else { - Debug::log(WARN, "Unknown acceleration profile, falling back to default"); + Log::logger->log(Log::WARN, "Unknown acceleration profile, falling back to default"); } - const auto SCROLLBUTTON = g_pConfigManager->getDeviceInt(devname, "scroll_button", "input:scroll_button"); + const auto SCROLLBUTTON = Config::mgr()->getDeviceInt(devname, "scroll_button", "input:scroll_button"); libinput_device_config_scroll_set_button(LIBINPUTDEV, SCROLLBUTTON == 0 ? libinput_device_config_scroll_get_default_button(LIBINPUTDEV) : SCROLLBUTTON); - const auto SCROLLBUTTONLOCK = g_pConfigManager->getDeviceInt(devname, "scroll_button_lock", "input:scroll_button_lock"); + const auto SCROLLBUTTONLOCK = Config::mgr()->getDeviceInt(devname, "scroll_button_lock", "input:scroll_button_lock"); libinput_device_config_scroll_set_button_lock(LIBINPUTDEV, SCROLLBUTTONLOCK == 0 ? LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED : LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED); - Debug::log(LOG, "Applied config to mouse {}, sens {:.2f}", m->m_hlName, LIBINPUTSENS); + Log::logger->log(Log::DEBUG, "Applied config to mouse {}, sens {:.2f}", m->m_hlName, LIBINPUTSENS); } } } @@ -1339,7 +1427,7 @@ static void removeFromHIDs(WP hid) { } void CInputManager::destroyKeyboard(SP pKeyboard) { - Debug::log(LOG, "Keyboard at {:x} removed", rc(pKeyboard.get())); + Log::logger->log(Log::DEBUG, "Keyboard at {:x} removed", rc(pKeyboard.get())); std::erase_if(m_keyboards, [pKeyboard](const auto& other) { return other == pKeyboard; }); @@ -1363,7 +1451,7 @@ void CInputManager::destroyKeyboard(SP pKeyboard) { } void CInputManager::destroyPointer(SP mouse) { - Debug::log(LOG, "Pointer at {:x} removed", rc(mouse.get())); + Log::logger->log(Log::DEBUG, "Pointer at {:x} removed", rc(mouse.get())); std::erase_if(m_pointers, [mouse](const auto& other) { return other == mouse; }); @@ -1376,7 +1464,7 @@ void CInputManager::destroyPointer(SP mouse) { } void CInputManager::destroyTouchDevice(SP touch) { - Debug::log(LOG, "Touch device at {:x} removed", rc(touch.get())); + Log::logger->log(Log::DEBUG, "Touch device at {:x} removed", rc(touch.get())); std::erase_if(m_touches, [touch](const auto& other) { return other == touch; }); @@ -1384,7 +1472,7 @@ void CInputManager::destroyTouchDevice(SP touch) { } void CInputManager::destroyTablet(SP tablet) { - Debug::log(LOG, "Tablet device at {:x} removed", rc(tablet.get())); + Log::logger->log(Log::DEBUG, "Tablet device at {:x} removed", rc(tablet.get())); std::erase_if(m_tablets, [tablet](const auto& other) { return other == tablet; }); @@ -1392,7 +1480,7 @@ void CInputManager::destroyTablet(SP tablet) { } void CInputManager::destroyTabletTool(SP tool) { - Debug::log(LOG, "Tablet tool at {:x} removed", rc(tool.get())); + Log::logger->log(Log::DEBUG, "Tablet tool at {:x} removed", rc(tool.get())); std::erase_if(m_tabletTools, [tool](const auto& other) { return other == tool; }); @@ -1400,7 +1488,7 @@ void CInputManager::destroyTabletTool(SP tool) { } void CInputManager::destroyTabletPad(SP pad) { - Debug::log(LOG, "Tablet pad at {:x} removed", rc(pad.get())); + Log::logger->log(Log::DEBUG, "Tablet pad at {:x} removed", rc(pad.get())); std::erase_if(m_tabletPads, [pad](const auto& other) { return other == pad; }); @@ -1425,14 +1513,16 @@ void CInputManager::onKeyboardKey(const IKeyboard::SKeyEvent& event, SPm_enabled || !pKeyboard->m_allowed) return; - const bool DISALLOWACTION = pKeyboard->isVirtual() && shouldIgnoreVirtualKeyboard(pKeyboard); + const bool DISALLOWACTION = pKeyboard->isVirtual() && shouldIgnoreVirtualKeyboard(pKeyboard); - const auto IME = m_relay.m_inputMethod.lock(); - const bool HASIME = IME && IME->hasGrab(); - const bool USEIME = HASIME && !DISALLOWACTION; + const auto IME = m_relay.m_inputMethod.lock(); + const bool HASIME = IME && IME->hasGrab(); + const bool USEIME = HASIME && !DISALLOWACTION; - const auto EMAP = std::unordered_map{{"keyboard", pKeyboard}, {"event", event}}; - EMIT_HOOK_EVENT_CANCELLABLE("keyPress", EMAP); + Event::SCallbackInfo info; + Event::bus()->m_events.input.keyboard.key.emit(event, info); + if (info.cancelled) + return; bool passEvent = DISALLOWACTION; @@ -1518,10 +1608,10 @@ void CInputManager::onKeyboardMod(SP pKeyboard) { const auto LAYOUT = pKeyboard->getActiveLayout(); - Debug::log(LOG, "LAYOUT CHANGED TO {} GROUP {}", LAYOUT, MODS.group); + Log::logger->log(Log::DEBUG, "LAYOUT CHANGED TO {} GROUP {}", LAYOUT, MODS.group); g_pEventManager->postEvent(SHyprIPCEvent{"activelayout", pKeyboard->m_hlName + "," + LAYOUT}); - EMIT_HOOK_EVENT("activeLayout", (std::vector{pKeyboard, LAYOUT})); + Event::bus()->m_events.input.keyboard.layout.emit(pKeyboard, LAYOUT); } } @@ -1548,7 +1638,7 @@ void CInputManager::refocus(std::optional overridePos) { bool CInputManager::refocusLastWindow(PHLMONITOR pMonitor) { if (!m_exclusiveLSes.empty()) { - Debug::log(LOG, "CInputManager::refocusLastWindow: ignoring, exclusive LS present."); + Log::logger->log(Log::DEBUG, "CInputManager::refocusLastWindow: ignoring, exclusive LS present."); return false; } @@ -1581,13 +1671,13 @@ bool CInputManager::refocusLastWindow(PHLMONITOR pMonitor) { if (!foundSurface && Desktop::focusState()->window() && Desktop::focusState()->window()->m_workspace && Desktop::focusState()->window()->m_workspace->isVisibleNotCovered()) { // then the last focused window if we're on the same workspace as it const auto PLASTWINDOW = Desktop::focusState()->window(); - Desktop::focusState()->fullWindowFocus(PLASTWINDOW); + Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_FFM); } else { // otherwise fall back to a normal refocus. if (foundSurface && !foundSurface->m_hlSurface->keyboardFocusable()) { const auto PLASTWINDOW = Desktop::focusState()->window(); - Desktop::focusState()->fullWindowFocus(PLASTWINDOW); + Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_FFM); } refocus(); @@ -1624,7 +1714,7 @@ bool CInputManager::isLocked() { if (!isConstrained()) return false; - const auto SURF = CWLSurface::fromResource(Desktop::focusState()->surface()); + const auto SURF = Desktop::View::CWLSurface::fromResource(Desktop::focusState()->surface()); const auto CONSTRAINT = SURF ? SURF->constraint() : nullptr; return CONSTRAINT && CONSTRAINT->isLocked(); @@ -1712,7 +1802,7 @@ void CInputManager::newTouchDevice(SP pDevice) { try { PNEWDEV->m_hlName = getNameForNewDevice(PNEWDEV->m_deviceName); } catch (std::exception& e) { - Debug::log(ERR, "Touch Device had no name???"); // logic error + Log::logger->log(Log::ERR, "Touch Device had no name???"); // logic error } setTouchDeviceConfigs(PNEWDEV); @@ -1727,7 +1817,7 @@ void CInputManager::newTouchDevice(SP pDevice) { destroyTouchDevice(PDEV); }); - Debug::log(LOG, "New touch device added at {:x}", rc(PNEWDEV.get())); + Log::logger->log(Log::DEBUG, "New touch device added at {:x}", rc(PNEWDEV.get())); } void CInputManager::setTouchDeviceConfigs(SP dev) { @@ -1735,20 +1825,20 @@ void CInputManager::setTouchDeviceConfigs(SP dev) { if (PTOUCHDEV->aq() && PTOUCHDEV->aq()->getLibinputHandle()) { const auto LIBINPUTDEV = PTOUCHDEV->aq()->getLibinputHandle(); - const auto ENABLED = g_pConfigManager->getDeviceInt(PTOUCHDEV->m_hlName, "enabled", "input:touchdevice:enabled"); + const auto ENABLED = Config::mgr()->getDeviceInt(PTOUCHDEV->m_hlName, "enabled", "input:touchdevice:enabled"); const auto mode = ENABLED ? LIBINPUT_CONFIG_SEND_EVENTS_ENABLED : LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; if (libinput_device_config_send_events_get_mode(LIBINPUTDEV) != mode) libinput_device_config_send_events_set_mode(LIBINPUTDEV, mode); if (libinput_device_config_calibration_has_matrix(LIBINPUTDEV)) { - Debug::log(LOG, "Setting calibration matrix for device {}", PTOUCHDEV->m_hlName); + Log::logger->log(Log::DEBUG, "Setting calibration matrix for device {}", PTOUCHDEV->m_hlName); // default value of transform being -1 means it's unset. - const int ROTATION = std::clamp(g_pConfigManager->getDeviceInt(PTOUCHDEV->m_hlName, "transform", "input:touchdevice:transform"), -1, 7); + const int ROTATION = std::clamp(Config::mgr()->getDeviceInt(PTOUCHDEV->m_hlName, "transform", "input:touchdevice:transform"), -1, 7); if (ROTATION > -1) libinput_device_config_calibration_set_matrix(LIBINPUTDEV, MATRICES[ROTATION]); } - auto output = g_pConfigManager->getDeviceString(PTOUCHDEV->m_hlName, "output", "input:touchdevice:output"); + auto output = Config::mgr()->getDeviceString(PTOUCHDEV->m_hlName, "output", "input:touchdevice:output"); bool bound = !output.empty() && output != STRVAL_EMPTY; const bool AUTODETECT = output == "[[Auto]]"; if (!bound && AUTODETECT) { @@ -1762,10 +1852,10 @@ void CInputManager::setTouchDeviceConfigs(SP dev) { PTOUCHDEV->m_boundOutput = bound ? output : ""; const auto PMONITOR = bound ? g_pCompositor->getMonitorFromName(output) : nullptr; if (PMONITOR) { - Debug::log(LOG, "Binding touch device {} to output {}", PTOUCHDEV->m_hlName, PMONITOR->m_name); + Log::logger->log(Log::DEBUG, "Binding touch device {} to output {}", PTOUCHDEV->m_hlName, PMONITOR->m_name); // wlr_cursor_map_input_to_output(g_pCompositor->m_sWLRCursor, &PTOUCHDEV->wlr()->base, PMONITOR->output); } else if (bound) - Debug::log(ERR, "Failed to bind touch device {} to output '{}': monitor not found", PTOUCHDEV->m_hlName, output); + Log::logger->log(Log::ERR, "Failed to bind touch device {} to output '{}': monitor not found", PTOUCHDEV->m_hlName, output); } }; @@ -1785,35 +1875,35 @@ void CInputManager::setTabletConfigs() { const auto NAME = t->m_hlName; const auto LIBINPUTDEV = t->aq()->getLibinputHandle(); - const auto RELINPUT = g_pConfigManager->getDeviceInt(NAME, "relative_input", "input:tablet:relative_input"); + const auto RELINPUT = Config::mgr()->getDeviceInt(NAME, "relative_input", "input:tablet:relative_input"); t->m_relativeInput = RELINPUT; - const int ROTATION = std::clamp(g_pConfigManager->getDeviceInt(NAME, "transform", "input:tablet:transform"), -1, 7); - Debug::log(LOG, "Setting calibration matrix for device {}", NAME); + const int ROTATION = std::clamp(Config::mgr()->getDeviceInt(NAME, "transform", "input:tablet:transform"), -1, 7); + Log::logger->log(Log::DEBUG, "Setting calibration matrix for device {}", NAME); if (ROTATION > -1) libinput_device_config_calibration_set_matrix(LIBINPUTDEV, MATRICES[ROTATION]); - if (g_pConfigManager->getDeviceInt(NAME, "left_handed", "input:tablet:left_handed") == 0) + if (Config::mgr()->getDeviceInt(NAME, "left_handed", "input:tablet:left_handed") == 0) libinput_device_config_left_handed_set(LIBINPUTDEV, 0); else libinput_device_config_left_handed_set(LIBINPUTDEV, 1); - const auto OUTPUT = g_pConfigManager->getDeviceString(NAME, "output", "input:tablet:output"); + const auto OUTPUT = Config::mgr()->getDeviceString(NAME, "output", "input:tablet:output"); if (OUTPUT != STRVAL_EMPTY) { - Debug::log(LOG, "Binding tablet {} to output {}", NAME, OUTPUT); + Log::logger->log(Log::DEBUG, "Binding tablet {} to output {}", NAME, OUTPUT); t->m_boundOutput = OUTPUT; } else t->m_boundOutput = ""; - const auto REGION_POS = g_pConfigManager->getDeviceVec(NAME, "region_position", "input:tablet:region_position"); - const auto REGION_SIZE = g_pConfigManager->getDeviceVec(NAME, "region_size", "input:tablet:region_size"); + const auto REGION_POS = Config::mgr()->getDeviceVec(NAME, "region_position", "input:tablet:region_position"); + const auto REGION_SIZE = Config::mgr()->getDeviceVec(NAME, "region_size", "input:tablet:region_size"); t->m_boundBox = {REGION_POS, REGION_SIZE}; - const auto ABSOLUTE_REGION_POS = g_pConfigManager->getDeviceInt(NAME, "absolute_region_position", "input:tablet:absolute_region_position"); + const auto ABSOLUTE_REGION_POS = Config::mgr()->getDeviceInt(NAME, "absolute_region_position", "input:tablet:absolute_region_position"); t->m_absolutePos = ABSOLUTE_REGION_POS; - const auto ACTIVE_AREA_SIZE = g_pConfigManager->getDeviceVec(NAME, "active_area_size", "input:tablet:active_area_size"); - const auto ACTIVE_AREA_POS = g_pConfigManager->getDeviceVec(NAME, "active_area_position", "input:tablet:active_area_position"); + const auto ACTIVE_AREA_SIZE = Config::mgr()->getDeviceVec(NAME, "active_area_size", "input:tablet:active_area_size"); + const auto ACTIVE_AREA_POS = Config::mgr()->getDeviceVec(NAME, "active_area_position", "input:tablet:active_area_position"); if (ACTIVE_AREA_SIZE.x != 0 || ACTIVE_AREA_SIZE.y != 0) { // Rotations with an odd index (90 and 270 degrees, and their flipped variants) swap the X and Y axes. // Use swapped dimensions when the axes are rotated, otherwise keep the original ones. @@ -1831,22 +1921,22 @@ void CInputManager::newSwitch(SP pDevice) { const auto PNEWDEV = &m_switches.emplace_back(); PNEWDEV->pDevice = pDevice; - Debug::log(LOG, "New switch with name \"{}\" added", pDevice->getName()); + Log::logger->log(Log::DEBUG, "New switch with name \"{}\" added", pDevice->getName()); PNEWDEV->listeners.destroy = pDevice->events.destroy.listen([this, PNEWDEV] { destroySwitch(PNEWDEV); }); PNEWDEV->listeners.fire = pDevice->events.fire.listen([PNEWDEV](const Aquamarine::ISwitch::SFireEvent& event) { const auto NAME = PNEWDEV->pDevice->getName(); - Debug::log(LOG, "Switch {} fired, triggering binds.", NAME); + Log::logger->log(Log::DEBUG, "Switch {} fired, triggering binds.", NAME); g_pKeybindManager->onSwitchEvent(NAME); if (event.enable) { - Debug::log(LOG, "Switch {} turn on, triggering binds.", NAME); + Log::logger->log(Log::DEBUG, "Switch {} turn on, triggering binds.", NAME); g_pKeybindManager->onSwitchOnEvent(NAME); } else { - Debug::log(LOG, "Switch {} turn off, triggering binds.", NAME); + Log::logger->log(Log::DEBUG, "Switch {} turn off, triggering binds.", NAME); g_pKeybindManager->onSwitchOffEvent(NAME); } }); @@ -1903,7 +1993,7 @@ void CInputManager::setCursorIconOnBorder(PHLWINDOW w) { if (w->hasPopupAt(mouseCoords)) direction = BORDERICON_NONE; - else if (!boxFullGrabInput.containsPoint(mouseCoords) || (!m_currentlyHeldButtons.empty() && m_currentlyDraggedWindow.expired())) + else if (!boxFullGrabInput.containsPoint(mouseCoords) || (!m_currentlyHeldButtons.empty() && !g_layoutManager->dragController()->target())) direction = BORDERICON_NONE; else { @@ -1989,7 +2079,10 @@ void CInputManager::recheckMouseWarpOnMouseInput() { } void CInputManager::onSwipeBegin(IPointer::SSwipeBeginEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("swipeBegin", e); + Event::SCallbackInfo info; + Event::bus()->m_events.gesture.swipe.begin.emit(e, info); + if (info.cancelled) + return; g_pTrackpadGestures->gestureBegin(e); @@ -1997,7 +2090,10 @@ void CInputManager::onSwipeBegin(IPointer::SSwipeBeginEvent e) { } void CInputManager::onSwipeUpdate(IPointer::SSwipeUpdateEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("swipeUpdate", e); + Event::SCallbackInfo info; + Event::bus()->m_events.gesture.swipe.update.emit(e, info); + if (info.cancelled) + return; g_pTrackpadGestures->gestureUpdate(e); @@ -2005,7 +2101,10 @@ void CInputManager::onSwipeUpdate(IPointer::SSwipeUpdateEvent e) { } void CInputManager::onSwipeEnd(IPointer::SSwipeEndEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("swipeEnd", e); + Event::SCallbackInfo info; + Event::bus()->m_events.gesture.swipe.end.emit(e, info); + if (info.cancelled) + return; g_pTrackpadGestures->gestureEnd(e); @@ -2013,7 +2112,10 @@ void CInputManager::onSwipeEnd(IPointer::SSwipeEndEvent e) { } void CInputManager::onPinchBegin(IPointer::SPinchBeginEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("pinchBegin", e); + Event::SCallbackInfo info; + Event::bus()->m_events.gesture.pinch.begin.emit(e, info); + if (info.cancelled) + return; g_pTrackpadGestures->gestureBegin(e); @@ -2021,7 +2123,10 @@ void CInputManager::onPinchBegin(IPointer::SPinchBeginEvent e) { } void CInputManager::onPinchUpdate(IPointer::SPinchUpdateEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("pinchUpdate", e); + Event::SCallbackInfo info; + Event::bus()->m_events.gesture.pinch.update.emit(e, info); + if (info.cancelled) + return; g_pTrackpadGestures->gestureUpdate(e); @@ -2029,7 +2134,10 @@ void CInputManager::onPinchUpdate(IPointer::SPinchUpdateEvent e) { } void CInputManager::onPinchEnd(IPointer::SPinchEndEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("pinchEnd", e); + Event::SCallbackInfo info; + Event::bus()->m_events.gesture.pinch.end.emit(e, info); + if (info.cancelled) + return; g_pTrackpadGestures->gestureEnd(e); diff --git a/src/managers/input/InputManager.hpp b/src/managers/input/InputManager.hpp index 9fbf68b45..0727b7b1f 100644 --- a/src/managers/input/InputManager.hpp +++ b/src/managers/input/InputManager.hpp @@ -7,6 +7,7 @@ #include "../../helpers/time/Timer.hpp" #include "InputMethodRelay.hpp" #include "../../helpers/signal/Signal.hpp" +#include "../../desktop/view/WLSurface.hpp" #include "../../devices/IPointer.hpp" #include "../../devices/ITouch.hpp" #include "../../devices/IKeyboard.hpp" @@ -15,7 +16,6 @@ #include "../SeatManager.hpp" class CPointerConstraint; -class CWindow; class CIdleInhibitor; class CVirtualKeyboardV1Resource; class CVirtualPointerV1Resource; @@ -89,8 +89,9 @@ class CInputManager { void onMouseMoved(IPointer::SMotionEvent); void onMouseWarp(IPointer::SMotionAbsoluteEvent); - void onMouseButton(IPointer::SButtonEvent); + void onMouseButton(IPointer::SButtonEvent, SP); void onMouseWheel(IPointer::SAxisEvent, SP pointer = nullptr); + void onPointerFrame(); void onKeyboardKey(const IKeyboard::SKeyEvent&, SP); void onKeyboardMod(SP); @@ -153,12 +154,6 @@ class CInputManager { STouchData m_touchData; - // for dragging floating windows - PHLWINDOWREF m_currentlyDraggedWindow; - eMouseBindMode m_dragMode = MBIND_INVALID; - bool m_wasDraggingWindow = false; - bool m_dragThresholdReached = false; - // for refocus to be forced PHLWINDOWREF m_forcedFocus; @@ -208,6 +203,9 @@ class CInputManager { // for hiding cursor on touch bool m_lastInputTouch = false; + // for hiding cursor on tablet + bool m_lastInputTablet = false; + // for tracking mouse refocus PHLWINDOWREF m_lastMouseFocus; @@ -235,7 +233,7 @@ class CInputManager { void setupKeyboard(SP keeb); void setupMouse(SP mauz); - void processMouseDownNormal(const IPointer::SButtonEvent& e); + void processMouseDownNormal(const IPointer::SButtonEvent& e, SP); void processMouseDownKill(const IPointer::SButtonEvent& e); bool cursorImageUnlocked(); @@ -281,10 +279,10 @@ class CInputManager { // cursor surface struct { - bool hidden = false; // null surface = hidden - SP wlSurface; - Vector2D vHotspot; - std::string name; // if not empty, means set by name. + bool hidden = false; // null surface = hidden + SP wlSurface; + Vector2D vHotspot; + std::string name; // if not empty, means set by name. } m_cursorSurfaceInfo; void restoreCursorIconToApp(); // no-op if restored @@ -296,6 +294,7 @@ class CInputManager { uint32_t lastEventTime = 0; uint32_t accumulatedScroll = 0; } m_scrollWheelState; + bool m_pointerAxisFramePending = false; bool shareKeyFromAllKBs(uint32_t key, bool pressed); uint32_t shareModsFromAllKBs(uint32_t depressed); @@ -303,7 +302,7 @@ class CInputManager { uint32_t m_lastMods = 0; friend class CKeybindManager; - friend class CWLSurface; + friend class Desktop::View::CWLSurface; friend class CWorkspaceSwipeGesture; }; diff --git a/src/managers/input/InputMethodPopup.cpp b/src/managers/input/InputMethodPopup.cpp index 3c4731b53..9a8912135 100644 --- a/src/managers/input/InputMethodPopup.cpp +++ b/src/managers/input/InputMethodPopup.cpp @@ -12,17 +12,17 @@ CInputPopup::CInputPopup(SP popup_) : m_popup(popup_) { m_listeners.map = popup_->m_events.map.listen([this] { onMap(); }); m_listeners.unmap = popup_->m_events.unmap.listen([this] { onUnmap(); }); m_listeners.destroy = popup_->m_events.destroy.listen([this] { onDestroy(); }); - m_surface = CWLSurface::create(); + m_surface = Desktop::View::CWLSurface::create(); m_surface->assign(popup_->surface()); } -SP CInputPopup::queryOwner() { +SP CInputPopup::queryOwner() { const auto FOCUSED = g_pInputManager->m_relay.getFocusedTextInput(); if (!FOCUSED) return nullptr; - return CWLSurface::fromResource(FOCUSED->focusedSurface()); + return Desktop::View::CWLSurface::fromResource(FOCUSED->focusedSurface()); } void CInputPopup::onDestroy() { @@ -30,7 +30,7 @@ void CInputPopup::onDestroy() { } void CInputPopup::onMap() { - Debug::log(LOG, "Mapped an IME Popup"); + Log::logger->log(Log::DEBUG, "Mapped an IME Popup"); updateBox(); damageEntire(); @@ -44,7 +44,7 @@ void CInputPopup::onMap() { } void CInputPopup::onUnmap() { - Debug::log(LOG, "Unmapped an IME Popup"); + Log::logger->log(Log::DEBUG, "Unmapped an IME Popup"); damageEntire(); } @@ -57,7 +57,7 @@ void CInputPopup::damageEntire() { const auto OWNER = queryOwner(); if (!OWNER) { - Debug::log(ERR, "BUG THIS: No owner in imepopup::damageentire"); + Log::logger->log(Log::ERR, "BUG THIS: No owner in imepopup::damageentire"); return; } CBox box = globalBox(); @@ -68,7 +68,7 @@ void CInputPopup::damageSurface() { const auto OWNER = queryOwner(); if (!OWNER) { - Debug::log(ERR, "BUG THIS: No owner in imepopup::damagesurface"); + Log::logger->log(Log::ERR, "BUG THIS: No owner in imepopup::damagesurface"); return; } @@ -150,7 +150,7 @@ CBox CInputPopup::globalBox() { const auto OWNER = queryOwner(); if (!OWNER) { - Debug::log(ERR, "BUG THIS: No owner in imepopup::globalbox"); + Log::logger->log(Log::ERR, "BUG THIS: No owner in imepopup::globalbox"); return {}; } CBox parentBox = OWNER->getSurfaceBoxGlobal().value_or(CBox{0, 0, 500, 500}); diff --git a/src/managers/input/InputMethodPopup.hpp b/src/managers/input/InputMethodPopup.hpp index 20767963d..a7014973f 100644 --- a/src/managers/input/InputMethodPopup.hpp +++ b/src/managers/input/InputMethodPopup.hpp @@ -1,6 +1,6 @@ #pragma once -#include "../../desktop/WLSurface.hpp" +#include "../../desktop/view/WLSurface.hpp" #include "../../macros.hpp" #include "../../helpers/math/Math.hpp" #include "../../helpers/signal/Signal.hpp" @@ -22,17 +22,17 @@ class CInputPopup { void onCommit(); private: - SP queryOwner(); - void updateBox(); + SP queryOwner(); + void updateBox(); - void onDestroy(); - void onMap(); - void onUnmap(); + void onDestroy(); + void onMap(); + void onUnmap(); - WP m_popup; - SP m_surface; - CBox m_lastBoxLocal; - MONITORID m_lastMonitor = MONITOR_INVALID; + WP m_popup; + SP m_surface; + CBox m_lastBoxLocal; + MONITORID m_lastMonitor = MONITOR_INVALID; struct { CHyprSignalListener map; diff --git a/src/managers/input/InputMethodRelay.cpp b/src/managers/input/InputMethodRelay.cpp index 0b4344106..27fd80b61 100644 --- a/src/managers/input/InputMethodRelay.cpp +++ b/src/managers/input/InputMethodRelay.cpp @@ -1,14 +1,13 @@ #include "InputMethodRelay.hpp" #include "../../desktop/state/FocusState.hpp" +#include "../../event/EventBus.hpp" #include "../../protocols/TextInputV3.hpp" #include "../../protocols/TextInputV1.hpp" #include "../../protocols/InputMethodV2.hpp" #include "../../protocols/core/Compositor.hpp" -#include "../../managers/HookSystemManager.hpp" CInputMethodRelay::CInputMethodRelay() { - static auto P = - g_pHookSystem->hookDynamic("keyboardFocus", [&](void* self, SCallbackInfo& info, std::any param) { onKeyboardFocus(std::any_cast>(param)); }); + static auto P = Event::bus()->m_events.input.keyboard.focus.listen([&](SP surf) { onKeyboardFocus(surf); }); m_listeners.newTIV3 = PROTO::textInputV3->m_events.newTextInput.listen([this](const auto& input) { onNewTextInput(input); }); m_listeners.newTIV1 = PROTO::textInputV1->m_events.newTextInput.listen([this](const auto& input) { onNewTextInput(input); }); @@ -17,7 +16,7 @@ CInputMethodRelay::CInputMethodRelay() { void CInputMethodRelay::onNewIME(SP pIME) { if (!m_inputMethod.expired()) { - Debug::log(ERR, "Cannot register 2 IMEs at once!"); + Log::logger->log(Log::ERR, "Cannot register 2 IMEs at once!"); pIME->unavailable(); @@ -30,7 +29,7 @@ void CInputMethodRelay::onNewIME(SP pIME) { const auto PTI = getFocusedTextInput(); if (!PTI) { - Debug::log(LOG, "No focused TextInput on IME Commit"); + Log::logger->log(Log::DEBUG, "No focused TextInput on IME Commit"); return; } @@ -40,7 +39,7 @@ void CInputMethodRelay::onNewIME(SP pIME) { m_listeners.destroyIME = pIME->m_events.destroy.listen([this] { const auto PTI = getFocusedTextInput(); - Debug::log(LOG, "IME Destroy"); + Log::logger->log(Log::DEBUG, "IME Destroy"); if (PTI) PTI->leave(); @@ -50,7 +49,7 @@ void CInputMethodRelay::onNewIME(SP pIME) { m_listeners.newPopup = pIME->m_events.newPopup.listen([this](const SP& popup) { m_inputMethodPopups.emplace_back(makeUnique(popup)); - Debug::log(LOG, "New input popup"); + Log::logger->log(Log::DEBUG, "New input popup"); }); if (!Desktop::focusState()->surface()) @@ -75,6 +74,11 @@ CTextInput* CInputMethodRelay::getFocusedTextInput() { if (!Desktop::focusState()->surface()) return nullptr; + for (auto const& ti : m_textInputs) { + if (ti->focusedSurface() == Desktop::focusState()->surface() && ti->isEnabled()) + return ti.get(); + } + for (auto const& ti : m_textInputs) { if (ti->focusedSurface() == Desktop::focusState()->surface()) return ti.get(); diff --git a/src/managers/input/InputMethodRelay.hpp b/src/managers/input/InputMethodRelay.hpp index cb631b12d..0f66fae83 100644 --- a/src/managers/input/InputMethodRelay.hpp +++ b/src/managers/input/InputMethodRelay.hpp @@ -9,7 +9,9 @@ #include class CInputManager; -class CHyprRenderer; +namespace Render { + class IHyprRenderer; +} class CTextInputV1; class CInputMethodV2; @@ -54,9 +56,8 @@ class CInputMethodRelay { CHyprSignalListener newPopup; } m_listeners; - friend class CHyprRenderer; + friend class Render::IHyprRenderer; friend class CInputManager; friend class CTextInputV1ProtocolManager; friend class CTextInput; - friend class CHyprRenderer; }; diff --git a/src/managers/input/Tablets.cpp b/src/managers/input/Tablets.cpp index 7a9c359ae..a2fec15c2 100644 --- a/src/managers/input/Tablets.cpp +++ b/src/managers/input/Tablets.cpp @@ -1,12 +1,12 @@ #include "InputManager.hpp" -#include "../../desktop/Window.hpp" +#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()) @@ -38,7 +38,7 @@ static void focusTool(SP tool, SP tablet, SP tab, SP tool, bool motion = false) { - const auto LASTHLSURFACE = CWLSurface::fromResource(g_pSeatManager->m_state.pointerFocus.lock()); + const auto LASTHLSURFACE = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.pointerFocus.lock()); if (!LASTHLSURFACE || !tool->m_active) { if (tool->getSurface()) @@ -63,6 +63,8 @@ static void refocusTablet(SP tab, SP tool, bool motion = f if (!motion) return; + const auto WINDOW = Desktop::View::CWindow::fromView(LASTHLSURFACE->view()); + if (LASTHLSURFACE->constraint() && tool->aq()->type != Aquamarine::ITabletTool::AQ_TABLET_TOOL_TYPE_MOUSE) { // cursor logic will completely break here as the cursor will be locked. // let's just "map" the desired position to the constraint area. @@ -70,13 +72,13 @@ static void refocusTablet(SP tab, SP tool, bool motion = f Vector2D local; // yes, this technically ignores any regions set by the app. Too bad! - if (LASTHLSURFACE->getWindow()) - local = tool->m_absolutePos * LASTHLSURFACE->getWindow()->m_realSize->goal(); + if (WINDOW) + local = tool->m_absolutePos * WINDOW->m_realSize->goal(); else local = tool->m_absolutePos * BOX->size(); - if (LASTHLSURFACE->getWindow() && LASTHLSURFACE->getWindow()->m_isX11) - local = local * LASTHLSURFACE->getWindow()->m_X11SurfaceScaledBy; + if (WINDOW && WINDOW->m_isX11) + local = local * WINDOW->m_X11SurfaceScaledBy; PROTO::tablet->motion(tool, local); return; @@ -84,8 +86,8 @@ static void refocusTablet(SP tab, SP tool, bool motion = f auto local = CURSORPOS - BOX->pos(); - if (LASTHLSURFACE->getWindow() && LASTHLSURFACE->getWindow()->m_isX11) - local = local * LASTHLSURFACE->getWindow()->m_X11SurfaceScaledBy; + if (WINDOW && WINDOW->m_isX11) + local = local * WINDOW->m_X11SurfaceScaledBy; PROTO::tablet->motion(tool, local); } @@ -105,6 +107,11 @@ static Vector2D transformToActiveRegion(const Vector2D pos, const CBox activeAre } void CInputManager::onTabletAxis(CTablet::SAxisEvent e) { + Event::SCallbackInfo info; + Event::bus()->m_events.input.tablet.axis.emit(e, info); + if (info.cancelled) + return; + const auto PTAB = e.tablet; const auto PTOOL = ensureTabletToolPresent(e.tool); @@ -169,7 +176,10 @@ void CInputManager::onTabletAxis(CTablet::SAxisEvent e) { } void CInputManager::onTabletTip(CTablet::STipEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("tabletTip", e); + Event::SCallbackInfo info; + Event::bus()->m_events.input.tablet.tip.emit(e, info); + if (info.cancelled) + return; const auto PTAB = e.tablet; const auto PTOOL = ensureTabletToolPresent(e.tool); @@ -194,6 +204,11 @@ void CInputManager::onTabletTip(CTablet::STipEvent e) { } void CInputManager::onTabletButton(CTablet::SButtonEvent e) { + Event::SCallbackInfo info; + Event::bus()->m_events.input.tablet.button.emit(e, info); + if (info.cancelled) + return; + const auto PTOOL = ensureTabletToolPresent(e.tool); if (e.down) @@ -208,15 +223,22 @@ 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); PTOOL->m_active = e.in; if (!e.in) { + m_lastInputTablet = false; if (PTOOL->getSurface()) unfocusTool(PTOOL); } else { + m_lastInputTablet = true; simulateMouseMovement(); refocusTablet(PTAB, PTOOL); } @@ -229,7 +251,7 @@ void CInputManager::newTablet(SP pDevice) { try { PNEWTABLET->m_hlName = g_pInputManager->getNameForNewDevice(pDevice->getName()); } catch (std::exception& e) { - Debug::log(ERR, "Tablet had no name???"); // logic error + Log::logger->log(Log::ERR, "Tablet had no name???"); // logic error } g_pPointerManager->attachTablet(PNEWTABLET); @@ -255,7 +277,7 @@ SP CInputManager::ensureTabletToolPresent(SPm_hlName = g_pInputManager->getNameForNewDevice(pTool->getName()); } catch (std::exception& e) { - Debug::log(ERR, "Tablet had no name???"); // logic error + Log::logger->log(Log::ERR, "Tablet had no name???"); // logic error } PTOOL->m_events.destroy.listenStatic([this, tool = PTOOL.get()] { @@ -273,7 +295,7 @@ void CInputManager::newTabletPad(SP pDevice) { try { PNEWPAD->m_hlName = g_pInputManager->getNameForNewDevice(pDevice->getName()); } catch (std::exception& e) { - Debug::log(ERR, "Pad had no name???"); // logic error + Log::logger->log(Log::ERR, "Pad had no name???"); // logic error } PNEWPAD->m_events.destroy.listenStatic([this, pad = PNEWPAD.get()] { diff --git a/src/managers/input/TextInput.cpp b/src/managers/input/TextInput.cpp index a2b37bb6e..be9a5d291 100644 --- a/src/managers/input/TextInput.cpp +++ b/src/managers/input/TextInput.cpp @@ -22,13 +22,7 @@ void CTextInput::initCallbacks() { m_listeners.disable = INPUT->m_events.disable.listen([this] { onDisabled(); }); m_listeners.commit = INPUT->m_events.onCommit.listen([this] { onCommit(); }); m_listeners.reset = INPUT->m_events.reset.listen([this] { onReset(); }); - m_listeners.destroy = INPUT->m_events.destroy.listen([this] { - m_listeners.surfaceUnmap.reset(); - m_listeners.surfaceDestroy.reset(); - g_pInputManager->m_relay.removeTextInput(this); - if (!g_pInputManager->m_relay.getFocusedTextInput()) - g_pInputManager->m_relay.deactivateIME(this); - }); + m_listeners.destroy = INPUT->m_events.destroy.listen([this] { destroy(); }); if (Desktop::focusState()->surface() && Desktop::focusState()->surface()->client() == INPUT->client()) enter(Desktop::focusState()->surface()); @@ -39,21 +33,25 @@ void CTextInput::initCallbacks() { m_listeners.disable = INPUT->m_events.disable.listen([this] { onDisabled(); }); m_listeners.commit = INPUT->m_events.onCommit.listen([this] { onCommit(); }); m_listeners.reset = INPUT->m_events.reset.listen([this] { onReset(); }); - m_listeners.destroy = INPUT->m_events.destroy.listen([this] { - m_listeners.surfaceUnmap.reset(); - m_listeners.surfaceDestroy.reset(); - g_pInputManager->m_relay.removeTextInput(this); - if (!g_pInputManager->m_relay.getFocusedTextInput()) - g_pInputManager->m_relay.deactivateIME(this); - }); + m_listeners.destroy = INPUT->m_events.destroy.listen([this] { destroy(); }); } } +void CTextInput::destroy() { + m_listeners.surfaceUnmap.reset(); + m_listeners.surfaceDestroy.reset(); + + g_pInputManager->m_relay.removeTextInput(this); + + if (!g_pInputManager->m_relay.getFocusedTextInput()) + g_pInputManager->m_relay.deactivateIME(nullptr, false); +} + void CTextInput::onEnabled(SP surfV1) { - Debug::log(LOG, "TI ENABLE"); + Log::logger->log(Log::DEBUG, "TI ENABLE"); if (g_pInputManager->m_relay.m_inputMethod.expired()) { - // Debug::log(WARN, "Enabling TextInput on no IME!"); + // Log::logger->log(Log::WARN, "Enabling TextInput on no IME!"); return; } @@ -70,7 +68,7 @@ void CTextInput::onEnabled(SP surfV1) { void CTextInput::onDisabled() { if (g_pInputManager->m_relay.m_inputMethod.expired()) { - // Debug::log(WARN, "Disabling TextInput on no IME!"); + // Log::logger->log(Log::WARN, "Disabling TextInput on no IME!"); return; } @@ -107,12 +105,12 @@ void CTextInput::onReset() { void CTextInput::onCommit() { if (g_pInputManager->m_relay.m_inputMethod.expired()) { - // Debug::log(WARN, "Committing TextInput on no IME!"); + // Log::logger->log(Log::WARN, "Committing TextInput on no IME!"); return; } if (!(isV3() ? m_v3Input->m_current.enabled.value : m_v1Input->m_active)) { - Debug::log(WARN, "Disabled TextInput commit?"); + Log::logger->log(Log::WARN, "Disabled TextInput commit?"); return; } @@ -132,7 +130,7 @@ void CTextInput::setFocusedSurface(SP pSurface) { m_listeners.surfaceDestroy.reset(); m_listeners.surfaceUnmap = pSurface->m_events.unmap.listen([this] { - Debug::log(LOG, "Unmap TI owner1"); + Log::logger->log(Log::DEBUG, "Unmap TI owner1"); if (m_enterLocks) m_enterLocks--; @@ -152,7 +150,7 @@ void CTextInput::setFocusedSurface(SP pSurface) { }); m_listeners.surfaceDestroy = pSurface->m_events.destroy.listen([this] { - Debug::log(LOG, "Destroy TI owner1"); + Log::logger->log(Log::DEBUG, "Destroy TI owner1"); if (m_enterLocks) m_enterLocks--; @@ -188,7 +186,7 @@ void CTextInput::enter(SP pSurface) { m_enterLocks++; if (m_enterLocks != 1) { - Debug::log(ERR, "BUG THIS: TextInput has != 1 locks in enter"); + Log::logger->log(Log::ERR, "BUG THIS: TextInput has != 1 locks in enter"); leave(); m_enterLocks = 1; } @@ -208,7 +206,7 @@ void CTextInput::leave() { m_enterLocks--; if (m_enterLocks != 0) { - Debug::log(ERR, "BUG THIS: TextInput has != 0 locks in leave"); + Log::logger->log(Log::ERR, "BUG THIS: TextInput has != 0 locks in leave"); m_enterLocks = 0; } @@ -305,3 +303,7 @@ bool CTextInput::hasCursorRectangle() { CBox CTextInput::cursorBox() { return CBox{isV3() ? m_v3Input->m_current.box.cursorBox : m_v1Input->m_cursorRectangle}; } + +bool CTextInput::isEnabled() { + return isV3() ? m_v3Input->m_current.enabled.value : true; +} diff --git a/src/managers/input/TextInput.hpp b/src/managers/input/TextInput.hpp index fd24dbfa4..798f31e9c 100644 --- a/src/managers/input/TextInput.hpp +++ b/src/managers/input/TextInput.hpp @@ -29,6 +29,7 @@ class CTextInput { void onCommit(); void onReset(); + bool isEnabled(); bool hasCursorRectangle(); CBox cursorBox(); @@ -38,6 +39,8 @@ class CTextInput { void setFocusedSurface(SP pSurface); void initCallbacks(); + void destroy(); + WP m_focusedSurface; int m_enterLocks = 0; WP m_v3Input; diff --git a/src/managers/input/Touch.cpp b/src/managers/input/Touch.cpp index e008b50c4..942c168cb 100644 --- a/src/managers/input/Touch.cpp +++ b/src/managers/input/Touch.cpp @@ -2,14 +2,14 @@ #include "../SessionLockManager.hpp" #include "../../protocols/SessionLock.hpp" #include "../../Compositor.hpp" -#include "../../desktop/LayerSurface.hpp" +#include "../../desktop/view/LayerSurface.hpp" #include "../../desktop/state/FocusState.hpp" #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.hpp" +#include "debug/log/Logger.hpp" #include "UnifiedWorkspaceSwipeGesture.hpp" void CInputManager::onTouchDown(ITouch::SDownEvent e) { @@ -17,12 +17,16 @@ void CInputManager::onTouchDown(ITouch::SDownEvent e) { static auto PSWIPETOUCH = CConfigValue("gestures:workspace_swipe_touch"); static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); - auto* const PGAPSOUT = sc((PGAPSOUTDATA.ptr())->getData()); + auto* const PGAPSOUT = sc((PGAPSOUTDATA.ptr())->getData()); // TODO: WORKSPACERULE.gapsOut.value_or() - auto gapsOut = *PGAPSOUT; - static auto PBORDERSIZE = CConfigValue("general:border_size"); - static auto PSWIPEINVR = CConfigValue("gestures:workspace_swipe_touch_invert"); - EMIT_HOOK_EVENT_CANCELLABLE("touchDown", e); + auto gapsOut = *PGAPSOUT; + static auto PBORDERSIZE = CConfigValue("general:border_size"); + static auto PSWIPEINVR = CConfigValue("gestures:workspace_swipe_touch_invert"); + + Event::SCallbackInfo info; + Event::bus()->m_events.input.touch.down.emit(e, info); + if (info.cancelled) + return; auto PMONITOR = g_pCompositor->getMonitorFromName(!e.device->m_boundOutput.empty() ? e.device->m_boundOutput : ""); @@ -32,6 +36,9 @@ void CInputManager::onTouchDown(ITouch::SDownEvent e) { refocus(TOUCH_COORDS); + if (PMONITOR != Desktop::focusState()->monitor()) + Desktop::focusState()->rawMonitorFocus(PMONITOR); + if (m_clickBehavior == CLICKMODE_KILL) { IPointer::SButtonEvent e; e.state = WL_POINTER_BUTTON_STATE_PRESSED; @@ -66,7 +73,7 @@ void CInputManager::onTouchDown(ITouch::SDownEvent e) { if (g_pSessionLockManager->isSessionLocked() && m_foundLSToFocus.expired()) { m_touchData.touchFocusLockSurface = g_pSessionLockManager->getSessionLockSurfaceForMonitor(PMONITOR->m_id); if (!m_touchData.touchFocusLockSurface) - Debug::log(WARN, "The session is locked but can't find a lock surface"); + Log::logger->log(Log::WARN, "The session is locked but can't find a lock surface"); else m_touchData.touchFocusSurface = m_touchData.touchFocusLockSurface->surface->surface(); } else { @@ -109,7 +116,11 @@ void CInputManager::onTouchDown(ITouch::SDownEvent e) { void CInputManager::onTouchUp(ITouch::SUpEvent e) { m_lastInputTouch = true; - EMIT_HOOK_EVENT_CANCELLABLE("touchUp", e); + Event::SCallbackInfo info; + Event::bus()->m_events.input.touch.up.emit(e, info); + if (info.cancelled) + return; + if (g_pUnifiedWorkspaceSwipe->isGestureInProgress()) { // If there was a swipe from this finger, end it. if (e.touchID == g_pUnifiedWorkspaceSwipe->m_touchID) @@ -126,7 +137,11 @@ void CInputManager::onTouchMove(ITouch::SMotionEvent e) { m_lastCursorMovement.reset(); - EMIT_HOOK_EVENT_CANCELLABLE("touchMove", e); + Event::SCallbackInfo info; + Event::bus()->m_events.input.touch.motion.emit(e, info); + if (info.cancelled) + return; + if (g_pUnifiedWorkspaceSwipe->isGestureInProgress()) { // Do nothing if this is using a different finger. if (e.touchID != g_pUnifiedWorkspaceSwipe->m_touchID) diff --git a/src/managers/input/UnifiedWorkspaceSwipeGesture.cpp b/src/managers/input/UnifiedWorkspaceSwipeGesture.cpp index 6dae1e634..c2c8a72ad 100644 --- a/src/managers/input/UnifiedWorkspaceSwipeGesture.cpp +++ b/src/managers/input/UnifiedWorkspaceSwipeGesture.cpp @@ -15,7 +15,7 @@ void CUnifiedWorkspaceSwipeGesture::begin() { const auto PWORKSPACE = Desktop::focusState()->monitor()->m_activeWorkspace; - Debug::log(LOG, "CUnifiedWorkspaceSwipeGesture::begin: Starting a swipe from {}", PWORKSPACE->m_name); + Log::logger->log(Log::DEBUG, "CUnifiedWorkspaceSwipeGesture::begin: Starting a swipe from {}", PWORKSPACE->m_name); m_workspaceBegin = PWORKSPACE; m_delta = 0; @@ -246,7 +246,6 @@ void CUnifiedWorkspaceSwipeGesture::end() { else { m_monitor->changeWorkspace(g_pCompositor->createNewWorkspace(workspaceIDLeft, m_monitor->m_id)); PWORKSPACEL = g_pCompositor->getWorkspaceByID(workspaceIDLeft); - PWORKSPACEL->rememberPrevWorkspace(m_workspaceBegin); } PWORKSPACEL->m_renderOffset->setValue(RENDEROFFSET); @@ -261,7 +260,7 @@ void CUnifiedWorkspaceSwipeGesture::end() { g_pInputManager->unconstrainMouse(); - Debug::log(LOG, "Ended swipe to the left"); + Log::logger->log(Log::DEBUG, "Ended swipe to the left"); pSwitchedTo = PWORKSPACEL; } else { @@ -273,7 +272,6 @@ void CUnifiedWorkspaceSwipeGesture::end() { else { m_monitor->changeWorkspace(g_pCompositor->createNewWorkspace(workspaceIDRight, m_monitor->m_id)); PWORKSPACER = g_pCompositor->getWorkspaceByID(workspaceIDRight); - PWORKSPACER->rememberPrevWorkspace(m_workspaceBegin); } PWORKSPACER->m_renderOffset->setValue(RENDEROFFSET); @@ -288,11 +286,10 @@ void CUnifiedWorkspaceSwipeGesture::end() { g_pInputManager->unconstrainMouse(); - Debug::log(LOG, "Ended swipe to the right"); + Log::logger->log(Log::DEBUG, "Ended swipe to the right"); pSwitchedTo = PWORKSPACER; } - m_workspaceBegin->rememberPrevWorkspace(pSwitchedTo); g_pHyprRenderer->damageMonitor(m_monitor.lock()); @@ -311,4 +308,4 @@ void CUnifiedWorkspaceSwipeGesture::end() { for (auto const& ls : Desktop::focusState()->monitor()->m_layerSurfaceLayers[2]) { *ls->m_alpha = pSwitchedTo->m_hasFullscreenWindow && pSwitchedTo->m_fullscreenMode == FSMODE_FULLSCREEN ? 0.f : 1.f; } -} \ No newline at end of file +} diff --git a/src/managers/input/trackpad/TrackpadGestures.cpp b/src/managers/input/trackpad/TrackpadGestures.cpp index 3595f5baa..e054c2f9f 100644 --- a/src/managers/input/trackpad/TrackpadGestures.cpp +++ b/src/managers/input/trackpad/TrackpadGestures.cpp @@ -1,6 +1,8 @@ #include "TrackpadGestures.hpp" #include "../InputManager.hpp" +#include "../../../config/ConfigValue.hpp" +#include "../../../protocols/ShortcutsInhibit.hpp" #include @@ -54,7 +56,7 @@ const char* CTrackpadGestures::stringForDir(eTrackpadGestureDirection dir) { } std::expected CTrackpadGestures::addGesture(UP&& gesture, size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, - float deltaScale) { + float deltaScale, bool disableInhibit) { for (const auto& g : m_gestures) { if (g->fingerCount != fingerCount) continue; @@ -84,14 +86,16 @@ std::expected CTrackpadGestures::addGesture(UP(std::move(gesture), fingerCount, modMask, direction, deltaScale)); + m_gestures.emplace_back(makeShared(std::move(gesture), fingerCount, modMask, direction, deltaScale, disableInhibit)); return {}; } -std::expected CTrackpadGestures::removeGesture(size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale) { - const auto IT = std::ranges::find_if( - m_gestures, [&](const auto& g) { return g->fingerCount == fingerCount && g->direction == direction && g->modMask == modMask && g->deltaScale == deltaScale; }); +std::expected CTrackpadGestures::removeGesture(size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale, + bool disableInhibit) { + const auto IT = std::ranges::find_if(m_gestures, [&](const auto& g) { + return g->fingerCount == fingerCount && g->direction == direction && g->modMask == modMask && g->deltaScale == deltaScale && g->disableInhibit == disableInhibit; + }); if (IT == m_gestures.end()) return std::unexpected("Can't remove a non-existent gesture"); @@ -103,7 +107,7 @@ std::expected CTrackpadGestures::removeGesture(size_t fingerC void CTrackpadGestures::gestureBegin(const IPointer::SSwipeBeginEvent& e) { if (m_activeGesture) { - Debug::log(ERR, "CTrackpadGestures::gestureBegin (swipe) but m_activeGesture is already present"); + Log::logger->log(Log::ERR, "CTrackpadGestures::gestureBegin (swipe) but m_activeGesture is already present"); return; } @@ -114,6 +118,8 @@ void CTrackpadGestures::gestureBegin(const IPointer::SSwipeBeginEvent& e) { } void CTrackpadGestures::gestureUpdate(const IPointer::SSwipeUpdateEvent& e) { + static auto PDISABLEINHIBIT = CConfigValue("binds:disable_keybind_grabbing"); + if (m_gestureFindFailed) return; @@ -121,7 +127,7 @@ void CTrackpadGestures::gestureUpdate(const IPointer::SSwipeUpdateEvent& e) { // 5 was chosen because I felt like that's a good number. if (!m_activeGesture && (std::abs(m_currentTotalDelta.x) < 5 && std::abs(m_currentTotalDelta.y) < 5)) { - Debug::log(TRACE, "CTrackpadGestures::gestureUpdate (swipe): gesture delta too small to start considering, waiting"); + Log::logger->log(Log::TRACE, "CTrackpadGestures::gestureUpdate (swipe): gesture delta too small to start considering, waiting"); return; } @@ -148,6 +154,9 @@ void CTrackpadGestures::gestureUpdate(const IPointer::SSwipeUpdateEvent& e) { if (g->modMask != MODS) continue; + if (PROTO::shortcutsInhibit->isInhibited() && !*PDISABLEINHIBIT && !g->disableInhibit) + continue; + m_activeGesture = g; g->currentDirection = g->gesture->isDirectionSensitive() ? g->direction : direction; m_activeGesture->gesture->begin({.swipe = &e, .direction = direction, .scale = g->deltaScale}); @@ -174,7 +183,7 @@ void CTrackpadGestures::gestureEnd(const IPointer::SSwipeEndEvent& e) { void CTrackpadGestures::gestureBegin(const IPointer::SPinchBeginEvent& e) { if (m_activeGesture) { - Debug::log(ERR, "CTrackpadGestures::gestureBegin (pinch) but m_activeGesture is already present"); + Log::logger->log(Log::ERR, "CTrackpadGestures::gestureBegin (pinch) but m_activeGesture is already present"); return; } @@ -184,12 +193,14 @@ 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; // 0.1 was chosen because I felt like that's a good number. if (!m_activeGesture && std::abs(e.scale - 1.F) < 0.1) { - Debug::log(TRACE, "CTrackpadGestures::gestureUpdate (pinch): gesture delta too small to start considering, waiting"); + Log::logger->log(Log::TRACE, "CTrackpadGestures::gestureUpdate (pinch): gesture delta too small to start considering, waiting"); return; } @@ -211,6 +222,9 @@ void CTrackpadGestures::gestureUpdate(const IPointer::SPinchUpdateEvent& e) { if (g->modMask != MODS) continue; + if (PROTO::shortcutsInhibit->isInhibited() && !*PDISABLEINHIBIT && !g->disableInhibit) + continue; + m_activeGesture = g; g->currentDirection = g->gesture->isDirectionSensitive() ? g->direction : direction; m_activeGesture->gesture->begin({.pinch = &e, .direction = direction}); diff --git a/src/managers/input/trackpad/TrackpadGestures.hpp b/src/managers/input/trackpad/TrackpadGestures.hpp index 7f96761f1..ecf11c402 100644 --- a/src/managers/input/trackpad/TrackpadGestures.hpp +++ b/src/managers/input/trackpad/TrackpadGestures.hpp @@ -11,8 +11,9 @@ class CTrackpadGestures { public: void clearGestures(); - std::expected addGesture(UP&& gesture, size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale); - std::expected removeGesture(size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale); + std::expected addGesture(UP&& gesture, size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale, + bool disableInhibit); + std::expected removeGesture(size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale, bool disableInhibit); void gestureBegin(const IPointer::SSwipeBeginEvent& e); void gestureUpdate(const IPointer::SSwipeUpdateEvent& e); @@ -32,6 +33,7 @@ class CTrackpadGestures { uint32_t modMask = 0; eTrackpadGestureDirection direction = TRACKPAD_GESTURE_DIR_NONE; // configured dir float deltaScale = 1.F; + bool disableInhibit = false; eTrackpadGestureDirection currentDirection = TRACKPAD_GESTURE_DIR_NONE; // actual dir of that select swipe }; diff --git a/src/managers/input/trackpad/gestures/CloseGesture.cpp b/src/managers/input/trackpad/gestures/CloseGesture.cpp index 7beba5637..0c37ee36c 100644 --- a/src/managers/input/trackpad/gestures/CloseGesture.cpp +++ b/src/managers/input/trackpad/gestures/CloseGesture.cpp @@ -1,13 +1,13 @@ #include "CloseGesture.hpp" #include "../../../../Compositor.hpp" -#include "../../../../managers/LayoutManager.hpp" #include "../../../../managers/animation/DesktopAnimationManager.hpp" #include "../../../../render/Renderer.hpp" #include "../../../../managers/eventLoop/EventLoopManager.hpp" #include "../../../../managers/eventLoop/EventLoopTimer.hpp" #include "../../../../config/ConfigValue.hpp" #include "../../../../desktop/state/FocusState.hpp" +#include "../../../../layout/target/Target.hpp" constexpr const float MAX_DISTANCE = 200.F; @@ -133,7 +133,7 @@ void CCloseTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) if (!window->m_isMapped) return; - g_pLayoutManager->getCurrentLayout()->recalculateWindow(window.lock()); + window->layoutTarget()->recalc(); window->updateDecorationValues(); window->sendWindowSize(true); *window->m_alpha = 1.F; diff --git a/src/managers/input/trackpad/gestures/CursorZoomGesture.cpp b/src/managers/input/trackpad/gestures/CursorZoomGesture.cpp new file mode 100644 index 000000000..97dfe1582 --- /dev/null +++ b/src/managers/input/trackpad/gestures/CursorZoomGesture.cpp @@ -0,0 +1,33 @@ +#include "CursorZoomGesture.hpp" + +#include "../../../../Compositor.hpp" +#include "../../../../helpers/Monitor.hpp" + +CCursorZoomTrackpadGesture::CCursorZoomTrackpadGesture(const std::string& first, const std::string& second) { + try { + m_zoomValue = std::stof(first); + } catch (...) { ; } + + if (second == "mult") + m_mode = MODE_MULT; +} + +void CCursorZoomTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) { + ITrackpadGesture::begin(e); + + if (m_mode == MODE_TOGGLE) + m_zoomed = !m_zoomed; + + for (auto const& m : g_pCompositor->m_monitors) { + switch (m_mode) { + case MODE_TOGGLE: + static auto PZOOMFACTOR = CConfigValue("cursor:zoom_factor"); + *m->m_cursorZoom = m_zoomed ? m_zoomValue : *PZOOMFACTOR; + break; + case MODE_MULT: *m->m_cursorZoom = std::clamp(m->m_cursorZoom->goal() * m_zoomValue, 1.0F, 100.0F); break; + } + } +} + +void CCursorZoomTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) {} +void CCursorZoomTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) {} diff --git a/src/managers/input/trackpad/gestures/CursorZoomGesture.hpp b/src/managers/input/trackpad/gestures/CursorZoomGesture.hpp new file mode 100644 index 000000000..b53c81e98 --- /dev/null +++ b/src/managers/input/trackpad/gestures/CursorZoomGesture.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "ITrackpadGesture.hpp" + +class CCursorZoomTrackpadGesture : public ITrackpadGesture { + public: + CCursorZoomTrackpadGesture(const std::string& zoomLevel, const std::string& mode); + virtual ~CCursorZoomTrackpadGesture() = default; + + virtual void begin(const ITrackpadGesture::STrackpadGestureBegin& e); + virtual void update(const ITrackpadGesture::STrackpadGestureUpdate& e); + virtual void end(const ITrackpadGesture::STrackpadGestureEnd& e); + + private: + float m_zoomValue = 1.0; + inline static bool m_zoomed = false; + + enum eMode : uint8_t { + MODE_TOGGLE = 0, + MODE_MULT, + }; + + eMode m_mode = MODE_TOGGLE; +}; diff --git a/src/managers/input/trackpad/gestures/FloatGesture.cpp b/src/managers/input/trackpad/gestures/FloatGesture.cpp index ea3307504..0849bfac1 100644 --- a/src/managers/input/trackpad/gestures/FloatGesture.cpp +++ b/src/managers/input/trackpad/gestures/FloatGesture.cpp @@ -1,9 +1,10 @@ #include "FloatGesture.hpp" -#include "../../../../managers/LayoutManager.hpp" #include "../../../../render/Renderer.hpp" #include "../../../../desktop/state/FocusState.hpp" -#include "../../../../desktop/Window.hpp" +#include "../../../../desktop/view/Window.hpp" +#include "../../../../layout/LayoutManager.hpp" +#include "../../../../layout/target/WindowTarget.hpp" constexpr const float MAX_DISTANCE = 250.F; @@ -40,8 +41,7 @@ void CFloatTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& return; } - m_window->m_isFloating = !m_window->m_isFloating; - g_pLayoutManager->getCurrentLayout()->changeWindowFloatingMode(m_window.lock()); + g_layoutManager->changeFloatingMode(m_window->layoutTarget()); m_posFrom = m_window->m_realPosition->begun(); m_sizeFrom = m_window->m_realSize->begun(); @@ -79,8 +79,7 @@ void CFloatTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) if (COMPLETION < 0.2F) { // revert the animation g_pHyprRenderer->damageWindow(m_window.lock()); - m_window->m_isFloating = !m_window->m_isFloating; - g_pLayoutManager->getCurrentLayout()->changeWindowFloatingMode(m_window.lock()); + g_layoutManager->changeFloatingMode(m_window->layoutTarget()); return; } diff --git a/src/managers/input/trackpad/gestures/FullscreenGesture.cpp b/src/managers/input/trackpad/gestures/FullscreenGesture.cpp index 31592f637..a219b6853 100644 --- a/src/managers/input/trackpad/gestures/FullscreenGesture.cpp +++ b/src/managers/input/trackpad/gestures/FullscreenGesture.cpp @@ -77,7 +77,6 @@ void CFullscreenTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd if (COMPLETION < 0.2F) { // revert the animation g_pHyprRenderer->damageWindow(m_window.lock()); - m_window->m_isFloating = !m_window->m_isFloating; g_pDesktopAnimationManager->overrideFullscreenFadeAmount(m_window->m_workspace, m_originalMode == FSMODE_NONE ? 1.F : 0.F, m_window.lock()); g_pCompositor->setWindowFullscreenInternal(m_window.lock(), m_window->m_fullscreenState.internal == FSMODE_NONE ? m_originalMode : FSMODE_NONE); return; diff --git a/src/managers/input/trackpad/gestures/MoveGesture.cpp b/src/managers/input/trackpad/gestures/MoveGesture.cpp index e9338e8af..0dcc310fe 100644 --- a/src/managers/input/trackpad/gestures/MoveGesture.cpp +++ b/src/managers/input/trackpad/gestures/MoveGesture.cpp @@ -1,9 +1,9 @@ #include "MoveGesture.hpp" #include "../../../../desktop/state/FocusState.hpp" -#include "../../../../desktop/Window.hpp" -#include "../../../../managers/LayoutManager.hpp" +#include "../../../../desktop/view/Window.hpp" #include "../../../../render/Renderer.hpp" +#include "../../../../layout/LayoutManager.hpp" void CMoveTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) { ITrackpadGesture::begin(e); @@ -19,7 +19,7 @@ void CMoveTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate const auto DELTA = e.swipe ? e.swipe->delta : e.pinch->delta; if (m_window->m_isFloating) { - g_pLayoutManager->getCurrentLayout()->moveActiveWindow(DELTA, m_window.lock()); + g_layoutManager->moveTarget(DELTA, m_window->layoutTarget()); m_window->m_realSize->warp(); m_window->m_realPosition->warp(); return; @@ -52,10 +52,10 @@ void CMoveTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) { if (std::abs(m_lastDelta.x) > std::abs(m_lastDelta.y)) { // horizontal - g_pLayoutManager->getCurrentLayout()->moveWindowTo(m_window.lock(), m_lastDelta.x > 0 ? "r" : "l"); + g_layoutManager->moveInDirection(m_window->layoutTarget(), m_lastDelta.x > 0 ? "r" : "l"); } else { // vertical - g_pLayoutManager->getCurrentLayout()->moveWindowTo(m_window.lock(), m_lastDelta.y > 0 ? "b" : "t"); + g_layoutManager->moveInDirection(m_window->layoutTarget(), m_lastDelta.y > 0 ? "b" : "t"); } const auto GOAL = m_window->m_realPosition->goal(); diff --git a/src/managers/input/trackpad/gestures/ResizeGesture.cpp b/src/managers/input/trackpad/gestures/ResizeGesture.cpp index 066ebe2a6..ffc7704bf 100644 --- a/src/managers/input/trackpad/gestures/ResizeGesture.cpp +++ b/src/managers/input/trackpad/gestures/ResizeGesture.cpp @@ -1,9 +1,9 @@ #include "ResizeGesture.hpp" #include "../../../../desktop/state/FocusState.hpp" -#include "../../../../desktop/Window.hpp" -#include "../../../../managers/LayoutManager.hpp" +#include "../../../../desktop/view/Window.hpp" #include "../../../../render/Renderer.hpp" +#include "../../../../layout/LayoutManager.hpp" void CResizeTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) { ITrackpadGesture::begin(e); @@ -17,8 +17,8 @@ void CResizeTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpda g_pHyprRenderer->damageWindow(m_window.lock()); - g_pLayoutManager->getCurrentLayout()->resizeActiveWindow((e.swipe ? e.swipe->delta : e.pinch->delta), - cornerFromBox(m_window->getWindowMainSurfaceBox(), g_pInputManager->getMouseCoordsInternal()), m_window.lock()); + g_layoutManager->resizeTarget((e.swipe ? e.swipe->delta : e.pinch->delta), m_window->layoutTarget(), + Layout::cornerFromBox(m_window->getWindowMainSurfaceBox(), g_pInputManager->getMouseCoordsInternal())); m_window->m_realSize->warp(); m_window->m_realPosition->warp(); diff --git a/src/managers/permissions/DynamicPermissionManager.cpp b/src/managers/permissions/DynamicPermissionManager.cpp index a54847737..d63a72a06 100644 --- a/src/managers/permissions/DynamicPermissionManager.cpp +++ b/src/managers/permissions/DynamicPermissionManager.cpp @@ -53,6 +53,7 @@ static const char* permissionToString(eDynamicPermissionType type) { case PERMISSION_TYPE_SCREENCOPY: return "PERMISSION_TYPE_SCREENCOPY"; case PERMISSION_TYPE_PLUGIN: return "PERMISSION_TYPE_PLUGIN"; case PERMISSION_TYPE_KEYBOARD: return "PERMISSION_TYPE_KEYBOARD"; + case PERMISSION_TYPE_CURSOR_POS: return "PERMISSION_TYPE_CURSOR_POS"; } return "error"; @@ -82,19 +83,19 @@ eDynamicPermissionAllowMode CDynamicPermissionManager::clientPermissionMode(wl_c const auto LOOKUP = binaryNameForWlClient(client); - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: checking permission {} for client {:x} (binary {})", permissionToString(permission), rc(client), - LOOKUP.has_value() ? LOOKUP.value() : "lookup failed: " + LOOKUP.error()); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: checking permission {} for client {:x} (binary {})", permissionToString(permission), + rc(client), LOOKUP.has_value() ? LOOKUP.value() : "lookup failed: " + LOOKUP.error()); // first, check if we have the client + perm combo in our cache. auto it = std::ranges::find_if(m_rules, [client, permission](const auto& e) { return e->m_client == client && e->m_type == permission; }); if (it == m_rules.end()) { - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission not cached, checking binary name"); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: permission not cached, checking binary name"); if (!LOOKUP.has_value()) - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: binary name check failed"); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: binary name check failed"); else { const auto BINNAME = LOOKUP.value().contains("/") ? LOOKUP.value().substr(LOOKUP.value().find_last_of('/') + 1) : LOOKUP.value(); - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: binary path {}, name {}", LOOKUP.value(), BINNAME); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: binary path {}, name {}", LOOKUP.value(), BINNAME); it = std::ranges::find_if(m_rules, [clientBinaryPath = LOOKUP.value(), permission](const auto& e) { if (e->m_type != permission) @@ -114,29 +115,29 @@ eDynamicPermissionAllowMode CDynamicPermissionManager::clientPermissionMode(wl_c }); if (it == m_rules.end()) - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: no rule for binary"); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: no rule for binary"); else { if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_ALLOW) { - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission allowed by config rule"); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: permission allowed by config rule"); return PERMISSION_RULE_ALLOW_MODE_ALLOW; } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_DENY) { - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission denied by config rule"); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: permission denied by config rule"); return PERMISSION_RULE_ALLOW_MODE_DENY; } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_PENDING) { - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission pending by config rule"); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: permission pending by config rule"); return PERMISSION_RULE_ALLOW_MODE_PENDING; } else - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission ask by config rule"); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: permission ask by config rule"); } } } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_ALLOW) { - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission allowed before by user"); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: permission allowed before by user"); return PERMISSION_RULE_ALLOW_MODE_ALLOW; } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_DENY) { - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission denied before by user"); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: permission denied before by user"); return PERMISSION_RULE_ALLOW_MODE_DENY; } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_PENDING) { - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission pending before by user"); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: permission pending before by user"); return PERMISSION_RULE_ALLOW_MODE_PENDING; } @@ -158,8 +159,8 @@ eDynamicPermissionAllowMode CDynamicPermissionManager::clientPermissionModeWithS if (pid > 0) { lookup = binaryNameForPid(pid); - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: checking permission {} for key {} (binary {})", permissionToString(permission), str, - lookup.has_value() ? lookup.value() : "lookup failed: " + lookup.error()); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: checking permission {} for key {} (binary {})", permissionToString(permission), str, + lookup.has_value() ? lookup.value() : "lookup failed: " + lookup.error()); if (lookup.has_value()) binaryName = *lookup; @@ -169,7 +170,7 @@ eDynamicPermissionAllowMode CDynamicPermissionManager::clientPermissionModeWithS // first, check if we have the client + perm combo in our cache. auto it = std::ranges::find_if(m_rules, [str, permission, pid](const auto& e) { return e->m_keyString == str && pid && pid == e->m_pid && e->m_type == permission; }); if (it == m_rules.end()) { - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission not cached, checking key"); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: permission not cached, checking key"); it = std::ranges::find_if(m_rules, [key = str, permission, &lookup](const auto& e) { if (e->m_type != permission) @@ -186,33 +187,33 @@ eDynamicPermissionAllowMode CDynamicPermissionManager::clientPermissionModeWithS }); if (it == m_rules.end()) - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: no rule for key"); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: no rule for key"); else { if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_ALLOW) { - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission allowed by config rule"); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: permission allowed by config rule"); return PERMISSION_RULE_ALLOW_MODE_ALLOW; } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_DENY) { - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission denied by config rule"); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: permission denied by config rule"); return PERMISSION_RULE_ALLOW_MODE_DENY; } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_PENDING) { - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission pending by config rule"); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: permission pending by config rule"); return PERMISSION_RULE_ALLOW_MODE_PENDING; } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_ASK) { - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission ask by config rule"); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: permission ask by config rule"); askForPermission(nullptr, str, permission, pid); return PERMISSION_RULE_ALLOW_MODE_PENDING; } else - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission ask by config rule"); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: permission ask by config rule"); } } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_ALLOW) { - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission allowed before by user"); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: permission allowed before by user"); return PERMISSION_RULE_ALLOW_MODE_ALLOW; } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_DENY) { - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission denied before by user"); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: permission denied before by user"); return PERMISSION_RULE_ALLOW_MODE_DENY; } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_PENDING) { - Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission pending before by user"); + Log::logger->log(Log::TRACE, "CDynamicPermissionManager::clientHasPermission: permission pending before by user"); return PERMISSION_RULE_ALLOW_MODE_PENDING; } @@ -251,7 +252,8 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s std::string description = ""; switch (rule->m_type) { case PERMISSION_TYPE_SCREENCOPY: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, {{"app", appName}}); break; - case PERMISSION_TYPE_PLUGIN: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, {{"app", appName}, {"plugin", binaryPath}}); break; + case PERMISSION_TYPE_CURSOR_POS: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_CURSOR_POS, {{"app", appName}}); break; + case PERMISSION_TYPE_PLUGIN: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_PLUGIN, {{"app", appName}, {"plugin", binaryPath}}); break; case PERMISSION_TYPE_KEYBOARD: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_KEYBOARD, {{"keyboard", binaryPath}}); break; case PERMISSION_TYPE_UNKNOWN: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_UNKNOWN, {{"app", appName}}); break; } @@ -272,7 +274,7 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s rule->m_dialogBox->m_priority = true; if (!rule->m_dialogBox) { - Debug::log(ERR, "CDynamicPermissionManager::askForPermission: hyprland-guiutils likely missing, cannot ask! Disabling permission control..."); + Log::logger->log(Log::ERR, "CDynamicPermissionManager::askForPermission: hyprland-guiutils likely missing, cannot ask! Disabling permission control..."); rule->m_allowMode = PERMISSION_RULE_ALLOW_MODE_ALLOW; return; } @@ -284,7 +286,7 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s if (pr->hasError()) { // not reachable for now - Debug::log(TRACE, "CDynamicPermissionRule: error spawning dialog box"); + Log::logger->log(Log::TRACE, "CDynamicPermissionRule: error spawning dialog box"); if (r->m_promiseResolverForExternal) r->m_promiseResolverForExternal->reject("error spawning dialog box"); r->m_promiseResolverForExternal.reset(); @@ -293,7 +295,7 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s const std::string& result = pr->result(); - Debug::log(TRACE, "CDynamicPermissionRule: user returned {}", result); + Log::logger->log(Log::TRACE, "CDynamicPermissionRule: user returned {}", result); if (result.starts_with(ALLOW_ONCE)) r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_ALLOW; diff --git a/src/managers/permissions/DynamicPermissionManager.hpp b/src/managers/permissions/DynamicPermissionManager.hpp index 4de1eb328..423596c39 100644 --- a/src/managers/permissions/DynamicPermissionManager.hpp +++ b/src/managers/permissions/DynamicPermissionManager.hpp @@ -18,6 +18,7 @@ enum eDynamicPermissionType : uint8_t { PERMISSION_TYPE_SCREENCOPY, PERMISSION_TYPE_PLUGIN, PERMISSION_TYPE_KEYBOARD, + PERMISSION_TYPE_CURSOR_POS, }; enum eDynamicPermissionRuleSource : uint8_t { @@ -104,4 +105,4 @@ class CDynamicPermissionManager { std::vector> m_rules; }; -inline UP g_pDynamicPermissionManager; \ No newline at end of file +inline UP g_pDynamicPermissionManager; diff --git a/src/managers/screenshare/CursorshareSession.cpp b/src/managers/screenshare/CursorshareSession.cpp new file mode 100644 index 000000000..f093cfd8b --- /dev/null +++ b/src/managers/screenshare/CursorshareSession.cpp @@ -0,0 +1,231 @@ +#include "ScreenshareManager.hpp" +#include "../PointerManager.hpp" +#include "../../protocols/core/Seat.hpp" +#include "../permissions/DynamicPermissionManager.hpp" +#include "../../render/Renderer.hpp" +#include "../../render/pass/ClearPassElement.hpp" +#include "../../render/pass/TexPassElement.hpp" +#include + +using namespace Hyprgraphics::Egl; +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_pHyprRenderer->m_renderData.transformDamage = false; + g_pHyprRenderer->setViewport(0, 0, m_bufferSize.x, m_bufferSize.y); + + bool overlaps = g_pPointerManager->getCursorBoxGlobal().overlaps(m_pendingFrame.sourceBoxCallback()); + g_pHyprRenderer->startRenderPass(); + if (PERM != PERMISSION_RULE_ALLOW_MODE_ALLOW || !overlaps) { + // render black when not allowed + g_pHyprRenderer->draw(CClearPassElement::SClearData{Colors::BLACK}); + } else if (!cursorImage.pBuffer || !cursorImage.surface || !cursorImage.bufferTex) { + // render clear when cursor is probably hidden + g_pHyprRenderer->draw(CClearPassElement::SClearData{{0, 0, 0, 0}}); + } else { + // render cursor + g_pHyprRenderer->draw(CTexPassElement::SRenderData{ + .tex = cursorImage.bufferTex, + .box = {{}, cursorImage.bufferTex->m_size}, + }); + } + + g_pHyprRenderer->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); + + 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->beginRenderToBuffer(m_pendingFrame.monitor, fakeDamage, m_pendingFrame.buffer, 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) { + const auto PFORMAT = 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->beginFullFakeRender(m_pendingFrame.monitor, fakeDamage, outFB)) { + LOGM(Log::ERR, "Can't copy: failed to begin rendering to shm"); + return false; + } + + render(); + + g_pHyprRenderer->endRender(); + + 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()) { + if (PFORMAT->swizzle == SWIZZLE_RGBA) + glFormat = GL_RGBA; + else if (PFORMAT->swizzle == SWIZZLE_BGRA) + glFormat = GL_BGRA_EXT; + else { + LOGM(Log::ERR, "Copied frame via shm might be broken or color flipped"); + glFormat = GL_RGBA; + } + } + } + + outFB->readPixels(m_pendingFrame.buffer, 0, 0, m_bufferSize.x, m_bufferSize.y); + + g_pHyprRenderer->m_renderData.pMonitor.reset(); + + m_pendingFrame.callback(RESULT_COPIED); + } else { + LOGM(Log::ERR, "Can't copy: invalid buffer type"); + return false; + } + + m_pendingFrame.pending = false; + m_constraintsChanged = false; + return true; +} + +DRMFormat CCursorshareSession::format() const { + return m_format; +} + +Vector2D CCursorshareSession::bufferSize() const { + return m_bufferSize; +} + +Vector2D CCursorshareSession::hotspot() const { + return m_hotspot; +} diff --git a/src/managers/screenshare/ScreenshareFrame.cpp b/src/managers/screenshare/ScreenshareFrame.cpp new file mode 100644 index 000000000..de71fcaac --- /dev/null +++ b/src/managers/screenshare/ScreenshareFrame.cpp @@ -0,0 +1,488 @@ +#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 "../../render/pass/ClearPassElement.hpp" +#include "../../render/pass/RectPassElement.hpp" +#include "helpers/cm/ColorManagement.hpp" +#include +#include + +using namespace Hyprgraphics::Egl; +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->m_renderData.pMonitor->resources()->getMirrorTexture(); + if (!TEXTURE) { + LOGM(Log::ERR, "Invalid source texture"); + return; + } + + if (!TEXTURE->m_imageDescription) + Log::logger->log(Log::ERR, "CM: FIXME no source image description for screenshare"); + + if (!g_pHyprRenderer->m_renderData.currentFB->imageDescription()) + Log::logger->log(Log::ERR, "CM: FIXME no target image description for screenshare"); + + if (TEXTURE->m_imageDescription && g_pHyprRenderer->m_renderData.currentFB->imageDescription()) + Log::logger->log(Log::TRACE, "CM: screenshot renderMonitor {} -> {}", TEXTURE->m_imageDescription->value(), + g_pHyprRenderer->m_renderData.currentFB->imageDescription()->value()); + + const bool IS_CM_AWARE = PROTO::colorManagement && PROTO::colorManagement->isClientCMAware(m_session->m_client); + g_pHyprRenderer->m_renderData.transformDamage = false; + g_pHyprRenderer->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. + + const auto OLD = g_pHyprRenderer->m_renderData.renderModif.enabled; + g_pHyprRenderer->m_renderData.renderModif.enabled = false; + g_pHyprRenderer->startRenderPass(); + g_pHyprRenderer->draw( + CTexPassElement::SRenderData{ + .tex = TEXTURE, + .box = monbox, + .flipEndFrame = true, + .cmBackToSRGB = !IS_CM_AWARE, + .cmBackToSRGBSource = !IS_CM_AWARE ? PMONITOR : nullptr, + }, + {0, 0, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y}); + g_pHyprRenderer->m_renderData.renderModif.enabled = OLD; + + // 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_pHyprRenderer->draw(CRectPassElement::SRectData{.box = surfBox, .color = Colors::BLACK}, surfBox); + }, + 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_pHyprRenderer->draw(CRectPassElement::SRectData{.box = noScreenShareBox, .color = Colors::BLACK}, noScreenShareBox); + + 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_pHyprRenderer->draw( + CRectPassElement::SRectData{ + .box = noScreenShareBox, + .color = Colors::BLACK, + .round = rounding, + .roundingPower = roundingPower, + }, + noScreenShareBox); + + 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_pHyprRenderer->m_renderData.fbSize = m_bufferSize; + g_pHyprRenderer->setProjectionType(Render::RPT_EXPORT); + g_pHyprRenderer->m_renderData.transformDamage = false; + g_pHyprRenderer->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::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); + + CRegion frameRegion = {0, 0, g_pHyprRenderer->m_renderData.pMonitor->m_pixelSize.x, g_pHyprRenderer->m_renderData.pMonitor->m_pixelSize.y}; + + g_pHyprRenderer->draw(CClearPassElement::SClearData{{0, 0, 0, 0}}, frameRegion); + + if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) + return; + + bool windowShareDenied = m_session->m_type == SHARE_WINDOW && m_session->m_window->m_ruleApplicator && m_session->m_window->m_ruleApplicator->noScreenShare().valueOrDefault(); + g_pHyprRenderer->startRenderPass(); + if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY || windowShareDenied) { + CBox texbox = CBox{m_bufferSize / 2.F, g_pHyprRenderer->m_screencopyDeniedTexture->m_size}.translate(-g_pHyprRenderer->m_screencopyDeniedTexture->m_size / 2.F); + g_pHyprRenderer->draw(CTexPassElement::SRenderData{.tex = g_pHyprRenderer->m_screencopyDeniedTexture, .box = texbox}, texbox); + return; + } + + if (m_session->m_tempFB && m_session->m_tempFB->isAllocated()) { + CBox texbox = {{}, m_bufferSize}; + g_pHyprRenderer->draw(CTexPassElement::SRenderData{.tex = m_session->m_tempFB->getTexture(), .box = texbox}, 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::RENDER_MODE_TO_BUFFER, m_buffer, nullptr, true)) { + LOGM(Log::ERR, "Can't copy: failed to begin rendering to dma frame"); + return false; + } + g_pHyprRenderer->m_renderData.currentFB->setImageDescription(NColorManagement::DEFAULT_SRGB_IMAGE_DESCRIPTION); + + render(); + + g_pHyprRenderer->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; + + auto shm = m_buffer->shm(); + + const auto PFORMAT = 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); + outFB->setImageDescription(NColorManagement::DEFAULT_SRGB_IMAGE_DESCRIPTION); + + if (!g_pHyprRenderer->beginFullFakeRender(PMONITOR, m_damage, outFB)) { + LOGM(Log::ERR, "Can't copy: failed to begin rendering"); + return false; + } + + render(); + + g_pHyprRenderer->m_renderData.blockScreenShader = true; + + g_pHyprRenderer->endRender(); + + m_damage.forEachRect([&](const auto& rect) { + int width = rect.x2 - rect.x1; + int height = rect.y2 - rect.y1; + outFB->readPixels(m_buffer, rect.x1, rect.y1, width, height); + }); + + g_pHyprRenderer->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); + m_session->m_tempFB->setImageDescription(NColorManagement::DEFAULT_SRGB_IMAGE_DESCRIPTION); + + CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX}; + + if (!g_pHyprRenderer->beginFullFakeRender(m_session->monitor(), fakeDamage, m_session->m_tempFB)) { + LOGM(Log::ERR, "Can't copy: failed to begin rendering to temp fb"); + return; + } + + switch (m_session->m_type) { + case SHARE_REGION: // TODO: could this be better? this is how screencopy works + case SHARE_MONITOR: renderMonitor(); break; + case SHARE_WINDOW: renderWindow(); break; + case SHARE_NONE: + default: return; + } + + g_pHyprRenderer->endRender(); +} + +Vector2D CScreenshareFrame::bufferSize() const { + return m_bufferSize; +} + +wl_output_transform CScreenshareFrame::transform() const { + switch (m_session->m_type) { + case SHARE_REGION: + case SHARE_MONITOR: return m_session->monitor()->m_transform; + default: + case SHARE_WINDOW: return WL_OUTPUT_TRANSFORM_NORMAL; + } +} + +const CRegion& CScreenshareFrame::damage() const { + return m_damage; +} diff --git a/src/managers/screenshare/ScreenshareManager.cpp b/src/managers/screenshare/ScreenshareManager.cpp new file mode 100644 index 000000000..6a0f5b958 --- /dev/null +++ b/src/managers/screenshare/ScreenshareManager.cpp @@ -0,0 +1,165 @@ +#include "ScreenshareManager.hpp" +#include "../../render/Renderer.hpp" +#include "../../Compositor.hpp" +#include "../../desktop/view/Window.hpp" +#include "../../protocols/core/Seat.hpp" + +using namespace Screenshare; + +CScreenshareManager::CScreenshareManager() { + ; +} + +void CScreenshareManager::onOutputCommit(PHLMONITOR monitor) { + std::erase_if(m_sessions, [&](const WP& session) { return session.expired(); }); + + // if no pending frames, and no sessions are sharing, then unblock ds + if (m_pendingFrames.empty()) { + for (const auto& session : m_sessions) { + if (!session->m_stopped && session->m_sharing) + return; + } + + g_pHyprRenderer->m_directScanoutBlocked = false; + return; // nothing to share + } + + std::ranges::for_each(m_pendingFrames, [&](WP& frame) { + if (frame.expired() || !frame->m_shared || frame->done()) + return; + + if (frame->m_session->monitor() != monitor) + return; + + if (frame->m_session->m_type == SHARE_WINDOW) { + CBox geometry = {frame->m_session->m_window->m_realPosition->value(), frame->m_session->m_window->m_realSize->value()}; + if (geometry.intersection({monitor->m_position, monitor->m_size}).empty()) + return; + } + + frame->copy(); + }); + + std::erase_if(m_pendingFrames, [&](const WP& frame) { return frame.expired(); }); +} + +UP CScreenshareManager::newSession(wl_client* client, PHLMONITOR monitor) { + if UNLIKELY (!monitor || !g_pCompositor->monitorExists(monitor)) { + LOGM(Log::ERR, "Client requested sharing of a monitor that is gone"); + return nullptr; + } + + UP session = UP(new CScreenshareSession(monitor, client)); + + session->m_self = session; + m_sessions.emplace_back(session); + + return session; +} + +UP CScreenshareManager::newSession(wl_client* client, PHLMONITOR monitor, CBox captureRegion) { + if UNLIKELY (!monitor || !g_pCompositor->monitorExists(monitor)) { + LOGM(Log::ERR, "Client requested sharing of a monitor that is gone"); + return nullptr; + } + + UP session = UP(new CScreenshareSession(monitor, captureRegion, client)); + + session->m_self = session; + m_sessions.emplace_back(session); + + return session; +} + +UP CScreenshareManager::newSession(wl_client* client, PHLWINDOW window) { + if UNLIKELY (!window || !window->m_isMapped) { + LOGM(Log::ERR, "Client requested sharing of window that is gone or not shareable!"); + return nullptr; + } + + UP session = UP(new CScreenshareSession(window, client)); + + session->m_self = session; + m_sessions.emplace_back(session); + + return session; +} + +UP CScreenshareManager::newCursorSession(wl_client* client, WP pointer) { + UP session = UP(new CCursorshareSession(client, pointer)); + + session->m_self = session; + m_cursorSessions.emplace_back(session); + + return session; +} + +WP CScreenshareManager::getManagedSession(wl_client* client, PHLMONITOR monitor) { + return getManagedSession(SHARE_MONITOR, client, monitor, nullptr, {}); +} + +WP CScreenshareManager::getManagedSession(wl_client* client, PHLMONITOR monitor, CBox captureBox) { + + return getManagedSession(SHARE_REGION, client, monitor, nullptr, captureBox); +} + +WP CScreenshareManager::getManagedSession(wl_client* client, PHLWINDOW window) { + return getManagedSession(SHARE_WINDOW, client, nullptr, window, {}); +} + +WP CScreenshareManager::getManagedSession(eScreenshareType type, wl_client* client, PHLMONITOR monitor, PHLWINDOW window, CBox captureBox) { + if (type == SHARE_NONE) + return {}; + + auto it = std::ranges::find_if(m_managedSessions, [&](const auto& session) { + if (session->m_session->m_client != client || session->m_session->m_type != type) + return false; + + switch (type) { + case SHARE_MONITOR: return session->m_session->m_monitor == monitor; + case SHARE_WINDOW: return session->m_session->m_window == window; + case SHARE_REGION: return session->m_session->m_monitor == monitor && session->m_session->m_captureBox == captureBox; + case SHARE_NONE: + default: return false; + } + + return false; + }); + + if (it == m_managedSessions.end()) { + UP session; + switch (type) { + case SHARE_MONITOR: session = UP(new CScreenshareSession(monitor, client)); break; + case SHARE_WINDOW: session = UP(new CScreenshareSession(window, client)); break; + case SHARE_REGION: session = UP(new CScreenshareSession(monitor, captureBox, client)); break; + case SHARE_NONE: + default: return {}; + } + + session->m_self = session; + m_sessions.emplace_back(session); + + it = m_managedSessions.emplace(m_managedSessions.end(), makeUnique(std::move(session))); + } + + auto& session = *it; + + session->stoppedListener = session->m_session->m_events.stopped.listen([session = WP(session)]() { + if (!session.expired()) + std::erase_if(Screenshare::mgr()->m_managedSessions, [&](const auto& s) { return s && s->m_session.get() == session->m_session.get(); }); + }); + + return session->m_session; +} + +bool CScreenshareManager::isOutputBeingSSd(PHLMONITOR monitor) { + return std::ranges::any_of(m_sessions, [monitor](const auto& s) { + if (!s) + return false; + return s->isActive() && (s->m_type == SHARE_MONITOR || s->m_type == SHARE_REGION) && s->m_monitor == monitor; + }); +} + +CScreenshareManager::SManagedSession::SManagedSession(UP&& session) : m_session(std::move(session)) { + ; +} diff --git a/src/managers/screenshare/ScreenshareManager.hpp b/src/managers/screenshare/ScreenshareManager.hpp new file mode 100644 index 000000000..958e7ffca --- /dev/null +++ b/src/managers/screenshare/ScreenshareManager.hpp @@ -0,0 +1,252 @@ +#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(); + bool isActive(); + + // constraints + const std::vector& allowedFormats() const; + Vector2D bufferSize() const; + PHLMONITOR monitor() const; // this will return the correct monitor based on type + + struct { + CSignalT<> stopped; + CSignalT<> constraintsChanged; + } m_events; + + private: + CScreenshareSession(PHLMONITOR monitor, wl_client* client); + CScreenshareSession(PHLMONITOR monitor, CBox captureRegion, wl_client* client); + CScreenshareSession(PHLWINDOW window, wl_client* client); + + WP m_self; + bool m_stopped = false; + + eScreenshareType m_type = SHARE_NONE; + PHLMONITORREF m_monitor; + PHLWINDOWREF m_window; + CBox m_captureBox = {}; // given capture area in logical coordinates (see xdg_output) + + wl_client* m_client = nullptr; + std::string m_name = ""; + + std::vector m_formats; + Vector2D m_bufferSize = Vector2D(0, 0); + + SP m_tempFB; + + SP m_shareStopTimer; + bool m_sharing = false; + + struct { + CHyprSignalListener monitorDestroyed; + CHyprSignalListener monitorModeChanged; + CHyprSignalListener windowDestroyed; + CHyprSignalListener windowSizeChanged; + CHyprSignalListener windowMonitorChanged; + } m_listeners; + + void screenshareEvents(bool started); + void calculateConstraints(); + void init(); + + friend class CScreenshareFrame; + friend class CScreenshareManager; + }; + + class CCursorshareSession { + public: + CCursorshareSession(const CCursorshareSession&) = delete; + CCursorshareSession(CCursorshareSession&&) = delete; + ~CCursorshareSession(); + + eScreenshareError share(PHLMONITOR monitor, SP buffer, FSourceBoxCallback sourceBoxCallback, FScreenshareCallback callback); + void stop(); + + // constraints + DRMFormat format() const; + Vector2D bufferSize() const; + Vector2D hotspot() const; + + struct { + CSignalT<> stopped; + CSignalT<> constraintsChanged; + } m_events; + + private: + CCursorshareSession(wl_client* client, WP pointer); + + WP m_self; + bool m_stopped = false; + bool m_constraintsChanged = true; + + wl_client* m_client = nullptr; + WP m_pointer; + + // constraints + DRMFormat m_format = 0 /* DRM_FORMAT_INVALID */; + Vector2D m_hotspot = Vector2D(0, 0); + Vector2D m_bufferSize = Vector2D(0, 0); + + struct { + bool pending = false; + PHLMONITOR monitor; + SP buffer; + FSourceBoxCallback sourceBoxCallback; + FScreenshareCallback callback; + } m_pendingFrame; + + struct { + CHyprSignalListener pointerDestroyed; + CHyprSignalListener cursorChanged; + } m_listeners; + + bool copy(); + void render(); + void calculateConstraints(); + + friend class CScreenshareFrame; + friend class CScreenshareManager; + }; + + class CScreenshareFrame { + public: + CScreenshareFrame(const CScreenshareFrame&) = delete; + CScreenshareFrame(CScreenshareFrame&&) = delete; + CScreenshareFrame(WP session, bool overlayCursor, bool isFirst); + ~CScreenshareFrame(); + + bool done() const; + eScreenshareError share(SP buffer, const CRegion& damage, FScreenshareCallback callback); + + Vector2D bufferSize() const; + wl_output_transform transform() const; // returns the transform applied by compositor on the buffer + const CRegion& damage() const; + + private: + WP m_self; + WP m_session; + FScreenshareCallback m_callback; + SP m_buffer; + Vector2D m_bufferSize = Vector2D(0, 0); + CRegion m_damage; // damage in buffer coords + bool m_shared = false, m_copied = false, m_failed = false; + bool m_overlayCursor = true; + bool m_isFirst = false; + + // + void copy(); + bool copyDmabuf(); + bool copyShm(); + + void render(); + void renderMonitor(); + void renderMonitorRegion(); + void renderWindow(); + + void storeTempFB(); + + friend class CScreenshareManager; + friend class CScreenshareSession; + }; + + class CScreenshareManager { + public: + CScreenshareManager(); + + UP newSession(wl_client* client, PHLMONITOR monitor); + UP newSession(wl_client* client, PHLMONITOR monitor, CBox captureRegion); + UP newSession(wl_client* client, PHLWINDOW window); + + WP getManagedSession(wl_client* client, PHLMONITOR monitor); + WP getManagedSession(wl_client* client, PHLMONITOR monitor, CBox captureBox); + WP getManagedSession(wl_client* client, PHLWINDOW window); + + UP newCursorSession(wl_client* client, WP pointer); + + void onOutputCommit(PHLMONITOR monitor); + bool isOutputBeingSSd(PHLMONITOR monitor); + + private: + std::vector> m_sessions; + std::vector> m_cursorSessions; + std::vector> m_pendingFrames; + + struct SManagedSession { + SManagedSession(UP&& session); + + UP m_session; + CHyprSignalListener stoppedListener; + }; + + std::vector> m_managedSessions; + WP getManagedSession(eScreenshareType type, wl_client* client, PHLMONITOR monitor, PHLWINDOW window, CBox captureBox); + + friend class CScreenshareSession; + }; + + inline UP& mgr() { + static UP manager = nullptr; + if (!manager && g_pHyprRenderer) { + Log::logger->log(Log::DEBUG, "Starting ScreenshareManager"); + manager = makeUnique(); + } + return manager; + } +} + +template <> +struct std::formatter : std::formatter { + auto format(const Screenshare::eScreenshareType& res, std::format_context& ctx) const { + switch (res) { + case Screenshare::SHARE_MONITOR: return formatter::format("monitor", ctx); + case Screenshare::SHARE_WINDOW: return formatter::format("window", ctx); + case Screenshare::SHARE_REGION: return formatter::format("region", ctx); + case Screenshare::SHARE_NONE: return formatter::format("ERR NONE", ctx); + } + return formatter::format("error", ctx); + } +}; diff --git a/src/managers/screenshare/ScreenshareSession.cpp b/src/managers/screenshare/ScreenshareSession.cpp new file mode 100644 index 000000000..e3676ec0b --- /dev/null +++ b/src/managers/screenshare/ScreenshareSession.cpp @@ -0,0 +1,179 @@ +#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) { + if UNLIKELY (!m_monitor) + return; + + init(); +} + +CScreenshareSession::CScreenshareSession(PHLWINDOW window, wl_client* client) : m_type(SHARE_WINDOW), m_window(window), m_client(client) { + if UNLIKELY (!m_window) + return; + + 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) { + if UNLIKELY (!m_monitor) + return; + + 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); +} + +bool CScreenshareSession::isActive() { + return !m_stopped; +} + +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(PMONITOR->getPreferredReadFormat())); + m_formats.push_back(PMONITOR->getPreferredReadFormat()); // 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/notification/NotificationOverlay.cpp b/src/notification/NotificationOverlay.cpp new file mode 100644 index 000000000..0f38bfa3f --- /dev/null +++ b/src/notification/NotificationOverlay.cpp @@ -0,0 +1,254 @@ +#include +#include +#include +#include +#include "NotificationOverlay.hpp" +#include "../Compositor.hpp" +#include "../config/ConfigValue.hpp" +#include "../render/pass/RectPassElement.hpp" +#include "../render/pass/TexPassElement.hpp" +#include "../event/EventBus.hpp" + +#include "../managers/animation/AnimationManager.hpp" +#include "../render/Renderer.hpp" + +using namespace Notification; + +static inline auto iconBackendFromLayout(PangoLayout* layout) { + // preference: Nerd > FontAwesome > text + auto eIconBackendChecks = std::array{ICONS_BACKEND_NF, ICONS_BACKEND_FA}; + for (auto iconID : eIconBackendChecks) { + auto iconsText = std::ranges::fold_left(ICONS_ARRAY[iconID], std::string(), std::plus<>()); + pango_layout_set_text(layout, iconsText.c_str(), -1); + if (pango_layout_get_unknown_glyphs_count(layout) == 0) + return iconID; + } + return ICONS_BACKEND_NONE; +} + +static constexpr auto ANIM_DURATION_MS = 600.F; +static constexpr auto ANIM_LAG_MS = 100.F; +static constexpr auto NOTIF_LEFTBAR_SIZE = 5.F; +static constexpr auto NOTIF_PAD_X = 20.F; +static constexpr auto NOTIF_PAD_Y = 10.F; +static constexpr auto NOTIF_OFFSET_Y = 10.F; +static constexpr auto NOTIF_GAP_Y = 10.F; +static constexpr auto NOTIF_DAMAGE_PAD_X = 20.F; +static constexpr auto ICON_PAD = 3.F; +static constexpr auto ICON_SCALE = 0.9F; + +UP& Notification::overlay() { + static UP p = makeUnique(); + return p; +} + +CNotificationOverlay::CNotificationOverlay() { + static auto P = Event::bus()->m_events.monitor.focused.listen([&](PHLMONITOR mon) { + if (m_notifications.empty()) + return; + + g_pHyprRenderer->damageBox(m_lastDamage); + }); +} + +CNotificationOverlay::~CNotificationOverlay() = default; + +eIconBackend CNotificationOverlay::iconBackendForFont(const std::string& fontFamily) { + if (m_iconBackendValid && m_cachedIconBackendFontFamily == fontFamily) + return m_cachedIconBackend; + + auto* cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + auto* cairoCtx = cairo_create(cairoSurface); + auto* layout = pango_cairo_create_layout(cairoCtx); + auto* pangoFD = pango_font_description_new(); + + pango_font_description_set_family(pangoFD, fontFamily.c_str()); + pango_font_description_set_style(pangoFD, PANGO_STYLE_NORMAL); + pango_font_description_set_weight(pangoFD, PANGO_WEIGHT_NORMAL); + pango_layout_set_font_description(layout, pangoFD); + + m_cachedIconBackend = iconBackendFromLayout(layout); + m_cachedIconBackendFontFamily = fontFamily; + m_iconBackendValid = true; + + pango_font_description_free(pangoFD); + g_object_unref(layout); + cairo_destroy(cairoCtx); + cairo_surface_destroy(cairoSurface); + + return m_cachedIconBackend; +} + +void CNotificationOverlay::ensureNotificationCache(SNotification& notif, PHLMONITOR pMonitor, const std::string& fontFamily) { + const auto iconBackend = iconBackendForFont(fontFamily); + const auto fontSizePx = std::clamp(sc(notif.fontSize * ((pMonitor->m_pixelSize.x * pMonitor->m_scale) / 1920.f)), 8, 40); + + const bool cacheValid = notif.cache.monitor == pMonitor && notif.cache.fontFamily == fontFamily && notif.cache.fontSizePx == fontSizePx && + notif.cache.iconBackend == iconBackend && notif.cache.textTex && (notif.icon == ICON_NONE || notif.cache.iconTex); + + if (cacheValid) + return; + + notif.cache = {}; + notif.cache.monitor = pMonitor; + notif.cache.fontFamily = fontFamily; + notif.cache.fontSizePx = fontSizePx; + notif.cache.iconBackend = iconBackend; + + notif.cache.textTex = g_pHyprRenderer->renderText(notif.text, CHyprColor{1.F, 1.F, 1.F, 1.F}, fontSizePx, false, fontFamily); + if (notif.cache.textTex) + notif.cache.textSize = notif.cache.textTex->m_size; + + if (notif.icon != ICON_NONE) { + const auto iconGlyph = ICONS_ARRAY[iconBackend][notif.icon]; + const auto iconSizePx = std::max(8, sc(std::round(fontSizePx * ICON_SCALE))); + + notif.cache.iconTex = g_pHyprRenderer->renderText(iconGlyph, CHyprColor{1.F, 1.F, 1.F, 1.F}, iconSizePx, false, fontFamily); + if (notif.cache.iconTex) + notif.cache.iconSize = notif.cache.iconTex->m_size; + } +} + +void CNotificationOverlay::addNotification(const std::string& text, const CHyprColor& color, const float timeMs, const eIcons icon, const float fontSize) { + const auto PNOTIF = m_notifications.emplace_back(makeUnique()).get(); + + PNOTIF->text = text; + PNOTIF->color = color == CHyprColor(0) ? ICONS_COLORS[icon] : color; + PNOTIF->started.reset(); + PNOTIF->timeMs = timeMs; + PNOTIF->icon = icon; + PNOTIF->fontSize = fontSize; + + for (auto const& m : g_pCompositor->m_monitors) { + g_pCompositor->scheduleFrameForMonitor(m); + } +} + +void CNotificationOverlay::dismissNotifications(const int amount) { + if (amount == -1) + m_notifications.clear(); + else { + const int AMT = std::min(amount, sc(m_notifications.size())); + + for (int i = 0; i < AMT; ++i) { + m_notifications.erase(m_notifications.begin()); + } + } + + for (auto const& m : g_pCompositor->m_monitors) { + g_pCompositor->scheduleFrameForMonitor(m); + } +} + +CBox CNotificationOverlay::drawNotifications(PHLMONITOR pMonitor) { + float offsetY = NOTIF_OFFSET_Y; + float maxWidth = 0; + + const auto MONSIZE = pMonitor->m_transformedSize; + + static auto fontFamily = CConfigValue("misc:font_family"); + const auto PBEZIER = g_pAnimationManager->getBezier("default"); + + for (auto const& notif : m_notifications) { + ensureNotificationCache(*notif, pMonitor, *fontFamily); + + const auto ICONPADFORNOTIF = notif->icon == ICON_NONE ? 0.F : ICON_PAD; + const auto ICONW = notif->cache.iconSize.x; + const auto ICONH = notif->cache.iconSize.y; + const auto TEXTW = notif->cache.textSize.x; + const auto TEXTH = notif->cache.textSize.y; + const auto NOTIFSIZE = Vector2D{TEXTW + NOTIF_PAD_X + ICONW + 2 * ICONPADFORNOTIF, std::max(TEXTH, ICONH) + NOTIF_PAD_Y}; + + const float elapsed = notif->started.getMillis(); + const float lifeMs = std::max(notif->timeMs, 1.F); + + // first rect (bg, col) + const float FIRSTRECTANIMP = std::clamp( + (elapsed > (ANIM_DURATION_MS - ANIM_LAG_MS) ? (elapsed > lifeMs - (ANIM_DURATION_MS - ANIM_LAG_MS) ? lifeMs - elapsed : (ANIM_DURATION_MS - ANIM_LAG_MS)) : elapsed) / + (ANIM_DURATION_MS - ANIM_LAG_MS), + 0.F, 1.F); + + const float FIRSTRECTPERC = FIRSTRECTANIMP >= 0.99f ? 1.f : PBEZIER->getYForPoint(FIRSTRECTANIMP); + + // second rect (fg, black) + const float SECONDRECTANIMP = + std::clamp((elapsed > ANIM_DURATION_MS ? (elapsed > lifeMs - ANIM_DURATION_MS ? lifeMs - elapsed : ANIM_DURATION_MS) : elapsed) / ANIM_DURATION_MS, 0.F, 1.F); + + const float SECONDRECTPERC = SECONDRECTANIMP >= 0.99f ? 1.f : PBEZIER->getYForPoint(SECONDRECTANIMP); + + // third rect (horiz, col) + const float THIRDRECTPERC = std::clamp(elapsed / lifeMs, 0.F, 1.F); + + const float firstRectX = MONSIZE.x - (NOTIFSIZE.x + NOTIF_LEFTBAR_SIZE) * FIRSTRECTPERC; + const float firstRectW = (NOTIFSIZE.x + NOTIF_LEFTBAR_SIZE) * FIRSTRECTPERC; + + const float secondRectX = MONSIZE.x - NOTIFSIZE.x * SECONDRECTPERC; + const float secondRectW = NOTIFSIZE.x * SECONDRECTPERC; + + CRectPassElement::SRectData bgData; + bgData.box = {firstRectX, offsetY, firstRectW, NOTIFSIZE.y}; + bgData.color = notif->color; + g_pHyprRenderer->m_renderPass.add(makeUnique(bgData)); + + CRectPassElement::SRectData fgData; + fgData.box = {secondRectX, offsetY, secondRectW, NOTIFSIZE.y}; + fgData.color = CHyprColor{0.F, 0.F, 0.F, 1.F}; + g_pHyprRenderer->m_renderPass.add(makeUnique(fgData)); + + CRectPassElement::SRectData progressData; + progressData.box = {secondRectX + 3, offsetY + NOTIFSIZE.y - 4, THIRDRECTPERC * std::max(0.0, NOTIFSIZE.x - 6.0), 2}; + progressData.color = notif->color; + g_pHyprRenderer->m_renderPass.add(makeUnique(progressData)); + + if (notif->icon != ICON_NONE && notif->cache.iconTex) { + CTexPassElement::SRenderData iconData; + iconData.tex = notif->cache.iconTex; + iconData.box = {secondRectX + NOTIF_LEFTBAR_SIZE + ICONPADFORNOTIF - 1, offsetY - 2 + std::round((NOTIFSIZE.y - ICONH) / 2.0), ICONW, ICONH}; + iconData.a = 1.F; + g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(iconData))); + } + + if (notif->cache.textTex) { + CTexPassElement::SRenderData textData; + textData.tex = notif->cache.textTex; + textData.box = {secondRectX + NOTIF_LEFTBAR_SIZE + ICONW + 2 * ICONPADFORNOTIF, offsetY - 2 + std::round((NOTIFSIZE.y - TEXTH) / 2.0), TEXTW, TEXTH}; + textData.a = 1.F; + g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(textData))); + } + + // adjust offset and move on + offsetY += NOTIFSIZE.y + NOTIF_GAP_Y; + + if (maxWidth < NOTIFSIZE.x) + maxWidth = NOTIFSIZE.x; + } + + // cleanup notifs + std::erase_if(m_notifications, [](const auto& notif) { return notif->started.getMillis() > notif->timeMs; }); + + return CBox{sc(pMonitor->m_position.x + pMonitor->m_size.x - maxWidth - NOTIF_DAMAGE_PAD_X), sc(pMonitor->m_position.y), sc(maxWidth + NOTIF_DAMAGE_PAD_X), + sc(offsetY + NOTIF_OFFSET_Y)}; +} + +void CNotificationOverlay::draw(PHLMONITOR pMonitor) { + // Draw the notifications + if (m_notifications.empty()) { + if (m_lastDamage.width > 0 && m_lastDamage.height > 0) + g_pHyprRenderer->damageBox(m_lastDamage); + m_lastDamage = {}; + return; + } + + CBox damage = drawNotifications(pMonitor); + + g_pHyprRenderer->damageBox(damage); + g_pHyprRenderer->damageBox(m_lastDamage); + + g_pCompositor->scheduleFrameForMonitor(pMonitor); + + m_lastDamage = damage; +} + +bool CNotificationOverlay::hasAny() { + return !m_notifications.empty(); +} diff --git a/src/notification/NotificationOverlay.hpp b/src/notification/NotificationOverlay.hpp new file mode 100644 index 000000000..b516cf22c --- /dev/null +++ b/src/notification/NotificationOverlay.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include "../defines.hpp" +#include "../helpers/time/Timer.hpp" +#include "../render/Texture.hpp" +#include "../SharedDefs.hpp" + +#include + +namespace Notification { + + enum eIconBackend : uint8_t { + ICONS_BACKEND_NONE = 0, + ICONS_BACKEND_NF, + ICONS_BACKEND_FA + }; + + 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}, + 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}, + CHyprColor{255 / 255.0, 204 / 255.0, 153 / 255.0, 1.0}, + CHyprColor{128 / 255.0, 255 / 255.0, 128 / 255.0, 1.0}, + CHyprColor{0, 0, 0, 1.0}}; + + struct SNotification { + struct SRenderCache { + SP textTex; + SP iconTex; + + Vector2D textSize = {}; + Vector2D iconSize = {}; + + PHLMONITORREF monitor; + std::string fontFamily; + int fontSizePx = -1; + eIconBackend iconBackend = ICONS_BACKEND_NONE; + } cache; + + std::string text = ""; + CHyprColor color; + CTimer started; + float timeMs = 0; + eIcons icon = ICON_NONE; + float fontSize = 13.f; + }; + + class CNotificationOverlay { + public: + CNotificationOverlay(); + ~CNotificationOverlay(); + + void draw(PHLMONITOR pMonitor); + void addNotification(const std::string& text, const CHyprColor& color, const float timeMs, const eIcons icon = ICON_NONE, const float fontSize = 13.f); + void dismissNotifications(const int amount); + bool hasAny(); + + private: + void ensureNotificationCache(SNotification& notif, PHLMONITOR pMonitor, const std::string& fontFamily); + eIconBackend iconBackendForFont(const std::string& fontFamily); + CBox drawNotifications(PHLMONITOR pMonitor); + CBox m_lastDamage; + + std::vector> m_notifications; + + std::string m_cachedIconBackendFontFamily; + eIconBackend m_cachedIconBackend = ICONS_BACKEND_NONE; + bool m_iconBackendValid = false; + }; + + UP& overlay(); +} \ No newline at end of file diff --git a/src/plugins/HookSystem.cpp b/src/plugins/HookSystem.cpp index 031b1def6..f17a4556d 100644 --- a/src/plugins/HookSystem.cpp +++ b/src/plugins/HookSystem.cpp @@ -1,5 +1,5 @@ #include "HookSystem.hpp" -#include "../debug/Log.hpp" +#include "../debug/log/Logger.hpp" #include "../helpers/varlist/VarList.hpp" #include "../managers/TokenManager.hpp" #include "../helpers/MiscFunctions.hpp" @@ -146,7 +146,7 @@ bool CFunctionHook::hook() { if (g_pFunctionHookSystem->m_activeHooks.contains(rc(m_source))) { // TODO: return actual error codes... - Debug::log(ERR, "[functionhook] failed, function is already hooked"); + Log::logger->log(Log::ERR, "[functionhook] failed, function is already hooked"); return false; } @@ -174,12 +174,12 @@ bool CFunctionHook::hook() { const auto PROBEFIXEDASM = fixInstructionProbeRIPCalls(probe); if (PROBEFIXEDASM.bytes.empty()) { - Debug::log(ERR, "[functionhook] failed, unsupported asm / failed assembling:\n{}", probe.assembly); + Log::logger->log(Log::ERR, "[functionhook] failed, unsupported asm / failed assembling:\n{}", probe.assembly); return false; } if (std::abs(rc(m_source) - rc(m_landTrampolineAddr)) > 2000000000 /* 2 GB */) { - Debug::log(ERR, "[functionhook] failed, source and trampo are over 2GB apart"); + Log::logger->log(Log::ERR, "[functionhook] failed, source and trampo are over 2GB apart"); return false; } @@ -189,7 +189,7 @@ bool CFunctionHook::hook() { const auto TRAMPOLINE_SIZE = sizeof(RELATIVE_JMP_ADDRESS) + HOOKSIZE; if (TRAMPOLINE_SIZE > MAX_TRAMPOLINE_SIZE) { - Debug::log(ERR, "[functionhook] failed, not enough space in trampo to alloc:\n{}", probe.assembly); + Log::logger->log(Log::ERR, "[functionhook] failed, not enough space in trampo to alloc:\n{}", probe.assembly); return false; } @@ -300,7 +300,7 @@ static uintptr_t seekNewPageAddr() { uint64_t start = 0, end = 0; if (props[0].empty()) { - Debug::log(WARN, "seekNewPageAddr: unexpected line in self maps"); + Log::logger->log(Log::WARN, "seekNewPageAddr: unexpected line in self maps"); continue; } @@ -310,11 +310,11 @@ static uintptr_t seekNewPageAddr() { start = std::stoull(startEnd[0], nullptr, 16); end = std::stoull(startEnd[1], nullptr, 16); } catch (std::exception& e) { - Debug::log(WARN, "seekNewPageAddr: unexpected line in self maps: {}", line); + Log::logger->log(Log::WARN, "seekNewPageAddr: unexpected line in self maps: {}", line); continue; } - Debug::log(LOG, "seekNewPageAddr: page 0x{:x} - 0x{:x}", start, end); + Log::logger->log(Log::DEBUG, "seekNewPageAddr: page 0x{:x} - 0x{:x}", start, end); if (lastStart == 0) { lastStart = start; @@ -323,17 +323,17 @@ static uintptr_t seekNewPageAddr() { } if (!anchoredToHyprland && line.contains("Hyprland")) { - Debug::log(LOG, "seekNewPageAddr: Anchored to hyprland at 0x{:x}", start); + Log::logger->log(Log::DEBUG, "seekNewPageAddr: Anchored to hyprland at 0x{:x}", start); anchoredToHyprland = true; } else if (start - lastEnd > PAGESIZE_VAR * 2) { if (!anchoredToHyprland) { - Debug::log(LOG, "seekNewPageAddr: skipping gap 0x{:x}-0x{:x}, not anchored to Hyprland code pages yet.", lastEnd, start); + Log::logger->log(Log::DEBUG, "seekNewPageAddr: skipping gap 0x{:x}-0x{:x}, not anchored to Hyprland code pages yet.", lastEnd, start); lastStart = start; lastEnd = end; continue; } - Debug::log(LOG, "seekNewPageAddr: found gap: 0x{:x}-0x{:x} ({} bytes)", lastEnd, start, start - lastEnd); + Log::logger->log(Log::DEBUG, "seekNewPageAddr: found gap: 0x{:x}-0x{:x} ({} bytes)", lastEnd, start, start - lastEnd); MAPS.close(); return lastEnd; } @@ -365,7 +365,7 @@ uint64_t CHookSystem::getAddressForTrampo() { if (!page->addr) { // allocate it - Debug::log(LOG, "getAddressForTrampo: Allocating new page for hooks"); + Log::logger->log(Log::DEBUG, "getAddressForTrampo: Allocating new page for hooks"); const uint64_t PAGESIZE_VAR = sysconf(_SC_PAGE_SIZE); const auto BASEPAGEADDR = seekNewPageAddr(); for (int attempt = 0; attempt < 2; ++attempt) { @@ -376,7 +376,7 @@ uint64_t CHookSystem::getAddressForTrampo() { page->len = PAGESIZE_VAR; page->used = 0; - Debug::log(LOG, "Attempted to allocate 0x{:x}, got 0x{:x}", PAGEADDR, page->addr); + Log::logger->log(Log::DEBUG, "Attempted to allocate 0x{:x}, got 0x{:x}", PAGEADDR, page->addr); if (page->addr == rc(MAP_FAILED)) continue; @@ -398,7 +398,7 @@ uint64_t CHookSystem::getAddressForTrampo() { page->used += HOOK_TRAMPOLINE_MAX_SIZE; - Debug::log(LOG, "getAddressForTrampo: Returning addr 0x{:x} for page at 0x{:x}", ADDRFORCONSUMER, page->addr); + Log::logger->log(Log::DEBUG, "getAddressForTrampo: Returning addr 0x{:x} for page at 0x{:x}", ADDRFORCONSUMER, page->addr); return ADDRFORCONSUMER; } diff --git a/src/plugins/PluginAPI.cpp b/src/plugins/PluginAPI.cpp index 66b0c74e3..4722924d2 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 "../config/legacy/ConfigManager.hpp" +#include "../notification/NotificationOverlay.hpp" +#include "../layout/target/Target.hpp" +#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp" #include #include @@ -37,9 +37,9 @@ APICALL SP HyprlandAPI::registerCallbackDynamic(HANDLE handle, if (!PLUGIN) return nullptr; - auto PFN = g_pHookSystem->hookDynamic(event, fn, handle); - PLUGIN->m_registeredCallbacks.emplace_back(std::make_pair<>(event, WP(PFN))); - return PFN; + //auto PFN = g_pHookSystem->hookDynamic(event, fn, handle); + //PLUGIN->m_registeredCallbacks.emplace_back(std::make_pair<>(event, WP(PFN))); + return nullptr; } APICALL bool HyprlandAPI::unregisterCallback(HANDLE handle, SP fn) { @@ -48,8 +48,8 @@ APICALL bool HyprlandAPI::unregisterCallback(HANDLE handle, SP if (!PLUGIN) return false; - g_pHookSystem->unhook(fn); - std::erase_if(PLUGIN->m_registeredCallbacks, [&](const auto& other) { return other.second.lock() == fn; }); + //g_pHookSystem->unhook(fn); + // std::erase_if(PLUGIN->m_registeredCallbacks, [&](const auto& other) { return other.second.lock() == fn; }); return true; } @@ -62,29 +62,48 @@ APICALL std::string HyprlandAPI::invokeHyprctlCommand(const std::string& call, c } APICALL bool HyprlandAPI::addLayout(HANDLE handle, const std::string& name, IHyprLayout* layout) { - auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle); - - if (!PLUGIN) - return false; - - PLUGIN->m_registeredLayouts.push_back(layout); - - return g_pLayoutManager->addLayout(name, layout); + return false; } APICALL bool HyprlandAPI::removeLayout(HANDLE handle, IHyprLayout* layout) { + return false; +} + +APICALL bool HyprlandAPI::addTiledAlgo(HANDLE handle, const std::string& name, const std::type_info* typeInfo, std::function()>&& factory) { auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle); if (!PLUGIN) return false; - std::erase(PLUGIN->m_registeredLayouts, layout); + PLUGIN->m_registeredAlgos.emplace_back(name); - return g_pLayoutManager->removeLayout(layout); + return Layout::Supplementary::algoMatcher()->registerTiledAlgo(name, typeInfo, std::move(factory)); +} + +APICALL bool HyprlandAPI::addFloatingAlgo(HANDLE handle, const std::string& name, const std::type_info* typeInfo, std::function()>&& factory) { + auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle); + + if (!PLUGIN) + return false; + + PLUGIN->m_registeredAlgos.emplace_back(name); + + return Layout::Supplementary::algoMatcher()->registerFloatingAlgo(name, typeInfo, std::move(factory)); +} + +APICALL bool HyprlandAPI::removeAlgo(HANDLE handle, const std::string& name) { + auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle); + + if (!PLUGIN) + return false; + + std::erase(PLUGIN->m_registeredAlgos, name); + + return Layout::Supplementary::algoMatcher()->unregisterAlgo(name); } APICALL bool HyprlandAPI::reloadConfig() { - g_pEventLoopManager->doLater([] { g_pConfigManager->reload(); }); + g_pEventLoopManager->doLater([] { Config::mgr()->reload(); }); return true; } @@ -94,7 +113,7 @@ APICALL bool HyprlandAPI::addNotification(HANDLE handle, const std::string& text if (!PLUGIN) return false; - g_pHyprNotificationOverlay->addNotification(text, color, timeMs); + Notification::overlay()->addNotification(text, color, timeMs); return true; } @@ -130,7 +149,7 @@ APICALL bool HyprlandAPI::addWindowDecoration(HANDLE handle, PHLWINDOW pWindow, pWindow->addWindowDeco(std::move(pDecoration)); - g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow); + pWindow->layoutTarget()->recalc(); return true; } @@ -165,7 +184,7 @@ APICALL bool HyprlandAPI::addConfigValue(HANDLE handle, const std::string& name, if (!name.starts_with("plugin:")) return false; - g_pConfigManager->addPluginConfigVar(handle, name, value); + Config::Legacy::mgr()->addPluginConfigVar(handle, name, value); return true; } @@ -178,7 +197,7 @@ APICALL bool HyprlandAPI::addConfigKeyword(HANDLE handle, const std::string& nam if (!PLUGIN) return false; - g_pConfigManager->addPluginKeyword(handle, name, fn, opts); + Config::Legacy::mgr()->addPluginKeyword(handle, name, fn, opts); return true; } @@ -189,9 +208,9 @@ APICALL Hyprlang::CConfigValue* HyprlandAPI::getConfigValue(HANDLE handle, const return nullptr; if (name.starts_with("plugin:")) - return g_pConfigManager->getHyprlangConfigValuePtr(name.substr(7), "plugin"); + return Config::Legacy::mgr()->getHyprlangConfigValuePtr(name.substr(7), "plugin"); - return g_pConfigManager->getHyprlangConfigValuePtr(name); + return Config::Legacy::mgr()->getHyprlangConfigValuePtr(name); } APICALL void* HyprlandAPI::getFunctionAddressFromSignature(HANDLE handle, const std::string& sig) { @@ -282,7 +301,7 @@ APICALL bool addNotificationV2(HANDLE handle, const std::unordered_map(iterator->second); - g_pHyprNotificationOverlay->addNotification(text, COLOR, TIME, icon); + Notification::overlay()->addNotification(text, COLOR, TIME, icon); } catch (std::exception& e) { // bad any_cast most likely, plugin error @@ -350,11 +369,11 @@ APICALL std::vector HyprlandAPI::findFunctionsByName(HANDLE hand }; if (SYMBOLS.empty()) { - Debug::log(ERR, R"(Unable to search for function "{}": no symbols found in binary (is "{}" in path?))", name, + Log::logger->log(Log::ERR, R"(Unable to search for function "{}": no symbols found in binary (is "{}" in path?))", name, #ifdef __clang__ - "llvm-nm" + "llvm-nm" #else - "nm" + "nm" #endif ); return {}; @@ -407,7 +426,7 @@ APICALL bool HyprlandAPI::unregisterHyprCtlCommand(HANDLE handle, SPm_registeredHyprctlCommands, cmd); + std::erase_if(PLUGIN->m_registeredHyprctlCommands, [&](const auto& other) { return !other || other == cmd; }); g_pHyprCtl->unregisterCommand(cmd); return true; diff --git a/src/plugins/PluginAPI.hpp b/src/plugins/PluginAPI.hpp index c88fe5869..77bb9926e 100644 --- a/src/plugins/PluginAPI.hpp +++ b/src/plugins/PluginAPI.hpp @@ -68,10 +68,16 @@ struct SVersionInfo { #endif class IHyprLayout; -class CWindow; class IHyprWindowDecoration; struct SConfigValue; -class CWindow; +class Hypr_dummyClass {}; + +namespace Layout { + class ITiledAlgorithm; + class IFloatingAlgorithm; +}; + +using HOOK_CALLBACK_FN = Hypr_dummyClass; /* These methods are for the plugin to implement @@ -145,6 +151,8 @@ namespace HyprlandAPI { APICALL Hyprlang::CConfigValue* getConfigValue(HANDLE handle, const std::string& name); /* + Deprecated: doesn't do anything anymore, use Event::bus() + Register a dynamic (function) callback to a selected event. Pointer will be free'd by Hyprland on unregisterCallback(). @@ -152,7 +160,7 @@ namespace HyprlandAPI { WARNING: Losing this pointer will unregister the callback! */ - APICALL [[nodiscard]] SP registerCallbackDynamic(HANDLE handle, const std::string& event, HOOK_CALLBACK_FN fn); + APICALL [[deprecated]] [[nodiscard]] SP registerCallbackDynamic(HANDLE handle, const std::string& event, HOOK_CALLBACK_FN fn); /* Unregisters a callback. If the callback was dynamic, frees the memory. @@ -174,15 +182,26 @@ namespace HyprlandAPI { Adds a layout to Hyprland. returns: true on success. False otherwise. + + deprecated: addTiledAlgo, addFloatingAlgo */ - APICALL bool addLayout(HANDLE handle, const std::string& name, IHyprLayout* layout); + APICALL [[deprecated]] bool addLayout(HANDLE handle, const std::string& name, IHyprLayout* layout); /* Removes an added layout from Hyprland. returns: true on success. False otherwise. + + deprecated: V2 removeAlgo */ - APICALL bool removeLayout(HANDLE handle, IHyprLayout* layout); + APICALL [[deprecated]] bool removeLayout(HANDLE handle, IHyprLayout* layout); + + /* + Algorithm fns. Used for registering and removing. Return success. + */ + APICALL bool addTiledAlgo(HANDLE handle, const std::string& name, const std::type_info* typeInfo, std::function()>&& factory); + APICALL bool addFloatingAlgo(HANDLE handle, const std::string& name, const std::type_info* typeInfo, std::function()>&& factory); + APICALL bool removeAlgo(HANDLE handle, const std::string& name); /* Queues a config reload. Does not take effect immediately. diff --git a/src/plugins/PluginSystem.cpp b/src/plugins/PluginSystem.cpp index 27e8232ec..18b6adde4 100644 --- a/src/plugins/PluginSystem.cpp +++ b/src/plugins/PluginSystem.cpp @@ -2,13 +2,12 @@ #include #include -#include "../config/ConfigManager.hpp" +#include "../config/legacy/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 "../notification/NotificationOverlay.hpp" +#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp" #include "../i18n/Engine.hpp" CPluginSystem::CPluginSystem() { @@ -25,7 +24,7 @@ SP> CPluginSystem::loadPlugin(const std::string& path, eSpeci return CPromise::make([path, pid, pidType, this](SP> resolver) { const auto PERM = g_pDynamicPermissionManager->clientPermissionModeWithString(pidType != SPECIAL_PID_TYPE_NONE ? pidType : pid, path, PERMISSION_TYPE_PLUGIN); if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) { - Debug::log(LOG, "CPluginSystem: Waiting for user confirmation to load {}", path); + Log::logger->log(Log::DEBUG, "CPluginSystem: Waiting for user confirmation to load {}", path); auto promise = g_pDynamicPermissionManager->promiseFor(pid, path, PERMISSION_TYPE_PLUGIN); if (!promise) { // already awaiting or something? @@ -35,18 +34,18 @@ SP> CPluginSystem::loadPlugin(const std::string& path, eSpeci promise->then([this, path, resolver](SP> result) { if (result->hasError()) { - Debug::log(ERR, "CPluginSystem: Error spawning permission prompt"); + Log::logger->log(Log::ERR, "CPluginSystem: Error spawning permission prompt"); resolver->reject("Error spawning permission prompt"); return; } if (result->result() != PERMISSION_RULE_ALLOW_MODE_ALLOW) { - Debug::log(ERR, "CPluginSystem: Rejecting plugin load of {}, user denied", path); + Log::logger->log(Log::ERR, "CPluginSystem: Rejecting plugin load of {}, user denied", path); resolver->reject("user denied"); return; } - Debug::log(LOG, "CPluginSystem: Loading {}, user allowed", path); + Log::logger->log(Log::DEBUG, "CPluginSystem: Loading {}, user allowed", path); const auto RESULT = loadPluginInternal(path); if (RESULT.has_value()) @@ -56,7 +55,7 @@ SP> CPluginSystem::loadPlugin(const std::string& path, eSpeci }); return; } else if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY) { - Debug::log(LOG, "CPluginSystem: Rejecting plugin load, permission is disabled"); + Log::logger->log(Log::DEBUG, "CPluginSystem: Rejecting plugin load, permission is disabled"); resolver->reject("permission is disabled"); return; } @@ -71,7 +70,7 @@ SP> CPluginSystem::loadPlugin(const std::string& path, eSpeci std::expected CPluginSystem::loadPluginInternal(const std::string& path) { if (getPluginByPath(path)) { - Debug::log(ERR, " [PluginSystem] Cannot load a plugin twice!"); + Log::logger->log(Log::ERR, " [PluginSystem] Cannot load a plugin twice!"); return std::unexpected("Cannot load a plugin twice!"); } @@ -83,7 +82,7 @@ std::expected CPluginSystem::loadPluginInternal(const std if (!MODULE) { std::string strerr = dlerror(); - Debug::log(ERR, " [PluginSystem] Plugin {} could not be loaded: {}", path, strerr); + Log::logger->log(Log::ERR, " [PluginSystem] Plugin {} could not be loaded: {}", path, strerr); m_loadedPlugins.pop_back(); return std::unexpected(std::format("Plugin {} could not be loaded: {}", path, strerr)); } @@ -94,7 +93,7 @@ std::expected CPluginSystem::loadPluginInternal(const std PPLUGIN_INIT_FUNC initFunc = rc(dlsym(MODULE, PLUGIN_INIT_FUNC_STR)); if (!apiVerFunc || !initFunc) { - Debug::log(ERR, " [PluginSystem] Plugin {} could not be loaded. (No apiver/init func)", path); + Log::logger->log(Log::ERR, " [PluginSystem] Plugin {} could not be loaded. (No apiver/init func)", path); dlclose(MODULE); m_loadedPlugins.pop_back(); return std::unexpected(std::format("Plugin {} could not be loaded: {}", path, "missing apiver/init func")); @@ -103,7 +102,7 @@ std::expected CPluginSystem::loadPluginInternal(const std const std::string PLUGINAPIVER = apiVerFunc(); if (PLUGINAPIVER != HYPRLAND_API_VERSION) { - Debug::log(ERR, " [PluginSystem] Plugin {} could not be loaded. (API version mismatch)", path); + Log::logger->log(Log::ERR, " [PluginSystem] Plugin {} could not be loaded. (API version mismatch)", path); dlclose(MODULE); m_loadedPlugins.pop_back(); return std::unexpected(std::format("Plugin {} could not be loaded: {}", path, "API version mismatch")); @@ -121,7 +120,7 @@ std::expected CPluginSystem::loadPluginInternal(const std } } catch (std::exception& e) { m_allowConfigVars = false; - Debug::log(ERR, " [PluginSystem] Plugin {} (Handle {:x}) crashed in init. Unloading.", path, rc(MODULE)); + Log::logger->log(Log::ERR, " [PluginSystem] Plugin {} (Handle {:x}) crashed in init. Unloading.", path, rc(MODULE)); unloadPlugin(PLUGIN, true); // Plugin could've already hooked/done something return std::unexpected(std::format("Plugin {} could not be loaded: plugin crashed/threw in main: {}", path, e.what())); } @@ -133,10 +132,10 @@ std::expected CPluginSystem::loadPluginInternal(const std PLUGIN->m_version = PLUGINDATA.version; PLUGIN->m_name = PLUGINDATA.name; - g_pEventLoopManager->doLater([] { g_pConfigManager->reload(); }); + g_pEventLoopManager->doLater([] { Config::mgr()->reload(); }); - Debug::log(LOG, R"( [PluginSystem] Plugin {} loaded. Handle: {:x}, path: "{}", author: "{}", description: "{}", version: "{}")", PLUGINDATA.name, rc(MODULE), path, - PLUGINDATA.author, PLUGINDATA.description, PLUGINDATA.version); + Log::logger->log(Log::DEBUG, R"( [PluginSystem] Plugin {} loaded. Handle: {:x}, path: "{}", author: "{}", description: "{}", version: "{}")", PLUGINDATA.name, + rc(MODULE), path, PLUGINDATA.author, PLUGINDATA.description, PLUGINDATA.version); return PLUGIN; } @@ -151,14 +150,14 @@ void CPluginSystem::unloadPlugin(const CPlugin* plugin, bool eject) { exitFunc(); } - for (auto const& [k, v] : plugin->m_registeredCallbacks) { - if (const auto SHP = v.lock()) - g_pHookSystem->unhook(SHP); - } + // for (auto const& [k, v] : plugin->m_registeredCallbacks) { + // if (const auto SHP = v.lock()) + // g_pHookSystem->unhook(SHP); + // } - const auto ls = plugin->m_registeredLayouts; - for (auto const& l : ls) - g_pLayoutManager->removeLayout(l); + for (const auto& l : plugin->m_registeredAlgos) { + Layout::Supplementary::algoMatcher()->unregisterAlgo(l); + } g_pFunctionHookSystem->removeAllHooksFrom(plugin->m_handle); @@ -171,10 +170,14 @@ void CPluginSystem::unloadPlugin(const CPlugin* plugin, bool eject) { HyprlandAPI::removeDispatcher(plugin->m_handle, d); const auto rhc = plugin->m_registeredHyprctlCommands; - for (auto const& c : rhc) - HyprlandAPI::unregisterHyprCtlCommand(plugin->m_handle, c); + for (auto const& c : rhc) { + if (const auto sp = c.lock()) + HyprlandAPI::unregisterHyprCtlCommand(plugin->m_handle, sp); + } - g_pConfigManager->removePluginConfig(plugin->m_handle); + // FIXME: this is wrong and if I forget to fix this by the time I add another config parser + // this will explode and I will be mad because I am a RETARD + Config::Legacy::mgr()->removePluginConfig(plugin->m_handle); // save these two for dlclose and a log, // as erase_if will kill the pointer @@ -185,10 +188,10 @@ void CPluginSystem::unloadPlugin(const CPlugin* plugin, bool eject) { dlclose(PLHANDLE); - Debug::log(LOG, " [PluginSystem] Plugin {} unloaded.", PLNAME); + Log::logger->log(Log::DEBUG, " [PluginSystem] Plugin {} unloaded.", PLNAME); // reload config to fix some stuf like e.g. unloadedPluginVars - g_pEventLoopManager->doLater([] { g_pConfigManager->reload(); }); + g_pEventLoopManager->doLater([] { Config::mgr()->reload(); }); } void CPluginSystem::unloadAllPlugins() { @@ -207,7 +210,7 @@ void CPluginSystem::updateConfigPlugins(const std::vector& plugins, if (!p->m_loadedWithConfig || std::ranges::find(plugins, p->m_path) != plugins.end()) continue; - Debug::log(LOG, "Unloading plugin {} which is no longer present in config", p->m_path); + Log::logger->log(Log::DEBUG, "Unloading plugin {} which is no longer present in config", p->m_path); unloadPlugin(p.get(), false); changed = true; } @@ -217,22 +220,22 @@ void CPluginSystem::updateConfigPlugins(const std::vector& plugins, if (std::ranges::find_if(m_loadedPlugins, [&](const auto& other) { return other->m_path == path; }) != m_loadedPlugins.end()) continue; - Debug::log(LOG, "Loading plugin {} which is now present in config", path); + Log::logger->log(Log::DEBUG, "Loading plugin {} which is now present in config", path); changed = true; loadPlugin(path, SPECIAL_PID_TYPE_CONFIG)->then([path](SP> result) { if (result->hasError()) { const auto NAME = path.contains('/') ? path.substr(path.find_last_of('/') + 1) : path; - Debug::log(ERR, "CPluginSystem::updateConfigPlugins: failed to load plugin {}: {}", NAME, result->error()); - g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, {{"name", NAME}, {"error", result->error()}}), - CHyprColor{0, 0, 0, 0}, 5000, ICON_ERROR); + Log::logger->log(Log::ERR, "CPluginSystem::updateConfigPlugins: failed to load plugin {}: {}", NAME, result->error()); + Notification::overlay()->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, {{"name", NAME}, {"error", result->error()}}), + CHyprColor{0, 0, 0, 0}, 5000, ICON_ERROR); return; } result->result()->m_loadedWithConfig = true; - Debug::log(LOG, "CPluginSystem::updateConfigPlugins: loaded {}", path); + Log::logger->log(Log::DEBUG, "CPluginSystem::updateConfigPlugins: loaded {}", path); }); } } diff --git a/src/plugins/PluginSystem.hpp b/src/plugins/PluginSystem.hpp index 7d062a9ba..85afaefa4 100644 --- a/src/plugins/PluginSystem.hpp +++ b/src/plugins/PluginSystem.hpp @@ -2,6 +2,7 @@ #include "../defines.hpp" #include "../helpers/defer/Promise.hpp" +#include "../helpers/time/Timer.hpp" #include "PluginAPI.hpp" #include "../managers/permissions/DynamicPermissionManager.hpp" #include @@ -11,22 +12,22 @@ class IHyprWindowDecoration; class CPlugin { public: - std::string m_name = ""; - std::string m_description = ""; - std::string m_author = ""; - std::string m_version = ""; + std::string m_name = ""; + std::string m_description = ""; + std::string m_author = ""; + std::string m_version = ""; - std::string m_path = ""; + std::string m_path = ""; - bool m_loadedWithConfig = false; + bool m_loadedWithConfig = false; - HANDLE m_handle = nullptr; + HANDLE m_handle = nullptr; - std::vector m_registeredLayouts; - std::vector m_registeredDecorations; - std::vector>> m_registeredCallbacks; - std::vector m_registeredDispatchers; - std::vector> m_registeredHyprctlCommands; + std::vector m_registeredDecorations; + //std::vector>> m_registeredCallbacks; + std::vector m_registeredDispatchers; + std::vector> m_registeredHyprctlCommands; + std::vector m_registeredAlgos; }; class CPluginSystem { diff --git a/src/protocols/AlphaModifier.cpp b/src/protocols/AlphaModifier.cpp index a9c09d979..a4ebc635b 100644 --- a/src/protocols/AlphaModifier.cpp +++ b/src/protocols/AlphaModifier.cpp @@ -1,5 +1,5 @@ #include "AlphaModifier.hpp" -#include "../desktop/WLSurface.hpp" +#include "../desktop/view/WLSurface.hpp" #include "../render/Renderer.hpp" #include "alpha-modifier-v1.hpp" #include "core/Compositor.hpp" @@ -31,7 +31,7 @@ void CAlphaModifier::setResource(UP&& resource) { }); m_listeners.surfaceCommitted = m_surface->m_events.commit.listen([this] { - auto surface = CWLSurface::fromResource(m_surface.lock()); + auto surface = Desktop::View::CWLSurface::fromResource(m_surface.lock()); if (surface && surface->m_alphaModifier != m_alpha) { surface->m_alphaModifier = m_alpha; @@ -85,7 +85,7 @@ void CAlphaModifierProtocol::getSurface(CWpAlphaModifierV1* manager, uint32_t id if (iter != m_alphaModifiers.end()) { if (iter->second->m_resource) { - LOGM(ERR, "AlphaModifier already present for surface {:x}", (uintptr_t)surface.get()); + LOGM(Log::ERR, "AlphaModifier already present for surface {:x}", (uintptr_t)surface.get()); manager->error(WP_ALPHA_MODIFIER_V1_ERROR_ALREADY_CONSTRUCTED, "AlphaModifier already present"); return; } else { diff --git a/src/protocols/CTMControl.cpp b/src/protocols/CTMControl.cpp index 9c2419422..3a590cdea 100644 --- a/src/protocols/CTMControl.cpp +++ b/src/protocols/CTMControl.cpp @@ -3,7 +3,7 @@ #include "../render/Renderer.hpp" #include "core/Output.hpp" #include "../config/ConfigValue.hpp" -#include "../config/ConfigManager.hpp" +#include "../config/shared/animation/AnimationTree.hpp" #include "managers/animation/AnimationManager.hpp" #include "../helpers/Monitor.hpp" #include "../helpers/MiscFunctions.hpp" @@ -42,14 +42,14 @@ CHyprlandCTMControlResource::CHyprlandCTMControlResource(UPm_name] = MAT; - LOGM(LOG, "CTM set for output {}: {}", PMONITOR->m_name, m_ctms.at(PMONITOR->m_name).toString()); + LOGM(Log::DEBUG, "CTM set for output {}: {}", PMONITOR->m_name, m_ctms.at(PMONITOR->m_name).toString()); }); m_resource->setCommit([this](CHyprlandCtmControlManagerV1* r) { if (m_blocked) return; - LOGM(LOG, "Committing ctms to outputs"); + LOGM(Log::DEBUG, "Committing ctms to outputs"); for (auto& m : g_pCompositor->m_monitors) { if (!m_ctms.contains(m->m_name)) { @@ -100,7 +100,7 @@ void CHyprlandCTMControlProtocol::bindManager(wl_client* client, void* data, uin else m_manager = RESOURCE; - LOGM(LOG, "New CTM Manager at 0x{:x}", (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New CTM Manager at 0x{:x}", (uintptr_t)RESOURCE.get()); } void CHyprlandCTMControlProtocol::destroyResource(CHyprlandCTMControlResource* res) { @@ -120,7 +120,7 @@ bool CHyprlandCTMControlProtocol::isCTMAnimationEnabled() { } CHyprlandCTMControlProtocol::SCTMData::SCTMData() { - g_pAnimationManager->createAnimation(0.f, progress, g_pConfigManager->getAnimationPropertyConfig("__internal_fadeCTM"), AVARDAMAGE_NONE); + g_pAnimationManager->createAnimation(0.f, progress, Config::animationTree()->getAnimationPropertyConfig("__internal_fadeCTM"), AVARDAMAGE_NONE); } void CHyprlandCTMControlProtocol::setCTM(PHLMONITOR monitor, const Mat3x3& ctm) { diff --git a/src/protocols/ColorManagement.cpp b/src/protocols/ColorManagement.cpp index 408ade4d3..fce91691b 100644 --- a/src/protocols/ColorManagement.cpp +++ b/src/protocols/ColorManagement.cpp @@ -3,7 +3,7 @@ #include "color-management-v1.hpp" #include "../helpers/Monitor.hpp" #include "core/Output.hpp" -#include "types/ColorManagement.hpp" +#include "../helpers/cm/ColorManagement.hpp" #include using namespace NColorManagement; @@ -18,12 +18,12 @@ CColorManager::CColorManager(SP resource) : m_resource(resour m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_SET_PRIMARIES); m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_SET_LUMINANCES); m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_WINDOWS_SCRGB); + m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES); + m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_EXTENDED_TARGET_VOLUME); if (PROTO::colorManagement->m_debug) { m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_ICC_V2_V4); m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_SET_TF_POWER); - m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES); - m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_EXTENDED_TARGET_VOLUME); } m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_SRGB); @@ -35,12 +35,14 @@ CColorManager::CColorManager(SP resource) : m_resource(resour m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_DCI_P3); m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_DISPLAY_P3); m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_ADOBE_RGB); + m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_CIE1931_XYZ); - if (PROTO::colorManagement->m_debug) { - m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_CIE1931_XYZ); - } + if (m_resource->version() == 1) { + m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB); + m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_SRGB); + } else + m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_COMPOUND_POWER_2_4); - m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB); m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22); m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA28); m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR); @@ -51,7 +53,6 @@ CColorManager::CColorManager(SP resource) : m_resource(resour m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_100); m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_316); m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_XVYCC); - m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_SRGB); m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST428); m_resource->sendSupportedIntent(WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL); @@ -60,11 +61,13 @@ CColorManager::CColorManager(SP resource) : m_resource(resour m_resource->sendSupportedIntent(WP_COLOR_MANAGER_V1_RENDER_INTENT_SATURATION); m_resource->sendSupportedIntent(WP_COLOR_MANAGER_V1_RENDER_INTENT_ABSOLUTE); m_resource->sendSupportedIntent(WP_COLOR_MANAGER_V1_RENDER_INTENT_RELATIVE_BPC); + if (m_resource->version() > 1) + m_resource->sendSupportedIntent(WP_COLOR_MANAGER_V1_RENDER_INTENT_ABSOLUTE_NO_ADAPTATION); } - m_resource->setDestroy([](CWpColorManagerV1* r) { LOGM(TRACE, "Destroy WP_color_manager at {:x} (generated default)", (uintptr_t)r); }); + m_resource->setDestroy([](CWpColorManagerV1* r) { LOGM(Log::TRACE, "Destroy WP_color_manager at {:x} (generated default)", (uintptr_t)r); }); m_resource->setGetOutput([](CWpColorManagerV1* r, uint32_t id, wl_resource* output) { - LOGM(TRACE, "Get output for id={}, output={}", id, (uintptr_t)output); + LOGM(Log::TRACE, "Get output for id={}, output={}", id, (uintptr_t)output); const auto OUTPUTRESOURCE = CWLOutputResource::fromResource(output); @@ -80,11 +83,11 @@ CColorManager::CColorManager(SP resource) : m_resource(resour RESOURCE->m_self = RESOURCE; }); m_resource->setGetSurface([](CWpColorManagerV1* r, uint32_t id, wl_resource* surface) { - LOGM(TRACE, "Get surface for id={}, surface={}", id, (uintptr_t)surface); + LOGM(Log::TRACE, "Get surface for id={}, surface={}", id, (uintptr_t)surface); auto SURF = CWLSurfaceResource::fromResource(surface); if (!SURF) { - LOGM(ERR, "No surface for resource {}", (uintptr_t)surface); + LOGM(Log::ERR, "No surface for resource {}", (uintptr_t)surface); r->error(-1, "Invalid surface (2)"); return; } @@ -107,11 +110,11 @@ CColorManager::CColorManager(SP resource) : m_resource(resour SURF->m_colorManagement = RESOURCE; }); m_resource->setGetSurfaceFeedback([](CWpColorManagerV1* r, uint32_t id, wl_resource* surface) { - LOGM(TRACE, "Get feedback surface for id={}, surface={}", id, (uintptr_t)surface); + LOGM(Log::TRACE, "Get feedback surface for id={}, surface={}", id, (uintptr_t)surface); auto SURF = CWLSurfaceResource::fromResource(surface); if (!SURF) { - LOGM(ERR, "No surface for resource {}", (uintptr_t)surface); + LOGM(Log::ERR, "No surface for resource {}", (uintptr_t)surface); r->error(-1, "Invalid surface (2)"); return; } @@ -128,7 +131,7 @@ CColorManager::CColorManager(SP resource) : m_resource(resour RESOURCE->m_self = RESOURCE; }); m_resource->setCreateIccCreator([](CWpColorManagerV1* r, uint32_t id) { - LOGM(WARN, "New ICC creator for id={} (unsupported)", id); + LOGM(Log::WARN, "New ICC creator for id={} (unsupported)", id); if (!PROTO::colorManagement->m_debug) { r->error(WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, "ICC profiles are not supported"); return; @@ -146,7 +149,7 @@ CColorManager::CColorManager(SP resource) : m_resource(resour RESOURCE->m_self = RESOURCE; }); m_resource->setCreateParametricCreator([](CWpColorManagerV1* r, uint32_t id) { - LOGM(TRACE, "New parametric creator for id={}", id); + LOGM(Log::TRACE, "New parametric creator for id={}", id); const auto RESOURCE = PROTO::colorManagement->m_parametricCreators.emplace_back( makeShared(makeShared(r->client(), r->version(), id))); @@ -160,7 +163,7 @@ CColorManager::CColorManager(SP resource) : m_resource(resour RESOURCE->m_self = RESOURCE; }); m_resource->setCreateWindowsScrgb([](CWpColorManagerV1* r, uint32_t id) { - LOGM(WARN, "New Windows scRGB description id={}", id); + LOGM(Log::WARN, "New Windows scRGB description id={}", id); const auto RESOURCE = PROTO::colorManagement->m_imageDescriptions.emplace_back( makeShared(makeShared(r->client(), r->version(), id), false)); @@ -171,14 +174,35 @@ CColorManager::CColorManager(SP resource) : m_resource(resour return; } - RESOURCE->m_self = RESOURCE; - RESOURCE->m_settings.windowsScRGB = true; - RESOURCE->m_settings.primariesNamed = NColorManagement::CM_PRIMARIES_SRGB; - RESOURCE->m_settings.primariesNameSet = true; - RESOURCE->m_settings.primaries = NColorPrimaries::BT709; - RESOURCE->m_settings.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_EXT_LINEAR; - RESOURCE->m_settings.luminances.reference = 203; - RESOURCE->resource()->sendReady(RESOURCE->m_settings.updateId()); + RESOURCE->m_self = RESOURCE; + RESOURCE->m_settings = SCRGB_IMAGE_DESCRIPTION; + + RESOURCE->sendMaybeReady(); + }); + + m_resource->setGetImageDescription([](CWpColorManagerV1* r, uint32_t id, wl_resource* ref) { + LOGM(Log::TRACE, "Get image description for reference={}, id={}", (uintptr_t)ref, id); + + const auto OLD_RES = CColorManagementImageDescription::fromReference(ref); + if (!OLD_RES) { + OLD_RES->resource()->sendFailed(WP_IMAGE_DESCRIPTION_V1_CAUSE_UNSUPPORTED, "Not found"); + return; + } + + const auto RESOURCE = PROTO::colorManagement->m_imageDescriptions.emplace_back( + makeShared(makeShared(r->client(), r->version(), id), OLD_RES->m_allowGetInformation)); + + if UNLIKELY (!RESOURCE->good()) { + r->noMemory(); + PROTO::colorManagement->m_imageDescriptions.pop_back(); + return; + } + + RESOURCE->m_self = RESOURCE; + + RESOURCE->m_settings = OLD_RES->m_settings; + + RESOURCE->sendMaybeReady(); }); m_resource->setOnDestroy([this](CWpColorManagerV1* r) { PROTO::colorManagement->destroyResource(this); }); @@ -204,7 +228,7 @@ CColorManagementOutput::CColorManagementOutput(SP re m_resource->setOnDestroy([this](CWpColorManagementOutputV1* r) { PROTO::colorManagement->destroyResource(this); }); m_resource->setGetImageDescription([this](CWpColorManagementOutputV1* r, uint32_t id) { - LOGM(TRACE, "Get image description for output={}, id={}", (uintptr_t)r, id); + LOGM(Log::TRACE, "Get image description for output={}, id={}", (uintptr_t)r, id); if (m_imageDescription.valid()) PROTO::colorManagement->destroyResource(m_imageDescription.get()); @@ -223,7 +247,8 @@ CColorManagementOutput::CColorManagementOutput(SP re RESOURCE->m_resource->sendFailed(WP_IMAGE_DESCRIPTION_V1_CAUSE_NO_OUTPUT, "No output"); else { RESOURCE->m_settings = m_output->m_monitor->m_imageDescription; - RESOURCE->m_resource->sendReady(RESOURCE->m_settings.updateId()); + + RESOURCE->sendMaybeReady(); } }); } @@ -236,27 +261,24 @@ wl_client* CColorManagementOutput::client() { return m_client; } -CColorManagementSurface::CColorManagementSurface(SP surface_) : m_surface(surface_) { - // only for frog cm until wayland cm is adopted -} - CColorManagementSurface::CColorManagementSurface(SP resource, SP surface_) : m_surface(surface_), m_resource(resource) { if UNLIKELY (!good()) return; - m_client = m_resource->client(); + m_client = m_resource->client(); + m_imageDescription = getDefaultImageDescription(); m_resource->setDestroy([this](CWpColorManagementSurfaceV1* r) { - LOGM(TRACE, "Destroy wp cm surface {}", (uintptr_t)m_surface); + LOGM(Log::TRACE, "Destroy wp cm surface {}", (uintptr_t)m_surface); PROTO::colorManagement->destroyResource(this); }); m_resource->setOnDestroy([this](CWpColorManagementSurfaceV1* r) { - LOGM(TRACE, "Destroy wp cm surface {}", (uintptr_t)m_surface); + LOGM(Log::TRACE, "Destroy wp cm surface {}", (uintptr_t)m_surface); PROTO::colorManagement->destroyResource(this); }); m_resource->setSetImageDescription([this](CWpColorManagementSurfaceV1* r, wl_resource* image_description, uint32_t render_intent) { - LOGM(TRACE, "Set image description for surface={}, desc={}, intent={}", (uintptr_t)r, (uintptr_t)image_description, render_intent); + LOGM(Log::TRACE, "Set image description for surface={}, desc={}, intent={}", (uintptr_t)r, (uintptr_t)image_description, render_intent); const auto PO = sc(wl_resource_get_user_data(image_description)); if (!PO) { // FIXME check validity @@ -279,8 +301,8 @@ CColorManagementSurface::CColorManagementSurface(SP m_imageDescription = imageDescription->get()->m_settings; }); m_resource->setUnsetImageDescription([this](CWpColorManagementSurfaceV1* r) { - LOGM(TRACE, "Unset image description for surface={}", (uintptr_t)r); - m_imageDescription = SImageDescription{}; + LOGM(Log::TRACE, "Unset image description for surface={}", (uintptr_t)r); + m_imageDescription = getDefaultImageDescription(); setHasImageDescription(false); }); } @@ -294,9 +316,12 @@ wl_client* CColorManagementSurface::client() { } const SImageDescription& CColorManagementSurface::imageDescription() { - if (!hasImageDescription()) - LOGM(WARN, "Reading imageDescription while none set. Returns default or empty values"); - return m_imageDescription; + if (!hasImageDescription()) { + LOGM(Log::TRACE, "Reading imageDescription while none set. Returns default or empty values"); + return getDefaultImageDescription()->value(); // JIC default settings change + } + + return m_imageDescription->value(); } bool CColorManagementSurface::hasImageDescription() { @@ -327,13 +352,14 @@ bool CColorManagementSurface::needsHdrMetadataUpdate() { } bool CColorManagementSurface::isHDR() { - return m_imageDescription.transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ || m_imageDescription.transferFunction == CM_TRANSFER_FUNCTION_HLG || isWindowsScRGB(); + return m_imageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ || m_imageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_HLG || + isWindowsScRGB(); } bool CColorManagementSurface::isWindowsScRGB() { - return m_imageDescription.windowsScRGB || + return m_imageDescription->value().windowsScRGB || // autodetect scRGB, might be incorrect - (m_imageDescription.primariesNamed == CM_PRIMARIES_SRGB && m_imageDescription.transferFunction == CM_TRANSFER_FUNCTION_EXT_LINEAR); + (m_imageDescription->value().primariesNamed == CM_PRIMARIES_SRGB && m_imageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_EXT_LINEAR); } CColorManagementFeedbackSurface::CColorManagementFeedbackSurface(SP resource, SP surface_) : @@ -344,16 +370,16 @@ CColorManagementFeedbackSurface::CColorManagementFeedbackSurface(SPclient(); m_resource->setDestroy([this](CWpColorManagementSurfaceFeedbackV1* r) { - LOGM(TRACE, "Destroy wp cm feedback surface {}", (uintptr_t)m_surface); + LOGM(Log::TRACE, "Destroy wp cm feedback surface {}", (uintptr_t)m_surface); PROTO::colorManagement->destroyResource(this); }); m_resource->setOnDestroy([this](CWpColorManagementSurfaceFeedbackV1* r) { - LOGM(TRACE, "Destroy wp cm feedback surface {}", (uintptr_t)m_surface); + LOGM(Log::TRACE, "Destroy wp cm feedback surface {}", (uintptr_t)m_surface); PROTO::colorManagement->destroyResource(this); }); m_resource->setGetPreferred([this](CWpColorManagementSurfaceFeedbackV1* r, uint32_t id) { - LOGM(TRACE, "Get preferred for id {}", id); + LOGM(Log::TRACE, "Get preferred for id {}", id); if (m_surface.expired()) { r->error(WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_ERROR_INERT, "Surface is inert"); @@ -372,11 +398,11 @@ CColorManagementFeedbackSurface::CColorManagementFeedbackSurface(SPm_self = RESOURCE; RESOURCE->m_settings = m_surface->getPreferredImageDescription(); - RESOURCE->resource()->sendReady(RESOURCE->m_settings.updateId()); + RESOURCE->sendMaybeReady(); }); m_resource->setGetPreferredParametric([this](CWpColorManagementSurfaceFeedbackV1* r, uint32_t id) { - LOGM(TRACE, "Get preferred for id {}", id); + LOGM(Log::TRACE, "Get preferred for id {}", id); if (m_surface.expired()) { r->error(WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_ERROR_INERT, "Surface is inert"); @@ -394,15 +420,15 @@ CColorManagementFeedbackSurface::CColorManagementFeedbackSurface(SPm_self = RESOURCE; RESOURCE->m_settings = m_surface->getPreferredImageDescription(); - m_currentPreferredId = RESOURCE->m_settings.updateId(); + m_currentPreferredId = RESOURCE->m_settings->id(); - if (!PROTO::colorManagement->m_debug && RESOURCE->m_settings.icc.fd >= 0) { - LOGM(ERR, "FIXME: parse icc profile"); - r->error(WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, "ICC profiles are not supported"); - return; + if (!PROTO::colorManagement->m_debug && RESOURCE->m_settings->value().icc.present) { + LOGM(Log::ERR, "FIXME: send ICC profile data"); + // r->error(WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, "ICC profiles are not supported"); + // return; } - RESOURCE->resource()->sendReady(m_currentPreferredId); + RESOURCE->sendMaybeReady(); }); m_listeners.enter = m_surface->m_events.enter.listen([this](const auto& monitor) { onPreferredChanged(); }); @@ -411,9 +437,15 @@ CColorManagementFeedbackSurface::CColorManagementFeedbackSurface(SPm_enteredOutputs.size() == 1) { - const auto newId = m_surface->getPreferredImageDescription().updateId(); - if (m_currentPreferredId != newId) - m_resource->sendPreferredChanged(newId); + const auto newId = m_surface->getPreferredImageDescription()->id(); + if (m_currentPreferredId != newId) { + const uint32_t lo = sc(newId & 0xFFFFFFFF); + const uint32_t hi = sc(newId >> 32); + if (m_resource->version() > 1) + m_resource->sendPreferredChanged2(hi, lo); + else if (!hi) + m_resource->sendPreferredChanged(lo); + } } } @@ -434,10 +466,10 @@ CColorManagementIccCreator::CColorManagementIccCreator(SPsetOnDestroy([this](CWpImageDescriptionCreatorIccV1* r) { PROTO::colorManagement->destroyResource(this); }); m_resource->setCreate([this](CWpImageDescriptionCreatorIccV1* r, uint32_t id) { - LOGM(TRACE, "Create image description from icc for id {}", id); + LOGM(Log::TRACE, "Create image description from icc for id {}", id); // FIXME actually check completeness - if (m_settings.icc.fd < 0 || !m_settings.icc.length) { + if (m_icc.fd < 0 || !m_icc.length) { r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INCOMPLETE_SET, "Missing required settings"); return; } @@ -451,25 +483,26 @@ CColorManagementIccCreator::CColorManagementIccCreator(SPresource()->sendFailed(WP_IMAGE_DESCRIPTION_V1_CAUSE_UNSUPPORTED, "unsupported"); return; } RESOURCE->m_self = RESOURCE; - RESOURCE->m_settings = m_settings; - RESOURCE->resource()->sendReady(m_settings.updateId()); + RESOURCE->m_settings = CImageDescription::from(m_settings); + + RESOURCE->sendMaybeReady(); PROTO::colorManagement->destroyResource(this); }); m_resource->setSetIccFile([this](CWpImageDescriptionCreatorIccV1* r, int fd, uint32_t offset, uint32_t length) { - m_settings.icc.fd = fd; - m_settings.icc.offset = offset; - m_settings.icc.length = length; + m_icc.fd = fd; + m_icc.offset = offset; + m_icc.length = length; }); } @@ -490,7 +523,7 @@ CColorManagementParametricCreator::CColorManagementParametricCreator(SPsetOnDestroy([this](CWpImageDescriptionCreatorParamsV1* r) { PROTO::colorManagement->destroyResource(this); }); m_resource->setCreate([this](CWpImageDescriptionCreatorParamsV1* r, uint32_t id) { - LOGM(TRACE, "Create image description from params for id {}", id); + LOGM(Log::TRACE, "Create image description from params for id {}", id); // FIXME actually check completeness if (!m_valuesSet) { @@ -513,14 +546,23 @@ CColorManagementParametricCreator::CColorManagementParametricCreator(SPm_self = RESOURCE; - RESOURCE->m_settings = m_settings; - RESOURCE->resource()->sendReady(m_settings.updateId()); + RESOURCE->m_settings = CImageDescription::from(m_settings); + + RESOURCE->sendMaybeReady(); PROTO::colorManagement->destroyResource(this); }); m_resource->setSetTfNamed([this](CWpImageDescriptionCreatorParamsV1* r, uint32_t tf) { - LOGM(TRACE, "Set image description transfer function to {}", tf); + LOGM(Log::TRACE, "Set image description transfer function to {}", tf); if (m_valuesSet & PC_TF) { r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, "Transfer function already set"); return; @@ -540,6 +582,7 @@ CColorManagementParametricCreator::CColorManagementParametricCreator(SPerror(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_TF, "Unsupported transfer function"); return; } @@ -547,7 +590,7 @@ CColorManagementParametricCreator::CColorManagementParametricCreator(SPsetSetTfPower([this](CWpImageDescriptionCreatorParamsV1* r, uint32_t eexp) { - LOGM(TRACE, "Set image description tf power to {}", eexp); + LOGM(Log::TRACE, "Set image description tf power to {}", eexp); if (m_valuesSet & PC_TF_POWER) { r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, "Transfer function power already set"); return; @@ -564,7 +607,7 @@ CColorManagementParametricCreator::CColorManagementParametricCreator(SPsetSetPrimariesNamed([this](CWpImageDescriptionCreatorParamsV1* r, uint32_t primaries) { - LOGM(TRACE, "Set image description primaries by name {}", primaries); + LOGM(Log::TRACE, "Set image description primaries by name {}", primaries); if (m_valuesSet & PC_PRIMARIES) { r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, "Primaries already set"); return; @@ -577,6 +620,7 @@ CColorManagementParametricCreator::CColorManagementParametricCreator(SPsetSetPrimaries( [this](CWpImageDescriptionCreatorParamsV1* r, int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) { - LOGM(TRACE, "Set image description primaries by values r:{},{} g:{},{} b:{},{} w:{},{}", r_x, r_y, g_x, g_y, b_x, b_y, w_x, w_y); + LOGM(Log::TRACE, "Set image description primaries by values r:{},{} g:{},{} b:{},{} w:{},{}", r_x, r_y, g_x, g_y, b_x, b_y, w_x, w_y); if (m_valuesSet & PC_PRIMARIES) { r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, "Primaries already set"); return; @@ -608,7 +652,7 @@ CColorManagementParametricCreator::CColorManagementParametricCreator(SPsetSetLuminances([this](CWpImageDescriptionCreatorParamsV1* r, uint32_t min_lum, uint32_t max_lum, uint32_t reference_lum) { auto min = min_lum / 10000.0f; - LOGM(TRACE, "Set image description luminances to {} - {} ({})", min, max_lum, reference_lum); + LOGM(Log::TRACE, "Set image description luminances to {} - {} ({})", min, max_lum, reference_lum); if (m_valuesSet & PC_LUMINANCES) { r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, "Luminances already set"); return; @@ -622,15 +666,12 @@ CColorManagementParametricCreator::CColorManagementParametricCreator(SPsetSetMasteringDisplayPrimaries( [this](CWpImageDescriptionCreatorParamsV1* r, int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) { - LOGM(TRACE, "Set image description mastering primaries by values r:{},{} g:{},{} b:{},{} w:{},{}", r_x, r_y, g_x, g_y, b_x, b_y, w_x, w_y); + LOGM(Log::TRACE, "Set image description mastering primaries by values r:{},{} g:{},{} b:{},{} w:{},{}", r_x, r_y, g_x, g_y, b_x, b_y, w_x, w_y); if (m_valuesSet & PC_MASTERING_PRIMARIES) { r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, "Mastering primaries already set"); return; } - if (!PROTO::colorManagement->m_debug) { - r->error(WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, "Mastering primaries are not supported"); - return; - } + m_settings.masteringPrimaries = SPCPRimaries{.red = {.x = r_x / PRIMARIES_SCALE, .y = r_y / PRIMARIES_SCALE}, .green = {.x = g_x / PRIMARIES_SCALE, .y = g_y / PRIMARIES_SCALE}, .blue = {.x = b_x / PRIMARIES_SCALE, .y = b_y / PRIMARIES_SCALE}, @@ -644,7 +685,7 @@ CColorManagementParametricCreator::CColorManagementParametricCreator(SPsetSetMasteringLuminance([this](CWpImageDescriptionCreatorParamsV1* r, uint32_t min_lum, uint32_t max_lum) { auto min = min_lum / 10000.0f; - LOGM(TRACE, "Set image description mastering luminances to {} - {}", min, max_lum); + LOGM(Log::TRACE, "Set image description mastering luminances to {} - {}", min, max_lum); // if (valuesSet & PC_MASTERING_LUMINANCES) { // r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, "Mastering luminances already set"); // return; @@ -653,15 +694,12 @@ CColorManagementParametricCreator::CColorManagementParametricCreator(SPerror(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_LUMINANCE, "Invalid luminances"); return; } - if (!PROTO::colorManagement->m_debug) { - r->error(WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, "Mastering luminances are not supported"); - return; - } + m_settings.masteringLuminances = SImageDescription::SPCMasteringLuminances{.min = min, .max = max_lum}; m_valuesSet |= PC_MASTERING_LUMINANCES; }); m_resource->setSetMaxCll([this](CWpImageDescriptionCreatorParamsV1* r, uint32_t max_cll) { - LOGM(TRACE, "Set image description max content light level to {}", max_cll); + LOGM(Log::TRACE, "Set image description max content light level to {}", max_cll); // if (valuesSet & PC_CLL) { // r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, "Max CLL already set"); // return; @@ -670,7 +708,7 @@ CColorManagementParametricCreator::CColorManagementParametricCreator(SPsetSetMaxFall([this](CWpImageDescriptionCreatorParamsV1* r, uint32_t max_fall) { - LOGM(TRACE, "Set image description max frame-average light level to {}", max_fall); + LOGM(Log::TRACE, "Set image description max frame-average light level to {}", max_fall); // if (valuesSet & PC_FALL) { // r->error(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, "Max FALL already set"); // return; @@ -699,13 +737,13 @@ CColorManagementImageDescription::CColorManagementImageDescription(SPsetOnDestroy([this](CWpImageDescriptionV1* r) { PROTO::colorManagement->destroyResource(this); }); m_resource->setGetInformation([this](CWpImageDescriptionV1* r, uint32_t id) { - LOGM(TRACE, "Get image information for image={}, id={}", (uintptr_t)r, id); + LOGM(Log::TRACE, "Get image information for image={}, id={}", (uintptr_t)r, id); if (!m_allowGetInformation) { r->error(WP_IMAGE_DESCRIPTION_V1_ERROR_NO_INFORMATION, "Image descriptions doesn't allow get_information request"); return; } - auto RESOURCE = makeShared(makeShared(r->client(), r->version(), id), m_settings); + auto RESOURCE = makeShared(makeShared(r->client(), r->version(), id), m_settings->value()); if UNLIKELY (!RESOURCE->good()) r->noMemory(); @@ -715,6 +753,11 @@ CColorManagementImageDescription::CColorManagementImageDescription(SP CColorManagementImageDescription::fromReference(wl_resource* res) { + auto data = sc(sc(wl_resource_get_user_data(res))->data()); + return data ? data->m_self.lock() : nullptr; +} + bool CColorManagementImageDescription::good() { return m_resource->resource(); } @@ -727,6 +770,22 @@ SP CColorManagementImageDescription::resource() { return m_resource; } +bool CColorManagementImageDescription::sendMaybeReady() { + const uint32_t lo = sc(m_settings->id() & 0xFFFFFFFF); + const uint32_t hi = sc(m_settings->id() >> 32); + + if (m_resource->version() > 1) + m_resource->sendReady2(hi, lo); + else if (!hi) + m_resource->sendReady(lo); + else { + m_resource->sendFailed(WP_IMAGE_DESCRIPTION_V1_CAUSE_LOW_VERSION, "id is too large"); + return false; + } + + return true; +} + CColorManagementImageDescriptionInfo::CColorManagementImageDescriptionInfo(SP resource, const SImageDescription& settings_) : m_resource(resource), m_settings(settings_) { if UNLIKELY (!good()) @@ -736,26 +795,47 @@ CColorManagementImageDescriptionInfo::CColorManagementImageDescriptionInfo(SP(std::round(value * PRIMARIES_SCALE)); }; - if (m_settings.icc.fd >= 0) - m_resource->sendIccFile(m_settings.icc.fd, m_settings.icc.length); + // FIXME: + // if (m_icc.fd >= 0) + // m_resource->sendIccFile(m_icc.fd, m_icc.length); // send preferred client paramateres m_resource->sendPrimaries(toProto(m_settings.primaries.red.x), toProto(m_settings.primaries.red.y), toProto(m_settings.primaries.green.x), toProto(m_settings.primaries.green.y), toProto(m_settings.primaries.blue.x), toProto(m_settings.primaries.blue.y), toProto(m_settings.primaries.white.x), toProto(m_settings.primaries.white.y)); + if (m_settings.primariesNameSet) m_resource->sendPrimariesNamed(m_settings.primariesNamed); - m_resource->sendTfPower(std::round(m_settings.transferFunctionPower * 10000)); + m_resource->sendTfNamed(m_settings.transferFunction); + + if (m_settings.transferFunctionPower != 1.0f) + m_resource->sendTfPower(std::round(m_settings.transferFunctionPower * 10000)); + m_resource->sendLuminances(std::round(m_settings.luminances.min * 10000), m_settings.luminances.max, m_settings.luminances.reference); - // send expected display paramateres - m_resource->sendTargetPrimaries(toProto(m_settings.masteringPrimaries.red.x), toProto(m_settings.masteringPrimaries.red.y), toProto(m_settings.masteringPrimaries.green.x), - toProto(m_settings.masteringPrimaries.green.y), toProto(m_settings.masteringPrimaries.blue.x), toProto(m_settings.masteringPrimaries.blue.y), - toProto(m_settings.masteringPrimaries.white.x), toProto(m_settings.masteringPrimaries.white.y)); - m_resource->sendTargetLuminance(std::round(m_settings.masteringLuminances.min * 10000), m_settings.masteringLuminances.max); - m_resource->sendTargetMaxCll(m_settings.maxCLL); - m_resource->sendTargetMaxFall(m_settings.maxFALL); + const auto& targetPrimaries = ( // + m_settings.masteringPrimaries.red.x != 0 || m_settings.masteringPrimaries.red.y != 0 || // + m_settings.masteringPrimaries.green.x != 0 || m_settings.masteringPrimaries.green.y != 0 || // + m_settings.masteringPrimaries.blue.x != 0 || m_settings.masteringPrimaries.blue.y != 0) ? + m_settings.masteringPrimaries : + m_settings.primaries; + + m_resource->sendTargetPrimaries( // + toProto(targetPrimaries.red.x), toProto(targetPrimaries.red.y), // + toProto(targetPrimaries.green.x), toProto(targetPrimaries.green.y), // + toProto(targetPrimaries.blue.x), toProto(targetPrimaries.blue.y), // + toProto(targetPrimaries.white.x), toProto(targetPrimaries.white.y)); + + if (m_settings.masteringLuminances.max > 0) + m_resource->sendTargetLuminance(std::round(m_settings.masteringLuminances.min * 10000), m_settings.masteringLuminances.max); + else + m_resource->sendTargetLuminance(std::round(m_settings.luminances.min * 10000), m_settings.luminances.max); + + if (m_settings.maxCLL > 0 || m_settings.maxFALL > 0) { + m_resource->sendTargetMaxCll(m_settings.maxCLL); + m_resource->sendTargetMaxFall(m_settings.maxFALL); + } m_resource->sendDone(); } @@ -782,7 +862,7 @@ void CColorManagementProtocol::bindManager(wl_client* client, void* data, uint32 return; } - LOGM(TRACE, "New WP_color_manager at {:x}", (uintptr_t)RESOURCE.get()); + LOGM(Log::TRACE, "New WP_color_manager at {:x}", (uintptr_t)RESOURCE.get()); } void CColorManagementProtocol::onImagePreferredChanged(uint32_t preferredId) { diff --git a/src/protocols/ColorManagement.hpp b/src/protocols/ColorManagement.hpp index dea9f74cb..14700c987 100644 --- a/src/protocols/ColorManagement.hpp +++ b/src/protocols/ColorManagement.hpp @@ -7,7 +7,7 @@ #include "../helpers/Monitor.hpp" #include "core/Compositor.hpp" #include "color-management-v1.hpp" -#include "types/ColorManagement.hpp" +#include "../helpers/cm/ColorManagement.hpp" class CColorManager; class CColorManagementOutput; @@ -46,7 +46,6 @@ class CColorManagementOutput { class CColorManagementSurface { public: - CColorManagementSurface(SP surface_); // temporary interface for frog CM CColorManagementSurface(SP resource, SP surface_); bool good(); @@ -67,14 +66,11 @@ class CColorManagementSurface { private: SP m_resource; wl_client* m_client = nullptr; - NColorManagement::SImageDescription m_imageDescription; - NColorManagement::SImageDescription m_lastImageDescription; + NColorManagement::PImageDescription m_imageDescription; + NColorManagement::PImageDescription m_lastImageDescription; bool m_hasImageDescription = false; bool m_needsNewMetadata = false; hdr_output_metadata m_hdrMetadata; - - friend class CXXColorManagementSurface; - friend class CFrogColorManagementSurface; }; class CColorManagementFeedbackSurface { @@ -91,7 +87,7 @@ class CColorManagementFeedbackSurface { SP m_resource; wl_client* m_client = nullptr; - uint32_t m_currentPreferredId = 0; + uint64_t m_currentPreferredId = 0; struct { CHyprSignalListener enter; @@ -113,6 +109,14 @@ class CColorManagementIccCreator { WP m_self; NColorManagement::SImageDescription m_settings; + struct SIccFile { + int fd = -1; + uint32_t length = 0; + uint32_t offset = 0; + bool operator==(const SIccFile& i2) const { + return fd == i2.fd; + } + } m_icc; private: SP m_resource; @@ -150,20 +154,23 @@ class CColorManagementParametricCreator { class CColorManagementImageDescription { public: CColorManagementImageDescription(SP resource, bool allowGetInformation); + static SP fromReference(wl_resource* res); - bool good(); - wl_client* client(); - SP resource(); + bool good(); + wl_client* client(); + SP resource(); + bool sendMaybeReady(); - WP m_self; + WP m_self; - NColorManagement::SImageDescription m_settings; + NColorManagement::PImageDescription m_settings; private: SP m_resource; wl_client* m_client = nullptr; bool m_allowGetInformation = false; + friend class CColorManager; friend class CColorManagementOutput; }; @@ -216,9 +223,6 @@ class CColorManagementProtocol : public IWaylandProtocol { friend class CColorManagementIccCreator; friend class CColorManagementParametricCreator; friend class CColorManagementImageDescription; - - friend class CXXColorManagementSurface; - friend class CFrogColorManagementSurface; }; namespace PROTO { diff --git a/src/protocols/CommitTiming.cpp b/src/protocols/CommitTiming.cpp index 2fcd8e9cb..9cc2da83e 100644 --- a/src/protocols/CommitTiming.cpp +++ b/src/protocols/CommitTiming.cpp @@ -12,61 +12,48 @@ CCommitTimerResource::CCommitTimerResource(UP&& resource_, SP< m_resource->setOnDestroy([this](CWpCommitTimerV1* r) { PROTO::commitTiming->destroyResource(this); }); m_resource->setSetTimestamp([this](CWpCommitTimerV1* r, uint32_t tvHi, uint32_t tvLo, uint32_t tvNsec) { - return; - if (!m_surface) { r->error(WP_COMMIT_TIMER_V1_ERROR_SURFACE_DESTROYED, "Surface was gone"); return; } - if (m_pendingTimeout.has_value()) { + if (m_surface->m_pending.pendingTimeout.has_value()) { r->error(WP_COMMIT_TIMER_V1_ERROR_TIMESTAMP_EXISTS, "Timestamp is already set"); return; } - timespec ts; - ts.tv_sec = (((uint64_t)tvHi) << 32) | (uint64_t)tvLo; - ts.tv_nsec = tvNsec; + const auto delay = Time::till({.tv_sec = (((uint64_t)tvHi) << 32) | (uint64_t)tvLo, .tv_nsec = tvNsec}); - const auto TIME = Time::fromTimespec(&ts); - const auto TIME_NOW = Time::steadyNow(); - - if (TIME_NOW > TIME) { - // TODO: should we err here? - // for now just do nothing I guess, thats some lag. - m_pendingTimeout = Time::steady_dur::min(); + if (delay.count() <= 0) { + m_surface->m_pending.pendingTimeout.reset(); } else - m_pendingTimeout = TIME - TIME_NOW; + m_surface->m_pending.pendingTimeout = delay; }); m_listeners.surfaceStateCommit = m_surface->m_events.stateCommit2.listen([this](auto state) { - if (!m_pendingTimeout.has_value()) + if (!state || !state->pendingTimeout.has_value() || !m_surface || m_surface->isTearing()) return; m_surface->m_stateQueue.lock(state, LOCK_REASON_TIMER); - if (!m_timerPresent) { - m_timerPresent = true; - timer = makeShared( - m_pendingTimeout, - [this](SP self, void* data) { - if (!m_surface) + if (!state->timer) { + state->timer = makeShared( + state->pendingTimeout, + [surface = m_surface, state](SP self, void* data) { + if (!surface || !state) return; - m_surface->m_stateQueue.unlockFirst(LOCK_REASON_TIMER); + surface->m_stateQueue.unlock(state, LOCK_REASON_TIMER); }, nullptr); + g_pEventLoopManager->addTimer(state->timer); } else - timer->updateTimeout(m_pendingTimeout); + state->timer->updateTimeout(state->pendingTimeout); - m_pendingTimeout.reset(); + state->pendingTimeout.reset(); }); } -CCommitTimerResource::~CCommitTimerResource() { - ; -} - bool CCommitTimerResource::good() { return m_resource->resource(); } diff --git a/src/protocols/CommitTiming.hpp b/src/protocols/CommitTiming.hpp index e79face8c..b5a1de93d 100644 --- a/src/protocols/CommitTiming.hpp +++ b/src/protocols/CommitTiming.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include "WaylandProtocol.hpp" #include "commit-timing-v1.hpp" @@ -14,16 +13,12 @@ class CEventLoopTimer; class CCommitTimerResource { public: CCommitTimerResource(UP&& resource_, SP surface); - ~CCommitTimerResource(); bool good(); private: - UP m_resource; - WP m_surface; - bool m_timerPresent = false; - std::optional m_pendingTimeout; - SP timer; + UP m_resource; + WP m_surface; struct { CHyprSignalListener surfaceStateCommit; diff --git a/src/protocols/ContentType.cpp b/src/protocols/ContentType.cpp index c32f54a28..acae218c6 100644 --- a/src/protocols/ContentType.cpp +++ b/src/protocols/ContentType.cpp @@ -6,20 +6,20 @@ CContentTypeManager::CContentTypeManager(SP resource) : if UNLIKELY (!good()) return; - m_resource->setDestroy([](CWpContentTypeManagerV1* r) {}); + m_resource->setDestroy([this](CWpContentTypeManagerV1* r) { PROTO::contentType->destroyResource(this); }); m_resource->setOnDestroy([this](CWpContentTypeManagerV1* r) { PROTO::contentType->destroyResource(this); }); m_resource->setGetSurfaceContentType([](CWpContentTypeManagerV1* r, uint32_t id, wl_resource* surface) { - LOGM(TRACE, "Get surface for id={}, surface={}", id, (uintptr_t)surface); + LOGM(Log::TRACE, "Get surface for id={}, surface={}", id, (uintptr_t)surface); auto SURF = CWLSurfaceResource::fromResource(surface); if (!SURF) { - LOGM(ERR, "No surface for resource {}", (uintptr_t)surface); + LOGM(Log::ERR, "No surface for resource {}", (uintptr_t)surface); r->error(-1, "Invalid surface (2)"); return; } - if (SURF->m_colorManagement) { + if (SURF->m_contentType) { r->error(WP_CONTENT_TYPE_MANAGER_V1_ERROR_ALREADY_CONSTRUCTED, "CT manager already exists"); return; } diff --git a/src/protocols/DRMLease.cpp b/src/protocols/DRMLease.cpp index 260497d3c..2da7b04a6 100644 --- a/src/protocols/DRMLease.cpp +++ b/src/protocols/DRMLease.cpp @@ -27,7 +27,7 @@ CDRMLeaseResource::CDRMLeaseResource(SP resource_, SPm_monitor || m->m_monitor->m_isBeingLeased) { - LOGM(ERR, "Rejecting lease: no monitor or monitor is being leased for {}", (m->m_monitor ? m->m_monitor->m_name : "null")); + LOGM(Log::ERR, "Rejecting lease: no monitor or monitor is being leased for {}", (m->m_monitor ? m->m_monitor->m_name : "null")); m_resource->sendFinished(); return; } @@ -35,7 +35,7 @@ CDRMLeaseResource::CDRMLeaseResource(SP resource_, SPm_monitor->m_name); @@ -53,7 +53,7 @@ CDRMLeaseResource::CDRMLeaseResource(SP resource_, SPsendFinished(); return; } @@ -71,10 +71,10 @@ CDRMLeaseResource::CDRMLeaseResource(SP resource_, SPsendFinished(); - LOGM(LOG, "Revoking lease for fd {}", m_lease->leaseFD); + LOGM(Log::DEBUG, "Revoking lease for fd {}", m_lease->leaseFD); }); - LOGM(LOG, "Granting lease, sending fd {}", m_lease->leaseFD); + LOGM(Log::DEBUG, "Granting lease, sending fd {}", m_lease->leaseFD); m_resource->sendLeaseFd(m_lease->leaseFD); @@ -211,18 +211,18 @@ CDRMLeaseDeviceResource::CDRMLeaseDeviceResource(std::string deviceName_, SPm_requests.emplace_back(RESOURCE); - LOGM(LOG, "New lease request {}", id); + LOGM(Log::DEBUG, "New lease request {}", id); RESOURCE->m_parent = m_self; }); CFileDescriptor fd{PROTO::lease.at(m_deviceName)->m_backend.get()->getNonMasterFD()}; if (!fd.isValid()) { - LOGM(ERR, "Failed to dup fd in lease"); + LOGM(Log::ERR, "Failed to dup fd in lease"); return; } - LOGM(LOG, "Sending DRMFD {} to new lease device", fd.get()); + LOGM(Log::DEBUG, "Sending DRMFD {} to new lease device", fd.get()); m_resource->sendDrmFd(fd.get()); for (auto const& m : PROTO::lease.at(m_deviceName)->m_offeredOutputs) { @@ -250,7 +250,7 @@ void CDRMLeaseDeviceResource::sendConnector(PHLMONITOR monitor) { RESOURCE->m_parent = m_self; RESOURCE->m_self = RESOURCE; - LOGM(LOG, "Sending new connector {}", monitor->m_name); + LOGM(Log::DEBUG, "Sending new connector {}", monitor->m_name); m_connectorsSent.emplace_back(RESOURCE); PROTO::lease.at(m_deviceName)->m_connectors.emplace_back(RESOURCE); @@ -271,7 +271,7 @@ CDRMLeaseProtocol::CDRMLeaseProtocol(const wl_interface* iface, const int& ver, CFileDescriptor fd{m_backend->getNonMasterFD()}; if (!fd.isValid()) { - LOGM(ERR, "Failed to dup fd for drm node {}", m_deviceName); + LOGM(Log::ERR, "Failed to dup fd for drm node {}", m_deviceName); return; } @@ -318,7 +318,7 @@ void CDRMLeaseProtocol::offer(PHLMONITOR monitor) { return; if (monitor->m_output->getBackend() != m_backend) { - LOGM(ERR, "Monitor {} cannot be leased: lease is for a different device", monitor->m_name); + LOGM(Log::ERR, "Monitor {} cannot be leased: lease is for a different device", monitor->m_name); return; } diff --git a/src/protocols/DRMSyncobj.cpp b/src/protocols/DRMSyncobj.cpp index 40869c0d6..821796d02 100644 --- a/src/protocols/DRMSyncobj.cpp +++ b/src/protocols/DRMSyncobj.cpp @@ -21,7 +21,7 @@ WP CDRMSyncPointState::timeline() { UP CDRMSyncPointState::createSyncRelease() { if (m_releaseTaken) - Debug::log(ERR, "CDRMSyncPointState: creating a sync releaser on an already created SyncRelease"); + Log::logger->log(Log::ERR, "CDRMSyncPointState: creating a sync releaser on an already created SyncRelease"); m_releaseTaken = true; return makeUnique(m_timeline, m_point); @@ -180,7 +180,7 @@ CDRMSyncobjManagerResource::CDRMSyncobjManagerResource(UPm_syncobj = RESOURCE; - LOGM(LOG, "New linux_syncobj at {:x} for surface {:x}", (uintptr_t)RESOURCE.get(), (uintptr_t)SURF.get()); + LOGM(Log::DEBUG, "New linux_syncobj at {:x} for surface {:x}", (uintptr_t)RESOURCE.get(), (uintptr_t)SURF.get()); }); m_resource->setImportTimeline([this](CWpLinuxDrmSyncobjManagerV1* r, uint32_t id, int32_t fd) { @@ -192,7 +192,7 @@ CDRMSyncobjManagerResource::CDRMSyncobjManagerResource(UPm_drm.syncobjSupport) m_drmFD = g_pCompositor->m_drm.fd; else { - LOGM(ERR, "CDRMSyncobjProtocol: no nodes support explicit sync?"); + LOGM(Log::ERR, "CDRMSyncobjProtocol: no nodes support explicit sync?"); return; } - LOGM(LOG, "CDRMSyncobjProtocol: using fd {}", m_drmFD); + LOGM(Log::DEBUG, "CDRMSyncobjProtocol: using fd {}", m_drmFD); } void CDRMSyncobjProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { diff --git a/src/protocols/DataDeviceWlr.cpp b/src/protocols/DataDeviceWlr.cpp index 835fbc012..d29106e0e 100644 --- a/src/protocols/DataDeviceWlr.cpp +++ b/src/protocols/DataDeviceWlr.cpp @@ -14,16 +14,16 @@ CWLRDataOffer::CWLRDataOffer(SP resource_, SPsetReceive([this](CZwlrDataControlOfferV1* r, const char* mime, int32_t fd) { CFileDescriptor sendFd{fd}; if (!m_source) { - LOGM(WARN, "Possible bug: Receive on an offer w/o a source"); + LOGM(Log::WARN, "Possible bug: Receive on an offer w/o a source"); return; } if (m_dead) { - LOGM(WARN, "Possible bug: Receive on an offer that's dead"); + LOGM(Log::WARN, "Possible bug: Receive on an offer that's dead"); return; } - LOGM(LOG, "Offer {:x} asks to send data from source {:x}", (uintptr_t)this, (uintptr_t)m_source.get()); + LOGM(Log::DEBUG, "Offer {:x} asks to send data from source {:x}", (uintptr_t)this, (uintptr_t)m_source.get()); m_source->send(mime, std::move(sendFd)); }); @@ -79,7 +79,7 @@ std::vector CWLRDataSource::mimes() { void CWLRDataSource::send(const std::string& mime, CFileDescriptor fd) { if (std::ranges::find(m_mimeTypes, mime) == m_mimeTypes.end()) { - LOGM(ERR, "Compositor/App bug: CWLRDataSource::sendAskSend with non-existent mime"); + LOGM(Log::ERR, "Compositor/App bug: CWLRDataSource::sendAskSend with non-existent mime"); return; } @@ -88,7 +88,7 @@ void CWLRDataSource::send(const std::string& mime, CFileDescriptor fd) { void CWLRDataSource::accepted(const std::string& mime) { if (std::ranges::find(m_mimeTypes, mime) == m_mimeTypes.end()) - LOGM(ERR, "Compositor/App bug: CWLRDataSource::sendAccepted with non-existent mime"); + LOGM(Log::ERR, "Compositor/App bug: CWLRDataSource::sendAccepted with non-existent mime"); // wlr has no accepted } @@ -113,34 +113,34 @@ CWLRDataDevice::CWLRDataDevice(SP resource_) : m_resou m_resource->setSetSelection([](CZwlrDataControlDeviceV1* r, wl_resource* sourceR) { auto source = sourceR ? CWLRDataSource::fromResource(sourceR) : CSharedPointer{}; if (!source) { - LOGM(LOG, "wlr reset selection received"); + LOGM(Log::DEBUG, "wlr reset selection received"); g_pSeatManager->setCurrentSelection(nullptr); return; } if (source && source->used()) - LOGM(WARN, "setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this."); + LOGM(Log::WARN, "setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this."); source->markUsed(); - LOGM(LOG, "wlr manager requests selection to {:x}", (uintptr_t)source.get()); + LOGM(Log::DEBUG, "wlr manager requests selection to {:x}", (uintptr_t)source.get()); g_pSeatManager->setCurrentSelection(source); }); m_resource->setSetPrimarySelection([](CZwlrDataControlDeviceV1* r, wl_resource* sourceR) { auto source = sourceR ? CWLRDataSource::fromResource(sourceR) : CSharedPointer{}; if (!source) { - LOGM(LOG, "wlr reset primary selection received"); + LOGM(Log::DEBUG, "wlr reset primary selection received"); g_pSeatManager->setCurrentPrimarySelection(nullptr); return; } if (source && source->used()) - LOGM(WARN, "setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this."); + LOGM(Log::WARN, "setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this."); source->markUsed(); - LOGM(LOG, "wlr manager requests primary selection to {:x}", (uintptr_t)source.get()); + LOGM(Log::DEBUG, "wlr manager requests primary selection to {:x}", (uintptr_t)source.get()); g_pSeatManager->setCurrentPrimarySelection(source); }); } @@ -197,7 +197,7 @@ CWLRDataControlManagerResource::CWLRDataControlManagerResource(SPsendInitialSelections(); - LOGM(LOG, "New wlr data device bound at {:x}", (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New wlr data device bound at {:x}", (uintptr_t)RESOURCE.get()); }); m_resource->setCreateDataSource([this](CZwlrDataControlManagerV1* r, uint32_t id) { @@ -213,13 +213,13 @@ CWLRDataControlManagerResource::CWLRDataControlManagerResource(SPm_self = RESOURCE; m_sources.emplace_back(RESOURCE); - LOGM(LOG, "New wlr data source bound at {:x}", (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New wlr data source bound at {:x}", (uintptr_t)RESOURCE.get()); }); } @@ -240,7 +240,7 @@ void CDataDeviceWLRProtocol::bindManager(wl_client* client, void* data, uint32_t return; } - LOGM(LOG, "New wlr_data_control_manager at {:x}", (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New wlr_data_control_manager at {:x}", (uintptr_t)RESOURCE.get()); } void CDataDeviceWLRProtocol::destroyResource(CWLRDataControlManagerResource* resource) { @@ -278,7 +278,7 @@ void CDataDeviceWLRProtocol::sendSelectionToDevice(SP dev, SPm_primary = primary; - LOGM(LOG, "New {}offer {:x} for data source {:x}", primary ? "primary " : " ", (uintptr_t)OFFER.get(), (uintptr_t)sel.get()); + LOGM(Log::DEBUG, "New {}offer {:x} for data source {:x}", primary ? "primary " : " ", (uintptr_t)OFFER.get(), (uintptr_t)sel.get()); dev->sendDataOffer(OFFER); OFFER->sendData(); @@ -298,7 +298,7 @@ void CDataDeviceWLRProtocol::setSelection(SP source, bool primary) } if (!source) { - LOGM(LOG, "resetting {}selection", primary ? "primary " : " "); + LOGM(Log::DEBUG, "resetting {}selection", primary ? "primary " : " "); for (auto const& d : m_devices) { sendSelectionToDevice(d, nullptr, primary); @@ -307,7 +307,7 @@ void CDataDeviceWLRProtocol::setSelection(SP source, bool primary) return; } - LOGM(LOG, "New {}selection for data source {:x}", primary ? "primary" : "", (uintptr_t)source.get()); + LOGM(Log::DEBUG, "New {}selection for data source {:x}", primary ? "primary" : "", (uintptr_t)source.get()); for (auto const& d : m_devices) { sendSelectionToDevice(d, source, primary); diff --git a/src/protocols/ExtDataDevice.cpp b/src/protocols/ExtDataDevice.cpp index c2d4c4976..6ab83ab45 100644 --- a/src/protocols/ExtDataDevice.cpp +++ b/src/protocols/ExtDataDevice.cpp @@ -14,16 +14,16 @@ CExtDataOffer::CExtDataOffer(SP resource_, SPsetReceive([this](CExtDataControlOfferV1* r, const char* mime, int32_t fd) { CFileDescriptor sendFd{fd}; if (!m_source) { - LOGM(WARN, "Possible bug: Receive on an offer w/o a source"); + LOGM(Log::WARN, "Possible bug: Receive on an offer w/o a source"); return; } if (m_dead) { - LOGM(WARN, "Possible bug: Receive on an offer that's dead"); + LOGM(Log::WARN, "Possible bug: Receive on an offer that's dead"); return; } - LOGM(LOG, "Offer {:x} asks to send data from source {:x}", (uintptr_t)this, (uintptr_t)m_source.get()); + LOGM(Log::DEBUG, "Offer {:x} asks to send data from source {:x}", (uintptr_t)this, (uintptr_t)m_source.get()); m_source->send(mime, std::move(sendFd)); }); @@ -79,7 +79,7 @@ std::vector CExtDataSource::mimes() { void CExtDataSource::send(const std::string& mime, CFileDescriptor fd) { if (std::ranges::find(m_mimeTypes, mime) == m_mimeTypes.end()) { - LOGM(ERR, "Compositor/App bug: CExtDataSource::sendAskSend with non-existent mime"); + LOGM(Log::ERR, "Compositor/App bug: CExtDataSource::sendAskSend with non-existent mime"); return; } @@ -88,7 +88,7 @@ void CExtDataSource::send(const std::string& mime, CFileDescriptor fd) { void CExtDataSource::accepted(const std::string& mime) { if (std::ranges::find(m_mimeTypes, mime) == m_mimeTypes.end()) - LOGM(ERR, "Compositor/App bug: CExtDataSource::sendAccepted with non-existent mime"); + LOGM(Log::ERR, "Compositor/App bug: CExtDataSource::sendAccepted with non-existent mime"); // ext has no accepted } @@ -113,34 +113,34 @@ CExtDataDevice::CExtDataDevice(SP resource_) : m_resour m_resource->setSetSelection([](CExtDataControlDeviceV1* r, wl_resource* sourceR) { auto source = sourceR ? CExtDataSource::fromResource(sourceR) : CSharedPointer{}; if (!source) { - LOGM(LOG, "ext reset selection received"); + LOGM(Log::DEBUG, "ext reset selection received"); g_pSeatManager->setCurrentSelection(nullptr); return; } if (source && source->used()) - LOGM(WARN, "setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this."); + LOGM(Log::WARN, "setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this."); source->markUsed(); - LOGM(LOG, "ext manager requests selection to {:x}", (uintptr_t)source.get()); + LOGM(Log::DEBUG, "ext manager requests selection to {:x}", (uintptr_t)source.get()); g_pSeatManager->setCurrentSelection(source); }); m_resource->setSetPrimarySelection([](CExtDataControlDeviceV1* r, wl_resource* sourceR) { auto source = sourceR ? CExtDataSource::fromResource(sourceR) : CSharedPointer{}; if (!source) { - LOGM(LOG, "ext reset primary selection received"); + LOGM(Log::DEBUG, "ext reset primary selection received"); g_pSeatManager->setCurrentPrimarySelection(nullptr); return; } if (source && source->used()) - LOGM(WARN, "setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this."); + LOGM(Log::WARN, "setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this."); source->markUsed(); - LOGM(LOG, "ext manager requests primary selection to {:x}", (uintptr_t)source.get()); + LOGM(Log::DEBUG, "ext manager requests primary selection to {:x}", (uintptr_t)source.get()); g_pSeatManager->setCurrentPrimarySelection(source); }); } @@ -197,7 +197,7 @@ CExtDataControlManagerResource::CExtDataControlManagerResource(SPsendInitialSelections(); - LOGM(LOG, "New ext data device bound at {:x}", (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New ext data device bound at {:x}", (uintptr_t)RESOURCE.get()); }); m_resource->setCreateDataSource([this](CExtDataControlManagerV1* r, uint32_t id) { @@ -213,13 +213,13 @@ CExtDataControlManagerResource::CExtDataControlManagerResource(SPm_self = RESOURCE; m_sources.emplace_back(RESOURCE); - LOGM(LOG, "New ext data source bound at {:x}", (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New ext data source bound at {:x}", (uintptr_t)RESOURCE.get()); }); } @@ -240,7 +240,7 @@ void CExtDataDeviceProtocol::bindManager(wl_client* client, void* data, uint32_t return; } - LOGM(LOG, "New ext_data_control_manager at {:x}", (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New ext_data_control_manager at {:x}", (uintptr_t)RESOURCE.get()); } void CExtDataDeviceProtocol::destroyResource(CExtDataControlManagerResource* resource) { @@ -278,7 +278,7 @@ void CExtDataDeviceProtocol::sendSelectionToDevice(SP dev, SPm_primary = primary; - LOGM(LOG, "New {}offer {:x} for data source {:x}", primary ? "primary " : " ", (uintptr_t)OFFER.get(), (uintptr_t)sel.get()); + LOGM(Log::DEBUG, "New {}offer {:x} for data source {:x}", primary ? "primary " : " ", (uintptr_t)OFFER.get(), (uintptr_t)sel.get()); dev->sendDataOffer(OFFER); OFFER->sendData(); @@ -298,7 +298,7 @@ void CExtDataDeviceProtocol::setSelection(SP source, bool primary) } if (!source) { - LOGM(LOG, "resetting {}selection", primary ? "primary " : " "); + LOGM(Log::DEBUG, "resetting {}selection", primary ? "primary " : " "); for (auto const& d : m_devices) { sendSelectionToDevice(d, nullptr, primary); @@ -307,7 +307,7 @@ void CExtDataDeviceProtocol::setSelection(SP source, bool primary) return; } - LOGM(LOG, "New {}selection for data source {:x}", primary ? "primary" : "", (uintptr_t)source.get()); + LOGM(Log::DEBUG, "New {}selection for data source {:x}", primary ? "primary" : "", (uintptr_t)source.get()); for (auto const& d : m_devices) { sendSelectionToDevice(d, source, primary); diff --git a/src/protocols/ExtWorkspace.cpp b/src/protocols/ExtWorkspace.cpp index bc4402f05..ac7c72779 100644 --- a/src/protocols/ExtWorkspace.cpp +++ b/src/protocols/ExtWorkspace.cpp @@ -1,9 +1,8 @@ #include "ExtWorkspace.hpp" #include "../Compositor.hpp" -#include "../managers/HookSystemManager.hpp" #include "../managers/eventLoop/EventLoopManager.hpp" +#include "../event/EventBus.hpp" #include -#include #include #include "core/Output.hpp" @@ -27,12 +26,20 @@ CExtWorkspaceGroupResource::CExtWorkspaceGroupResource(WPm_name); - if (auto resource = output->outputResourceFrom(m_resource->client())) - m_resource->sendOutputEnter(resource->getResource()->resource()); + if (auto resources = output->outputResourcesFrom(m_resource->client()); !resources.empty()) { + for (const auto& r : resources) { + m_resource->sendOutputEnter(r->getResource()->resource()); + } + } m_listeners.outputBound = output->m_events.outputBound.listen([this](const SP& output) { - if (output->client() == m_resource->client()) - m_resource->sendOutputEnter(output->getResource()->resource()); + if (output->client() != m_resource->client()) + return; + + m_resource->sendOutputEnter(output->getResource()->resource()); + + if (m_manager) + m_manager->scheduleDone(); }); } @@ -273,7 +280,7 @@ void CExtWorkspaceManagerResource::onMonitorCreated(const PHLMONITOR& monitor) { group->sendToWorkspaces(); if UNLIKELY (!group->good()) { - LOGM(ERR, "Couldn't create a workspace group object"); + LOGM(Log::ERR, "Couldn't create a workspace group object"); wl_client_post_no_memory(m_resource->client()); return; } @@ -287,24 +294,20 @@ void CExtWorkspaceManagerResource::onWorkspaceCreated(const PHLWORKSPACE& worksp ws->m_self = ws; if UNLIKELY (!ws->good()) { - LOGM(ERR, "Couldn't create a workspace object"); + LOGM(Log::ERR, "Couldn't create a workspace object"); wl_client_post_no_memory(m_resource->client()); return; } } CExtWorkspaceProtocol::CExtWorkspaceProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - static auto P1 = g_pHookSystem->hookDynamic("createWorkspace", [this](void* self, SCallbackInfo& info, std::any data) { - auto workspace = std::any_cast(data)->m_self.lock(); - + static auto P1 = Event::bus()->m_events.workspace.created.listen([this](PHLWORKSPACEREF workspace) { for (auto const& m : m_managers) { - m->onWorkspaceCreated(workspace); + m->onWorkspaceCreated(workspace.lock()); } }); - static auto P2 = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any data) { - auto monitor = std::any_cast(data); - + static auto P2 = Event::bus()->m_events.monitor.added.listen([this](PHLMONITOR monitor) { for (auto const& m : m_managers) { m->onMonitorCreated(monitor); } @@ -316,7 +319,7 @@ void CExtWorkspaceProtocol::bindManager(wl_client* client, void* data, uint32_t manager->init(manager); if UNLIKELY (!manager->good()) { - LOGM(ERR, "Couldn't create a workspace manager"); + LOGM(Log::ERR, "Couldn't create a workspace manager"); wl_client_post_no_memory(client); return; } diff --git a/src/protocols/Fifo.cpp b/src/protocols/Fifo.cpp index 63ce8579d..355644d96 100644 --- a/src/protocols/Fifo.cpp +++ b/src/protocols/Fifo.cpp @@ -1,8 +1,8 @@ #include "Fifo.hpp" #include "Compositor.hpp" #include "core/Compositor.hpp" -#include "../managers/HookSystemManager.hpp" #include "../helpers/Monitor.hpp" +#include "../event/EventBus.hpp" CFifoResource::CFifoResource(UP&& resource_, SP surface) : m_resource(std::move(resource_)), m_surface(surface) { if UNLIKELY (!m_resource->resource()) @@ -18,7 +18,8 @@ CFifoResource::CFifoResource(UP&& resource_, SP s return; } - m_pending.barrierSet = true; + m_surface->m_pending.barrierSet = true; + m_surface->m_pending.updated.bits.fifo = true; }); m_resource->setWaitBarrier([this](CWpFifoV1* r) { @@ -27,49 +28,37 @@ CFifoResource::CFifoResource(UP&& resource_, SP s return; } - if (!m_pending.barrierSet) - return; + if (!m_surface->m_current.barrierSet) { + // that might mean an empty commit with a barrier_set alone + static const auto PPEND = CConfigValue("debug:fifo_pending_workaround"); + if (!m_surface->m_pending.fifoScheduled) + m_surface->m_pending.fifoScheduled = checkMonitors(*PPEND); - m_pending.surfaceLocked = true; + return; + } + + m_surface->m_pending.surfaceLocked = true; }); m_listeners.surfaceStateCommit = m_surface->m_events.stateCommit.listen([this](auto state) { - if (!m_pending.surfaceLocked) + if (!state || !state->surfaceLocked) return; + static const auto PPEND = CConfigValue("debug:fifo_pending_workaround"); + //#TODO: // this feels wrong, but if we have no pending frames, presented might never come because // we are waiting on the barrier to unlock and no damage is around. - if (m_surface->m_enteredOutputs.empty() && m_surface->m_hlSurface) { - for (auto& m : g_pCompositor->m_monitors) { - if (!m || !m->m_enabled) - continue; + // unlock on timeout instead? + if (!state->fifoScheduled) + state->fifoScheduled = checkMonitors(*PPEND); - auto box = m_surface->m_hlSurface->getSurfaceBoxGlobal(); - if (box && !box->intersection({m->m_position, m->m_size}).empty()) { - if (m->m_tearingState.activelyTearing) - return; // dont fifo lock on tearing. - - g_pCompositor->scheduleFrameForMonitor(m, Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); - } - } - } else { - for (auto& m : m_surface->m_enteredOutputs) { - if (!m) - continue; - - if (m->m_tearingState.activelyTearing) - return; // dont fifo lock on tearing. - - g_pCompositor->scheduleFrameForMonitor(m.lock(), Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); - } - } + if (!state->fifoScheduled) + return; // only lock once its mapped. if (m_surface->m_mapped) m_surface->m_stateQueue.lock(state, LOCK_REASON_FIFO); - - m_pending = {}; }); } @@ -82,9 +71,41 @@ bool CFifoResource::good() { } void CFifoResource::presented() { + m_surface->m_current.barrierSet = false; m_surface->m_stateQueue.unlockFirst(LOCK_REASON_FIFO); } +bool CFifoResource::checkMonitors(bool needsSchedule) { + if (m_surface->m_enteredOutputs.empty() && m_surface->m_hlSurface) { + for (auto& m : g_pCompositor->m_monitors) { + if (!m || !m->m_enabled) + continue; + + auto box = m_surface->m_hlSurface->getSurfaceBoxGlobal(); + if (box && !box->intersection({m->m_position, m->m_size}).empty()) { + if (m->m_tearingState.activelyTearing) + return false; // dont fifo lock on tearing. + + if (needsSchedule) + g_pCompositor->scheduleFrameForMonitor(m, Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); + } + } + } else { + for (auto& m : m_surface->m_enteredOutputs) { + if (!m) + continue; + + if (m->m_tearingState.activelyTearing) + return false; // dont fifo lock on tearing. + + if (needsSchedule) + g_pCompositor->scheduleFrameForMonitor(m.lock(), Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); + } + } + + return true; +} + CFifoManagerResource::CFifoManagerResource(UP&& resource_) : m_resource(std::move(resource_)) { if UNLIKELY (!m_resource->resource()) return; @@ -119,7 +140,7 @@ CFifoManagerResource::CFifoManagerResource(UP&& resource_) : m } surf->m_fifo = RESOURCE; - LOGM(LOG, "New fifo at {:x} for surface {:x}", (uintptr_t)RESOURCE, (uintptr_t)surf.get()); + LOGM(Log::DEBUG, "New fifo at {:x} for surface {:x}", (uintptr_t)RESOURCE, (uintptr_t)surf.get()); }); } @@ -132,9 +153,7 @@ bool CFifoManagerResource::good() { } CFifoProtocol::CFifoProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - static auto P = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any param) { - auto M = std::any_cast(param); - + static auto P = Event::bus()->m_events.monitor.added.listen([this](PHLMONITOR M) { M->m_events.presented.listenStatic([this, m = PHLMONITORREF{M}]() { if (!m || !PROTO::fifo) return; diff --git a/src/protocols/Fifo.hpp b/src/protocols/Fifo.hpp index 8551e78cd..5b143f792 100644 --- a/src/protocols/Fifo.hpp +++ b/src/protocols/Fifo.hpp @@ -21,18 +21,12 @@ class CFifoResource { WP m_surface; - struct SState { - bool barrierSet = false; - bool surfaceLocked = false; - }; - - SState m_pending; - struct { CHyprSignalListener surfaceStateCommit; } m_listeners; void presented(); + bool checkMonitors(bool needsSchedule = false); friend class CFifoProtocol; friend class CFifoManagerResource; diff --git a/src/protocols/FocusGrab.cpp b/src/protocols/FocusGrab.cpp index b761d2643..62b65655b 100644 --- a/src/protocols/FocusGrab.cpp +++ b/src/protocols/FocusGrab.cpp @@ -107,7 +107,7 @@ void CFocusGrab::refocusKeyboard() { if (surface) Desktop::focusState()->rawSurfaceFocus(surface); else - LOGM(ERR, "CFocusGrab::refocusKeyboard called with no committed surfaces. This should never happen."); + LOGM(Log::ERR, "CFocusGrab::refocusKeyboard called with no committed surfaces. This should never happen."); } void CFocusGrab::commit(bool removeOnly) { diff --git a/src/protocols/ForeignToplevel.cpp b/src/protocols/ForeignToplevel.cpp index 22bef314f..baabda7c7 100644 --- a/src/protocols/ForeignToplevel.cpp +++ b/src/protocols/ForeignToplevel.cpp @@ -1,6 +1,6 @@ #include "ForeignToplevel.hpp" #include "../Compositor.hpp" -#include "../managers/HookSystemManager.hpp" +#include "../event/EventBus.hpp" CForeignToplevelHandle::CForeignToplevelHandle(SP resource_, PHLWINDOW pWindow_) : m_resource(resource_), m_window(pWindow_) { if UNLIKELY (!resource_->resource()) @@ -30,7 +30,7 @@ CForeignToplevelList::CForeignToplevelList(SP resourc m_resource->setStop([this](CExtForeignToplevelListV1* h) { m_resource->sendFinished(); m_finished = true; - LOGM(LOG, "CForeignToplevelList: finished"); + LOGM(Log::DEBUG, "CForeignToplevelList: finished"); }); for (auto const& w : g_pCompositor->m_windows) { @@ -54,26 +54,26 @@ void CForeignToplevelList::onMap(PHLWINDOW pWindow) { std::erase_if(m_handles, [&](const auto& other) { return other.get() == OLDHANDLE.get(); }); } - const auto NEWHANDLE = PROTO::foreignToplevel->m_handles.emplace_back( + auto newHandle = PROTO::foreignToplevel->m_handles.emplace_back( makeShared(makeShared(m_resource->client(), m_resource->version(), 0), pWindow)); - if (!NEWHANDLE->good()) { - LOGM(ERR, "Couldn't create a foreign handle"); + if (!newHandle->good()) { + LOGM(Log::ERR, "Couldn't create a foreign handle"); m_resource->noMemory(); PROTO::foreignToplevel->m_handles.pop_back(); return; } - const auto IDENTIFIER = std::format("{:08x}->{:016x}", sc(rc(this) & 0xFFFFFFFF), rc(pWindow.get())); + const auto IDENTIFIER = std::format("{:x}", pWindow->m_stableID); - LOGM(LOG, "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(); + 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_handles.push_back(NEWHANDLE); + m_handles.emplace_back(std::move(newHandle)); } SP CForeignToplevelList::handleForWindow(PHLWINDOW pWindow) { @@ -123,9 +123,7 @@ bool CForeignToplevelList::good() { } CForeignToplevelProtocol::CForeignToplevelProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - static auto P = g_pHookSystem->hookDynamic("openWindow", [this](void* self, SCallbackInfo& info, std::any data) { - auto window = std::any_cast(data); - + static auto P = Event::bus()->m_events.window.open.listen([this](PHLWINDOW window) { if (!windowValidForForeign(window)) return; @@ -134,9 +132,7 @@ CForeignToplevelProtocol::CForeignToplevelProtocol(const wl_interface* iface, co } }); - static auto P1 = g_pHookSystem->hookDynamic("closeWindow", [this](void* self, SCallbackInfo& info, std::any data) { - auto window = std::any_cast(data); - + static auto P1 = Event::bus()->m_events.window.close.listen([this](PHLWINDOW window) { if (!windowValidForForeign(window)) return; @@ -145,9 +141,7 @@ CForeignToplevelProtocol::CForeignToplevelProtocol(const wl_interface* iface, co } }); - static auto P2 = g_pHookSystem->hookDynamic("windowTitle", [this](void* self, SCallbackInfo& info, std::any data) { - auto window = std::any_cast(data); - + static auto P2 = Event::bus()->m_events.window.title.listen([this](PHLWINDOW window) { if (!windowValidForForeign(window)) return; @@ -161,7 +155,7 @@ void CForeignToplevelProtocol::bindManager(wl_client* client, void* data, uint32 const auto RESOURCE = m_managers.emplace_back(makeUnique(makeShared(client, ver, id))).get(); if UNLIKELY (!RESOURCE->good()) { - LOGM(ERR, "Couldn't create a foreign list"); + LOGM(Log::ERR, "Couldn't create a foreign list"); wl_client_post_no_memory(client); m_managers.pop_back(); return; diff --git a/src/protocols/ForeignToplevel.hpp b/src/protocols/ForeignToplevel.hpp index 355117e79..0ff74e755 100644 --- a/src/protocols/ForeignToplevel.hpp +++ b/src/protocols/ForeignToplevel.hpp @@ -3,7 +3,7 @@ #include #include #include "WaylandProtocol.hpp" -#include "desktop/DesktopTypes.hpp" +#include "../desktop/DesktopTypes.hpp" #include "ext-foreign-toplevel-list-v1.hpp" class CForeignToplevelHandle { @@ -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 ebe8163a1..56591261f 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()) @@ -35,7 +35,7 @@ CForeignToplevelHandleWlr::CForeignToplevelHandleWlr(SPm_suppressedEvents & SUPPRESS_FULLSCREEN) + if UNLIKELY (PWINDOW->m_suppressedEvents & Desktop::View::SUPPRESS_FULLSCREEN) return; if UNLIKELY (!PWINDOW->m_isMapped) { @@ -66,7 +66,7 @@ CForeignToplevelHandleWlr::CForeignToplevelHandleWlr(SPm_suppressedEvents & SUPPRESS_FULLSCREEN) + if UNLIKELY (PWINDOW->m_suppressedEvents & Desktop::View::SUPPRESS_FULLSCREEN) return; g_pCompositor->changeWindowFullscreenModeClient(PWINDOW, FSMODE_FULLSCREEN, false); @@ -78,7 +78,7 @@ CForeignToplevelHandleWlr::CForeignToplevelHandleWlr(SPm_suppressedEvents & SUPPRESS_MAXIMIZE) + if UNLIKELY (PWINDOW->m_suppressedEvents & Desktop::View::SUPPRESS_MAXIMIZE) return; if UNLIKELY (!PWINDOW->m_isMapped) { @@ -95,7 +95,7 @@ CForeignToplevelHandleWlr::CForeignToplevelHandleWlr(SPm_suppressedEvents & SUPPRESS_MAXIMIZE) + if UNLIKELY (PWINDOW->m_suppressedEvents & Desktop::View::SUPPRESS_MAXIMIZE) return; g_pCompositor->changeWindowFullscreenModeClient(PWINDOW, FSMODE_MAXIMIZED, false); @@ -154,17 +154,23 @@ void CForeignToplevelHandleWlr::sendMonitor(PHLMONITOR pMonitor) { const auto CLIENT = m_resource->client(); if (const auto PLASTMONITOR = g_pCompositor->getMonitorFromID(m_lastMonitorID); PLASTMONITOR && PROTO::outputs.contains(PLASTMONITOR->m_name)) { - const auto OLDRESOURCE = PROTO::outputs.at(PLASTMONITOR->m_name)->outputResourceFrom(CLIENT); + const auto OLDRESOURCES = PROTO::outputs.at(PLASTMONITOR->m_name)->outputResourcesFrom(CLIENT); - if LIKELY (OLDRESOURCE) - m_resource->sendOutputLeave(OLDRESOURCE->getResource()->resource()); + if LIKELY (!OLDRESOURCES.empty()) { + for (const auto& r : OLDRESOURCES) { + m_resource->sendOutputLeave(r->getResource()->resource()); + } + } } if (PROTO::outputs.contains(pMonitor->m_name)) { - const auto NEWRESOURCE = PROTO::outputs.at(pMonitor->m_name)->outputResourceFrom(CLIENT); + const auto NEWRESOURCES = PROTO::outputs.at(pMonitor->m_name)->outputResourcesFrom(CLIENT); - if LIKELY (NEWRESOURCE) - m_resource->sendOutputEnter(NEWRESOURCE->getResource()->resource()); + if LIKELY (!NEWRESOURCES.empty()) { + for (const auto& r : NEWRESOURCES) { + m_resource->sendOutputEnter(r->getResource()->resource()); + } + } } m_lastMonitorID = pMonitor->m_id; @@ -206,7 +212,7 @@ CForeignToplevelWlrManager::CForeignToplevelWlrManager(SPsetStop([this](CZwlrForeignToplevelManagerV1* h) { m_resource->sendFinished(); m_finished = true; - LOGM(LOG, "CForeignToplevelWlrManager: finished"); + LOGM(Log::DEBUG, "CForeignToplevelWlrManager: finished"); PROTO::foreignToplevelWlr->onManagerResourceDestroy(this); }); @@ -228,13 +234,13 @@ void CForeignToplevelWlrManager::onMap(PHLWINDOW pWindow) { makeShared(makeShared(m_resource->client(), m_resource->version(), 0), pWindow)); if UNLIKELY (!NEWHANDLE->good()) { - LOGM(ERR, "Couldn't create a foreign handle"); + LOGM(Log::ERR, "Couldn't create a foreign handle"); m_resource->noMemory(); PROTO::foreignToplevelWlr->m_handles.pop_back(); return; } - LOGM(LOG, "Newly mapped window {:016x}", (uintptr_t)pWindow.get()); + LOGM(Log::DEBUG, "Newly mapped window {:016x}", (uintptr_t)pWindow.get()); m_resource->sendToplevel(NEWHANDLE->m_resource.get()); NEWHANDLE->m_resource->sendAppId(pWindow->m_class.c_str()); NEWHANDLE->m_resource->sendTitle(pWindow->m_title.c_str()); @@ -337,70 +343,57 @@ bool CForeignToplevelWlrManager::good() { } CForeignToplevelWlrProtocol::CForeignToplevelWlrProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - static auto P = g_pHookSystem->hookDynamic("openWindow", [this](void* self, SCallbackInfo& info, std::any data) { - const auto PWINDOW = std::any_cast(data); - - if (!windowValidForForeign(PWINDOW)) + static auto P = Event::bus()->m_events.window.open.listen([this](PHLWINDOW window) { + if (!windowValidForForeign(window)) return; for (auto const& m : m_managers) { - m->onMap(PWINDOW); + m->onMap(window); } }); - static auto P1 = g_pHookSystem->hookDynamic("closeWindow", [this](void* self, SCallbackInfo& info, std::any data) { - const auto PWINDOW = std::any_cast(data); - - if (!windowValidForForeign(PWINDOW)) + static auto P1 = Event::bus()->m_events.window.close.listen([this](PHLWINDOW window) { + if (!windowValidForForeign(window)) return; for (auto const& m : m_managers) { - m->onUnmap(PWINDOW); + m->onUnmap(window); } }); - static auto P2 = g_pHookSystem->hookDynamic("windowTitle", [this](void* self, SCallbackInfo& info, std::any data) { - const auto PWINDOW = std::any_cast(data); - - if (!windowValidForForeign(PWINDOW)) + static auto P2 = Event::bus()->m_events.window.title.listen([this](PHLWINDOW window) { + if (!windowValidForForeign(window)) return; for (auto const& m : m_managers) { - m->onTitle(PWINDOW); + m->onTitle(window); } }); - static auto P3 = g_pHookSystem->hookDynamic("activeWindow", [this](void* self, SCallbackInfo& info, std::any data) { - const auto PWINDOW = std::any_cast(data); - - if (PWINDOW && !windowValidForForeign(PWINDOW)) + static auto P3 = Event::bus()->m_events.window.active.listen([this](PHLWINDOW window, Desktop::eFocusReason reason) { + if (window && !windowValidForForeign(window)) return; for (auto const& m : m_managers) { - m->onNewFocus(PWINDOW); + m->onNewFocus(window); } }); - static auto P4 = g_pHookSystem->hookDynamic("moveWindow", [this](void* self, SCallbackInfo& info, std::any data) { - const auto PWINDOW = std::any_cast(std::any_cast>(data).at(0)); - const auto PWORKSPACE = std::any_cast(std::any_cast>(data).at(1)); - - if (!PWORKSPACE) + static auto P4 = Event::bus()->m_events.window.moveToWorkspace.listen([this](PHLWINDOW window, PHLWORKSPACE ws) { + if (!ws) return; for (auto const& m : m_managers) { - m->onMoveMonitor(PWINDOW, PWORKSPACE->m_monitor.lock()); + m->onMoveMonitor(window, ws->m_monitor.lock()); } }); - static auto P5 = g_pHookSystem->hookDynamic("fullscreen", [this](void* self, SCallbackInfo& info, std::any data) { - const auto PWINDOW = std::any_cast(data); - - if (!windowValidForForeign(PWINDOW)) + static auto P5 = Event::bus()->m_events.window.fullscreen.listen([this](PHLWINDOW window) { + if (!windowValidForForeign(window)) return; for (auto const& m : m_managers) { - m->onFullscreen(PWINDOW); + m->onFullscreen(window); } }); } @@ -409,7 +402,7 @@ void CForeignToplevelWlrProtocol::bindManager(wl_client* client, void* data, uin const auto RESOURCE = m_managers.emplace_back(makeUnique(makeShared(client, ver, id))).get(); if UNLIKELY (!RESOURCE->good()) { - LOGM(ERR, "Couldn't create a foreign list"); + LOGM(Log::ERR, "Couldn't create a foreign list"); wl_client_post_no_memory(client); m_managers.pop_back(); return; diff --git a/src/protocols/ForeignToplevelWlr.hpp b/src/protocols/ForeignToplevelWlr.hpp index abfadf599..444cbe0de 100644 --- a/src/protocols/ForeignToplevelWlr.hpp +++ b/src/protocols/ForeignToplevelWlr.hpp @@ -4,7 +4,6 @@ #include "WaylandProtocol.hpp" #include "wlr-foreign-toplevel-management-unstable-v1.hpp" -class CWindow; class CMonitor; class CForeignToplevelHandleWlr { diff --git a/src/protocols/FractionalScale.cpp b/src/protocols/FractionalScale.cpp index 899d1390f..9bdf59105 100644 --- a/src/protocols/FractionalScale.cpp +++ b/src/protocols/FractionalScale.cpp @@ -26,7 +26,7 @@ void CFractionalScaleProtocol::onManagerResourceDestroy(wl_resource* res) { void CFractionalScaleProtocol::onGetFractionalScale(CWpFractionalScaleManagerV1* pMgr, uint32_t id, SP surface) { for (auto const& [k, v] : m_addons) { if (k == surface) { - LOGM(ERR, "Surface {:x} already has a fractionalScale addon", (uintptr_t)surface.get()); + LOGM(Log::ERR, "Surface {:x} already has a fractionalScale addon", (uintptr_t)surface.get()); pMgr->error(WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_FRACTIONAL_SCALE_EXISTS, "Fractional scale already exists"); return; } diff --git a/src/protocols/FrogColorManagement.cpp b/src/protocols/FrogColorManagement.cpp deleted file mode 100644 index 730a15b04..000000000 --- a/src/protocols/FrogColorManagement.cpp +++ /dev/null @@ -1,180 +0,0 @@ -#include "FrogColorManagement.hpp" -#include "color-management-v1.hpp" -#include "macros.hpp" -#include "protocols/ColorManagement.hpp" -#include "protocols/core/Subcompositor.hpp" -#include "protocols/types/ColorManagement.hpp" - -using namespace NColorManagement; - -static wpColorManagerV1TransferFunction getWPTransferFunction(frogColorManagedSurfaceTransferFunction tf) { - switch (tf) { - case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_UNDEFINED: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886; - case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_SRGB: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB; - case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_GAMMA_22: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22; - case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_ST2084_PQ: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ; - case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_SCRGB_LINEAR: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR; - default: UNREACHABLE(); - } -} - -static wpColorManagerV1Primaries getWPPrimaries(frogColorManagedSurfacePrimaries primaries) { - return sc(primaries + 1); -} - -CFrogColorManager::CFrogColorManager(SP resource_) : m_resource(resource_) { - if UNLIKELY (!good()) - return; - - m_resource->setDestroy([](CFrogColorManagementFactoryV1* r) { LOGM(TRACE, "Destroy frog_color_management at {:x} (generated default)", (uintptr_t)r); }); - m_resource->setOnDestroy([this](CFrogColorManagementFactoryV1* r) { PROTO::frogColorManagement->destroyResource(this); }); - - m_resource->setGetColorManagedSurface([](CFrogColorManagementFactoryV1* r, wl_resource* surface, uint32_t id) { - LOGM(TRACE, "Get surface for id={}, surface={}", id, (uintptr_t)surface); - auto SURF = CWLSurfaceResource::fromResource(surface); - - if (!SURF) { - LOGM(ERR, "No surface for resource {}", (uintptr_t)surface); - r->error(-1, "Invalid surface (2)"); - return; - } - - const auto RESOURCE = - PROTO::frogColorManagement->m_surfaces.emplace_back(makeShared(makeShared(r->client(), r->version(), id), SURF)); - if UNLIKELY (!RESOURCE->good()) { - r->noMemory(); - PROTO::frogColorManagement->m_surfaces.pop_back(); - return; - } - - RESOURCE->m_self = RESOURCE; - }); -} - -bool CFrogColorManager::good() { - return m_resource->resource(); -} - -CFrogColorManagementSurface::CFrogColorManagementSurface(SP resource_, SP surface_) : m_surface(surface_), m_resource(resource_) { - if UNLIKELY (!good()) - return; - - m_client = m_resource->client(); - - if (!m_surface->m_colorManagement.valid()) { - const auto RESOURCE = PROTO::colorManagement->m_surfaces.emplace_back(makeShared(surface_)); - if UNLIKELY (!RESOURCE) { - m_resource->noMemory(); - PROTO::colorManagement->m_surfaces.pop_back(); - return; - } - - RESOURCE->m_self = RESOURCE; - - m_surface->m_colorManagement = RESOURCE; - - m_resource->setOnDestroy([this](CFrogColorManagedSurface* r) { - LOGM(TRACE, "Destroy frog cm and xx cm for surface {}", (uintptr_t)m_surface); - if (m_surface.valid()) - PROTO::colorManagement->destroyResource(m_surface->m_colorManagement.get()); - PROTO::frogColorManagement->destroyResource(this); - }); - } else - m_resource->setOnDestroy([this](CFrogColorManagedSurface* r) { - LOGM(TRACE, "Destroy frog cm surface {}", (uintptr_t)m_surface); - PROTO::frogColorManagement->destroyResource(this); - }); - - m_resource->setDestroy([this](CFrogColorManagedSurface* r) { - LOGM(TRACE, "Destroy frog cm surface {}", (uintptr_t)m_surface); - PROTO::frogColorManagement->destroyResource(this); - }); - - m_resource->setSetKnownTransferFunction([this](CFrogColorManagedSurface* r, frogColorManagedSurfaceTransferFunction tf) { - LOGM(TRACE, "Set frog cm transfer function {} for {}", (uint32_t)tf, m_surface->id()); - switch (tf) { - case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_ST2084_PQ: - m_surface->m_colorManagement->m_imageDescription.transferFunction = - convertTransferFunction(getWPTransferFunction(FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_ST2084_PQ)); - break; - ; - case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_GAMMA_22: - if (m_pqIntentSent) { - LOGM(TRACE, - "FIXME: assuming broken enum value 2 (FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_GAMMA_22) referring to eotf value 2 (TRANSFER_FUNCTION_ST2084_PQ)"); - m_surface->m_colorManagement->m_imageDescription.transferFunction = - convertTransferFunction(getWPTransferFunction(FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_ST2084_PQ)); - break; - }; - [[fallthrough]]; - case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_UNDEFINED: - case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_SCRGB_LINEAR: LOGM(TRACE, "FIXME: add tf support for {}", (uint32_t)tf); [[fallthrough]]; - case FROG_COLOR_MANAGED_SURFACE_TRANSFER_FUNCTION_SRGB: - m_surface->m_colorManagement->m_imageDescription.transferFunction = convertTransferFunction(getWPTransferFunction(tf)); - - m_surface->m_colorManagement->setHasImageDescription(true); - } - }); - m_resource->setSetKnownContainerColorVolume([this](CFrogColorManagedSurface* r, frogColorManagedSurfacePrimaries primariesName) { - LOGM(TRACE, "Set frog cm primaries {}", (uint32_t)primariesName); - switch (primariesName) { - case FROG_COLOR_MANAGED_SURFACE_PRIMARIES_UNDEFINED: - case FROG_COLOR_MANAGED_SURFACE_PRIMARIES_REC709: m_surface->m_colorManagement->m_imageDescription.primaries = NColorPrimaries::BT709; break; - case FROG_COLOR_MANAGED_SURFACE_PRIMARIES_REC2020: m_surface->m_colorManagement->m_imageDescription.primaries = NColorPrimaries::BT2020; break; - } - m_surface->m_colorManagement->m_imageDescription.primariesNamed = convertPrimaries(getWPPrimaries(primariesName)); - - m_surface->m_colorManagement->setHasImageDescription(true); - }); - m_resource->setSetRenderIntent([this](CFrogColorManagedSurface* r, frogColorManagedSurfaceRenderIntent intent) { - LOGM(TRACE, "Set frog cm intent {}", (uint32_t)intent); - m_pqIntentSent = intent == FROG_COLOR_MANAGED_SURFACE_RENDER_INTENT_PERCEPTUAL; - m_surface->m_colorManagement->setHasImageDescription(true); - }); - m_resource->setSetHdrMetadata([this](CFrogColorManagedSurface* r, uint32_t r_x, uint32_t r_y, uint32_t g_x, uint32_t g_y, uint32_t b_x, uint32_t b_y, uint32_t w_x, - uint32_t w_y, uint32_t max_lum, uint32_t min_lum, uint32_t cll, uint32_t fall) { - LOGM(TRACE, "Set frog primaries r:{},{} g:{},{} b:{},{} w:{},{} luminances {} - {} cll {} fall {}", r_x, r_y, g_x, g_y, b_x, b_y, w_x, w_y, min_lum, max_lum, cll, fall); - m_surface->m_colorManagement->m_imageDescription.masteringPrimaries = SPCPRimaries{.red = {.x = r_x / 50000.0f, .y = r_y / 50000.0f}, - .green = {.x = g_x / 50000.0f, .y = g_y / 50000.0f}, - .blue = {.x = b_x / 50000.0f, .y = b_y / 50000.0f}, - .white = {.x = w_x / 50000.0f, .y = w_y / 50000.0f}}; - m_surface->m_colorManagement->m_imageDescription.masteringLuminances.min = min_lum / 10000.0f; - m_surface->m_colorManagement->m_imageDescription.masteringLuminances.max = max_lum; - m_surface->m_colorManagement->m_imageDescription.maxCLL = cll; - m_surface->m_colorManagement->m_imageDescription.maxFALL = fall; - - m_surface->m_colorManagement->setHasImageDescription(true); - }); -} - -bool CFrogColorManagementSurface::good() { - return m_resource->resource(); -} - -wl_client* CFrogColorManagementSurface::client() { - return m_client; -} - -CFrogColorManagementProtocol::CFrogColorManagementProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - ; -} - -void CFrogColorManagementProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { - const auto RESOURCE = m_managers.emplace_back(makeShared(makeShared(client, ver, id))); - - if UNLIKELY (!RESOURCE->good()) { - wl_client_post_no_memory(client); - m_managers.pop_back(); - return; - } - - LOGM(TRACE, "New frog_color_management at {:x}", (uintptr_t)RESOURCE.get()); -} - -void CFrogColorManagementProtocol::destroyResource(CFrogColorManager* resource) { - std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; }); -} - -void CFrogColorManagementProtocol::destroyResource(CFrogColorManagementSurface* resource) { - std::erase_if(m_surfaces, [&](const auto& other) { return other.get() == resource; }); -} diff --git a/src/protocols/FrogColorManagement.hpp b/src/protocols/FrogColorManagement.hpp deleted file mode 100644 index 32e2202c6..000000000 --- a/src/protocols/FrogColorManagement.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include -#include "WaylandProtocol.hpp" -#include "protocols/core/Compositor.hpp" -#include "frog-color-management-v1.hpp" - -class CFrogColorManager { - public: - CFrogColorManager(SP resource_); - - bool good(); - - private: - SP m_resource; -}; - -class CFrogColorManagementSurface { - public: - CFrogColorManagementSurface(SP resource_, SP surface_); - - bool good(); - wl_client* client(); - - WP m_self; - WP m_surface; - - bool m_pqIntentSent = false; - - private: - SP m_resource; - wl_client* m_client = nullptr; -}; - -class CFrogColorManagementProtocol : public IWaylandProtocol { - public: - CFrogColorManagementProtocol(const wl_interface* iface, const int& ver, const std::string& name); - - virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); - - private: - void destroyResource(CFrogColorManager* resource); - void destroyResource(CFrogColorManagementSurface* resource); - - std::vector> m_managers; - std::vector> m_surfaces; - - friend class CFrogColorManager; - friend class CFrogColorManagementSurface; -}; - -namespace PROTO { - inline UP frogColorManagement; -}; diff --git a/src/protocols/GammaControl.cpp b/src/protocols/GammaControl.cpp index 58382de98..c28b881f6 100644 --- a/src/protocols/GammaControl.cpp +++ b/src/protocols/GammaControl.cpp @@ -13,7 +13,7 @@ CGammaControl::CGammaControl(SP resource_, wl_resource* out auto OUTPUTRES = CWLOutputResource::fromResource(output); if UNLIKELY (!OUTPUTRES) { - LOGM(ERR, "No output in CGammaControl"); + LOGM(Log::ERR, "No output in CGammaControl"); m_resource->sendFailed(); return; } @@ -21,7 +21,7 @@ CGammaControl::CGammaControl(SP resource_, wl_resource* out m_monitor = OUTPUTRES->m_monitor; if UNLIKELY (!m_monitor || !m_monitor->m_output) { - LOGM(ERR, "No CMonitor"); + LOGM(Log::ERR, "No CMonitor"); m_resource->sendFailed(); return; } @@ -36,7 +36,7 @@ CGammaControl::CGammaControl(SP resource_, wl_resource* out m_gammaSize = m_monitor->m_output->getGammaSize(); if UNLIKELY (m_gammaSize <= 0) { - LOGM(ERR, "Output {} doesn't support gamma", m_monitor->m_name); + LOGM(Log::ERR, "Output {} doesn't support gamma", m_monitor->m_name); m_resource->sendFailed(); return; } @@ -49,24 +49,30 @@ CGammaControl::CGammaControl(SP resource_, wl_resource* out m_resource->setSetGamma([this](CZwlrGammaControlV1* gamma, int32_t fd) { CFileDescriptor gammaFd{fd}; if UNLIKELY (!m_monitor) { - LOGM(ERR, "setGamma for a dead monitor"); + LOGM(Log::ERR, "setGamma for a dead monitor"); m_resource->sendFailed(); return; } - LOGM(LOG, "setGamma for {}", m_monitor->m_name); + 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) { - LOGM(ERR, "Failed to get fd flags"); + LOGM(Log::ERR, "Failed to get fd flags"); m_resource->sendFailed(); return; } // TODO: make CFileDescriptor setflags use F_SETFL if UNLIKELY (fcntl(gammaFd.get(), F_SETFL, fdFlags | O_NONBLOCK) < 0) { - LOGM(ERR, "Failed to set fd flags"); + LOGM(Log::ERR, "Failed to set fd flags"); m_resource->sendFailed(); return; } @@ -81,7 +87,7 @@ CGammaControl::CGammaControl(SP resource_, wl_resource* out } if (readBytes < 0 || sc(readBytes) != m_gammaTable.size() * sizeof(uint16_t) || moreBytes != 0) { - LOGM(ERR, "Failed to read bytes"); + LOGM(Log::ERR, "Failed to read bytes"); if (sc(readBytes) != m_gammaTable.size() * sizeof(uint16_t) || moreBytes > 0) { gamma->error(ZWLR_GAMMA_CONTROL_V1_ERROR_INVALID_GAMMA, "Gamma ramps size mismatch"); @@ -136,7 +142,7 @@ void CGammaControl::applyToMonitor() { if UNLIKELY (!m_monitor || !m_monitor->m_output) return; // ?? - LOGM(LOG, "setting to monitor {}", m_monitor->m_name); + LOGM(Log::DEBUG, "setting to monitor {}", m_monitor->m_name); if (!m_gammaTableSet) { m_monitor->m_output->state->setGammaLut({}); @@ -146,7 +152,7 @@ void CGammaControl::applyToMonitor() { m_monitor->m_output->state->setGammaLut(m_gammaTable); if (!m_monitor->m_state.test()) { - LOGM(LOG, "setting to monitor {} failed", m_monitor->m_name); + LOGM(Log::DEBUG, "setting to monitor {} failed", m_monitor->m_name); m_monitor->m_output->state->setGammaLut({}); } @@ -158,7 +164,7 @@ PHLMONITOR CGammaControl::getMonitor() { } void CGammaControl::onMonitorDestroy() { - LOGM(LOG, "Destroying gamma control for {}", m_monitor->m_name); + LOGM(Log::DEBUG, "Destroying gamma control for {}", m_monitor->m_name); m_resource->sendFailed(); } diff --git a/src/protocols/HyprlandSurface.cpp b/src/protocols/HyprlandSurface.cpp index b2692d1f6..5d8822063 100644 --- a/src/protocols/HyprlandSurface.cpp +++ b/src/protocols/HyprlandSurface.cpp @@ -1,5 +1,5 @@ #include "HyprlandSurface.hpp" -#include "../desktop/WLSurface.hpp" +#include "../desktop/view/WLSurface.hpp" #include "../render/Renderer.hpp" #include "core/Compositor.hpp" #include "hyprland-surface-v1.hpp" @@ -52,7 +52,7 @@ void CHyprlandSurface::setResource(SP resource) { }); m_listeners.surfaceCommitted = m_surface->m_events.commit.listen([this] { - auto surface = CWLSurface::fromResource(m_surface.lock()); + auto surface = Desktop::View::CWLSurface::fromResource(m_surface.lock()); if (surface && (surface->m_overallOpacity != m_opacity || m_visibleRegionChanged)) { surface->m_overallOpacity = m_opacity; @@ -113,7 +113,7 @@ void CHyprlandSurfaceProtocol::getSurface(CHyprlandSurfaceManagerV1* manager, ui if (iter != m_surfaces.end()) { if (iter->second->m_resource) { - LOGM(ERR, "HyprlandSurface already present for surface {:x}", (uintptr_t)surface.get()); + LOGM(Log::ERR, "HyprlandSurface already present for surface {:x}", (uintptr_t)surface.get()); manager->error(HYPRLAND_SURFACE_MANAGER_V1_ERROR_ALREADY_CONSTRUCTED, "HyprlandSurface already present"); return; } else { diff --git a/src/protocols/IdleNotify.cpp b/src/protocols/IdleNotify.cpp index 4122bf242..b80bf5c66 100644 --- a/src/protocols/IdleNotify.cpp +++ b/src/protocols/IdleNotify.cpp @@ -23,7 +23,7 @@ CExtIdleNotification::CExtIdleNotification(SP resource_, update(); - LOGM(LOG, "Registered idle-notification for {}ms", timeoutMs_); + LOGM(Log::DEBUG, "Registered idle-notification for {}ms", timeoutMs_); } CExtIdleNotification::~CExtIdleNotification() { diff --git a/src/protocols/ImageCaptureSource.cpp b/src/protocols/ImageCaptureSource.cpp new file mode 100644 index 000000000..9f54533e0 --- /dev/null +++ b/src/protocols/ImageCaptureSource.cpp @@ -0,0 +1,135 @@ +#include "ImageCaptureSource.hpp" +#include "core/Output.hpp" +#include "../helpers/Monitor.hpp" +#include "../desktop/view/Window.hpp" +#include "ForeignToplevel.hpp" + +CImageCaptureSource::CImageCaptureSource(SP resource, PHLMONITOR pMonitor) : m_resource(resource), m_monitor(pMonitor) { + if UNLIKELY (!good()) + return; + + m_resource->setData(this); + m_resource->setDestroy([this](CExtImageCaptureSourceV1* pMgr) { PROTO::imageCaptureSource->destroyResource(this); }); + m_resource->setOnDestroy([this](CExtImageCaptureSourceV1* pMgr) { PROTO::imageCaptureSource->destroyResource(this); }); +} + +CImageCaptureSource::CImageCaptureSource(SP resource, PHLWINDOW pWindow) : m_resource(resource), m_window(pWindow) { + if UNLIKELY (!good()) + return; + + m_resource->setData(this); + m_resource->setDestroy([this](CExtImageCaptureSourceV1* pMgr) { PROTO::imageCaptureSource->destroyResource(this); }); + m_resource->setOnDestroy([this](CExtImageCaptureSourceV1* pMgr) { PROTO::imageCaptureSource->destroyResource(this); }); +} + +bool CImageCaptureSource::good() { + return m_resource && m_resource->resource(); +} + +std::string CImageCaptureSource::getName() { + if (!m_monitor.expired()) + return m_monitor->m_name; + if (!m_window.expired()) + return m_window->m_title; + + return "error"; +} + +std::string CImageCaptureSource::getTypeName() { + if (!m_monitor.expired()) + return "monitor"; + if (!m_window.expired()) + return "window"; + + return "error"; +} + +CBox CImageCaptureSource::logicalBox() { + if (!m_monitor.expired()) + return m_monitor->logicalBox(); + if (!m_window.expired()) + return m_window->getFullWindowBoundingBox(); + return CBox(); +} + +COutputImageCaptureSourceProtocol::COutputImageCaptureSourceProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void COutputImageCaptureSourceProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = PROTO::imageCaptureSource->m_outputManagers.emplace_back(makeShared(client, ver, id)); + + if UNLIKELY (!RESOURCE->resource()) { + wl_client_post_no_memory(client); + PROTO::imageCaptureSource->m_outputManagers.pop_back(); + return; + } + + RESOURCE->setDestroy([](CExtOutputImageCaptureSourceManagerV1* pMgr) { PROTO::imageCaptureSource->destroyResource(pMgr); }); + RESOURCE->setOnDestroy([](CExtOutputImageCaptureSourceManagerV1* pMgr) { PROTO::imageCaptureSource->destroyResource(pMgr); }); + RESOURCE->setCreateSource([](CExtOutputImageCaptureSourceManagerV1* pMgr, uint32_t id, wl_resource* output) { + PHLMONITOR pMonitor = CWLOutputResource::fromResource(output)->m_monitor.lock(); + if (!pMonitor) { + LOGM(Log::ERR, "Client tried to create source from invalid output resource"); + pMgr->error(-1, "invalid output resource"); + return; + } + + auto PSOURCE = + PROTO::imageCaptureSource->m_sources.emplace_back(makeShared(makeShared(pMgr->client(), pMgr->version(), id), pMonitor)); + PSOURCE->m_self = PSOURCE; + + LOGM(Log::INFO, "New capture source for monitor: {}", pMonitor->m_name); + }); +} + +CToplevelImageCaptureSourceProtocol::CToplevelImageCaptureSourceProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void CToplevelImageCaptureSourceProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = PROTO::imageCaptureSource->m_toplevelManagers.emplace_back(makeShared(client, ver, id)); + + if UNLIKELY (!RESOURCE->resource()) { + RESOURCE->noMemory(); + PROTO::imageCaptureSource->m_toplevelManagers.pop_back(); + return; + } + + RESOURCE->setDestroy([](CExtForeignToplevelImageCaptureSourceManagerV1* pMgr) { PROTO::imageCaptureSource->destroyResource(pMgr); }); + RESOURCE->setOnDestroy([](CExtForeignToplevelImageCaptureSourceManagerV1* pMgr) { PROTO::imageCaptureSource->destroyResource(pMgr); }); + RESOURCE->setCreateSource([](CExtForeignToplevelImageCaptureSourceManagerV1* pMgr, uint32_t id, wl_resource* handle) { + PHLWINDOW pWindow = PROTO::foreignToplevel->windowFromHandleResource(handle); + if (!pWindow) { + LOGM(Log::ERR, "Client tried to create source from invalid foreign toplevel handle resource"); + pMgr->error(-1, "invalid foreign toplevel resource"); + return; + } + + auto PSOURCE = + PROTO::imageCaptureSource->m_sources.emplace_back(makeShared(makeShared(pMgr->client(), pMgr->version(), id), pWindow)); + PSOURCE->m_self = PSOURCE; + + LOGM(Log::INFO, "New capture source for foreign toplevel: {}", pWindow->m_title); + }); +} + +CImageCaptureSourceProtocol::CImageCaptureSourceProtocol() { + m_output = makeUnique(&ext_output_image_capture_source_manager_v1_interface, 1, "OutputImageCaptureSource"); + m_toplevel = makeUnique(&ext_foreign_toplevel_image_capture_source_manager_v1_interface, 1, "ForeignToplevelImageCaptureSource"); +} + +SP CImageCaptureSourceProtocol::sourceFromResource(wl_resource* res) { + auto data = sc(sc(wl_resource_get_user_data(res))->data()); + return data && data->m_self ? data->m_self.lock() : nullptr; +} + +void CImageCaptureSourceProtocol::destroyResource(CExtOutputImageCaptureSourceManagerV1* resource) { + std::erase_if(m_outputManagers, [&](const auto& other) { return other.get() == resource; }); +} +void CImageCaptureSourceProtocol::destroyResource(CExtForeignToplevelImageCaptureSourceManagerV1* resource) { + std::erase_if(m_toplevelManagers, [&](const auto& other) { return other.get() == resource; }); +} +void CImageCaptureSourceProtocol::destroyResource(CImageCaptureSource* resource) { + std::erase_if(m_sources, [&](const auto& other) { return other.get() == resource; }); +} diff --git a/src/protocols/ImageCaptureSource.hpp b/src/protocols/ImageCaptureSource.hpp new file mode 100644 index 000000000..47580dd25 --- /dev/null +++ b/src/protocols/ImageCaptureSource.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include + +#include "../defines.hpp" +#include "../helpers/signal/Signal.hpp" +#include "WaylandProtocol.hpp" +#include "ext-image-capture-source-v1.hpp" + +class CImageCopyCaptureSession; + +class CImageCaptureSource { + public: + CImageCaptureSource(SP resource, PHLMONITOR pMonitor); + CImageCaptureSource(SP resource, PHLWINDOW pWindow); + + bool good(); + std::string getName(); + std::string getTypeName(); + CBox logicalBox(); + + WP m_self; + + private: + SP m_resource; + + PHLMONITORREF m_monitor; + PHLWINDOWREF m_window; + + friend class CImageCopyCaptureSession; + friend class CImageCopyCaptureCursorSession; +}; + +class COutputImageCaptureSourceProtocol : public IWaylandProtocol { + public: + COutputImageCaptureSourceProtocol(const wl_interface* iface, const int& ver, const std::string& name); + + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); +}; + +class CToplevelImageCaptureSourceProtocol : public IWaylandProtocol { + public: + CToplevelImageCaptureSourceProtocol(const wl_interface* iface, const int& ver, const std::string& name); + + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); +}; + +class CImageCaptureSourceProtocol { + public: + CImageCaptureSourceProtocol(); + + SP sourceFromResource(wl_resource* resource); + + void destroyResource(CExtOutputImageCaptureSourceManagerV1* resource); + void destroyResource(CExtForeignToplevelImageCaptureSourceManagerV1* resource); + void destroyResource(CImageCaptureSource* resource); + + private: + UP m_output; + UP m_toplevel; + + std::vector> m_outputManagers; + std::vector> m_toplevelManagers; + + std::vector> m_sources; + + friend class COutputImageCaptureSourceProtocol; + friend class CToplevelImageCaptureSourceProtocol; +}; + +namespace PROTO { + inline UP imageCaptureSource; +}; diff --git a/src/protocols/ImageCopyCapture.cpp b/src/protocols/ImageCopyCapture.cpp new file mode 100644 index 000000000..2e0527bf0 --- /dev/null +++ b/src/protocols/ImageCopyCapture.cpp @@ -0,0 +1,508 @@ +#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 "render/Renderer.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(); + return; + } + + sendConstraints(); + + m_listeners.constraintsChanged = m_session->m_events.constraintsChanged.listen([this]() { sendConstraints(); }); + m_listeners.stopped = m_session->m_events.stopped.listen([this]() { m_resource->sendStopped(); }); +} + +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(); + return; + } + + for (DRMFormat format : formats) { + m_resource->sendShmFormat(NFormatUtils::drmToShm(format)); + + auto modifiers = g_pHyprRenderer->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(); + 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(); + return; + } + + m_sessionResource->sendShmFormat(NFormatUtils::drmToShm(format)); + + auto modifiers = g_pHyprRenderer->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) + 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"); + pMgr->error(-1, "invalid image capture source"); + return; + } + + const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(pMgr->client(), PERMISSION_TYPE_CURSOR_POS); + if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY) + return; + + m_cursorSessions.emplace_back(makeShared(makeShared(pMgr->client(), pMgr->version(), id), source, + CWLPointerResource::fromResource(pointer_))); + + LOGM(Log::INFO, "New image copy capture cursor session for source ({}): \"{}\"", source->getTypeName(), source->getName()); + }); +} + +void CImageCopyCaptureProtocol::destroyResource(CExtImageCopyCaptureManagerV1* resource) { + std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; }); +} + +void CImageCopyCaptureProtocol::destroyResource(CImageCopyCaptureSession* resource) { + std::erase_if(m_sessions, [&](const auto& other) { return other.get() == resource; }); +} + +void CImageCopyCaptureProtocol::destroyResource(CImageCopyCaptureCursorSession* resource) { + std::erase_if(m_cursorSessions, [&](const auto& other) { return other.get() == resource; }); +} + +void CImageCopyCaptureProtocol::destroyResource(CImageCopyCaptureFrame* resource) { + std::erase_if(m_frames, [&](const auto& other) { return other.get() == resource; }); +} diff --git a/src/protocols/ImageCopyCapture.hpp b/src/protocols/ImageCopyCapture.hpp new file mode 100644 index 000000000..b8cfa1e89 --- /dev/null +++ b/src/protocols/ImageCopyCapture.hpp @@ -0,0 +1,133 @@ +#pragma once + +#include + +#include "../defines.hpp" +#include "../helpers/signal/Signal.hpp" +#include "../helpers/Format.hpp" +#include "WaylandProtocol.hpp" +#include "ImageCaptureSource.hpp" +#include "ext-image-copy-capture-v1.hpp" + +class IHLBuffer; +class CWLPointerResource; +namespace Screenshare { + class CCursorshareSession; + class CScreenshareSession; + class CScreenshareFrame; +}; + +class CImageCopyCaptureFrame { + public: + CImageCopyCaptureFrame(SP resource, WP session); + ~CImageCopyCaptureFrame(); + + bool good(); + + private: + SP m_resource; + WP m_session; + UP m_frame; + + bool m_captured = false; + SP m_buffer; + CRegion m_clientDamage; + + friend class CImageCopyCaptureSession; +}; + +class CImageCopyCaptureSession { + public: + CImageCopyCaptureSession(SP resource, SP source, extImageCopyCaptureManagerV1Options options); + ~CImageCopyCaptureSession(); + + bool good(); + + private: + SP m_resource; + + SP m_source; + UP m_session; + WP m_frame; + + Vector2D m_bufferSize = Vector2D(0, 0); + bool m_paintCursor = true; + + struct { + CHyprSignalListener constraintsChanged; + CHyprSignalListener stopped; + } m_listeners; + + WP m_self; + + // + void sendConstraints(); + + friend class CImageCopyCaptureProtocol; + friend class CImageCopyCaptureFrame; +}; + +class CImageCopyCaptureCursorSession { + public: + CImageCopyCaptureCursorSession(SP resource, SP source, SP pointer); + ~CImageCopyCaptureCursorSession(); + + bool good(); + + private: + SP m_resource; + SP m_source; + SP m_pointer; + + // cursor session stuff + bool m_entered = false; + Vector2D m_pos = Vector2D(0, 0); + Vector2D m_hotspot = Vector2D(0, 0); + + // capture session stuff + SP m_sessionResource; + UP m_session; + Vector2D m_bufferSize = Vector2D(0, 0); + + // frame stuff + SP m_frameResource; + bool m_captured = false; + SP m_buffer; + + struct { + CHyprSignalListener constraintsChanged; + CHyprSignalListener stopped; + CHyprSignalListener commit; + } m_listeners; + + void sendCursorEvents(); + + void createFrame(SP resource); + void destroyCaptureSession(); + void sendConstraints(); +}; + +class CImageCopyCaptureProtocol : public IWaylandProtocol { + public: + CImageCopyCaptureProtocol(const wl_interface* iface, const int& ver, const std::string& name); + + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); + + void destroyResource(CExtImageCopyCaptureManagerV1* resource); + void destroyResource(CImageCopyCaptureSession* resource); + void destroyResource(CImageCopyCaptureCursorSession* resource); + void destroyResource(CImageCopyCaptureFrame* resource); + + private: + std::vector> m_managers; + std::vector> m_sessions; + std::vector> m_cursorSessions; + + std::vector> m_frames; + + friend class CImageCopyCaptureSession; +}; + +namespace PROTO { + inline UP imageCopyCapture; +}; diff --git a/src/protocols/InputMethodV2.cpp b/src/protocols/InputMethodV2.cpp index eaf7f1418..0917d100b 100644 --- a/src/protocols/InputMethodV2.cpp +++ b/src/protocols/InputMethodV2.cpp @@ -15,7 +15,7 @@ CInputMethodKeyboardGrabV2::CInputMethodKeyboardGrabV2(SPsetOnDestroy([this](CZwpInputMethodKeyboardGrabV2* r) { PROTO::ime->destroyResource(this); }); if (!g_pSeatManager->m_keyboard) { - LOGM(ERR, "IME called but no active keyboard???"); + LOGM(Log::ERR, "IME called but no active keyboard???"); return; } @@ -36,13 +36,13 @@ void CInputMethodKeyboardGrabV2::sendKeyboardData(SP keyboard) { auto keymapFD = allocateSHMFile(keyboard->m_xkbKeymapV1String.length() + 1); if UNLIKELY (!keymapFD.isValid()) { - LOGM(ERR, "Failed to create a keymap file for keyboard grab"); + LOGM(Log::ERR, "Failed to create a keymap file for keyboard grab"); return; } void* data = mmap(nullptr, keyboard->m_xkbKeymapV1String.length() + 1, PROT_READ | PROT_WRITE, MAP_SHARED, keymapFD.get(), 0); if UNLIKELY (data == MAP_FAILED) { - LOGM(ERR, "Failed to mmap a keymap file for keyboard grab"); + LOGM(Log::ERR, "Failed to mmap a keymap file for keyboard grab"); return; } @@ -194,7 +194,7 @@ CInputMethodV2::CInputMethodV2(SP resource_) : m_resource(res return; } - LOGM(LOG, "New IME Popup with resource id {}", id); + LOGM(Log::DEBUG, "New IME Popup with resource id {}", id); m_popups.emplace_back(RESOURCE); @@ -211,7 +211,7 @@ CInputMethodV2::CInputMethodV2(SP resource_) : m_resource(res return; } - LOGM(LOG, "New IME Grab with resource id {}", id); + LOGM(Log::DEBUG, "New IME Grab with resource id {}", id); m_grabs.emplace_back(RESOURCE); }); @@ -367,7 +367,7 @@ void CInputMethodV2Protocol::onGetIME(CZwpInputMethodManagerV2* mgr, wl_resource RESOURCE->m_self = RESOURCE; - LOGM(LOG, "New IME with resource id {}", id); + LOGM(Log::DEBUG, "New IME with resource id {}", id); m_events.newIME.emit(RESOURCE); } diff --git a/src/protocols/InputMethodV2.hpp b/src/protocols/InputMethodV2.hpp index 4ee579ec1..b948d6099 100644 --- a/src/protocols/InputMethodV2.hpp +++ b/src/protocols/InputMethodV2.hpp @@ -6,7 +6,7 @@ #include "input-method-unstable-v2.hpp" #include "text-input-unstable-v3.hpp" #include "../helpers/signal/Signal.hpp" -#include "../desktop/WLSurface.hpp" +#include "../desktop/view/WLSurface.hpp" class CInputMethodKeyboardGrabV2; class CInputMethodPopupV2; diff --git a/src/protocols/LayerShell.cpp b/src/protocols/LayerShell.cpp index 2ed4bfb1c..802226273 100644 --- a/src/protocols/LayerShell.cpp +++ b/src/protocols/LayerShell.cpp @@ -1,6 +1,6 @@ #include "LayerShell.hpp" #include "../Compositor.hpp" -#include "../desktop/LayerSurface.hpp" +#include "../desktop/view/LayerSurface.hpp" #include "XDGShell.hpp" #include "core/Compositor.hpp" #include "core/Output.hpp" @@ -247,9 +247,9 @@ void CLayerShellProtocol::onGetLayerSurface(CZwlrLayerShellV1* pMgr, uint32_t id } SURF->m_role = makeShared(RESOURCE); - g_pCompositor->m_layers.emplace_back(CLayerSurface::create(RESOURCE)); + g_pCompositor->m_layers.emplace_back(Desktop::View::CLayerSurface::create(RESOURCE)); - LOGM(LOG, "New wlr_layer_surface {:x}", (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New wlr_layer_surface {:x}", (uintptr_t)RESOURCE.get()); } CLayerShellRole::CLayerShellRole(SP ls) : m_layerSurface(ls) { diff --git a/src/protocols/LinuxDMABUF.cpp b/src/protocols/LinuxDMABUF.cpp index 4e90d870c..7886826b4 100644 --- a/src/protocols/LinuxDMABUF.cpp +++ b/src/protocols/LinuxDMABUF.cpp @@ -8,11 +8,12 @@ #include #include #include "core/Compositor.hpp" +#include "render/Renderer.hpp" #include "types/DMABuffer.hpp" #include "types/WLBuffer.hpp" -#include "../managers/HookSystemManager.hpp" #include "../render/OpenGL.hpp" #include "../Compositor.hpp" +#include "../event/EventBus.hpp" using namespace Hyprutils::OS; @@ -26,6 +27,8 @@ static std::optional devIDFromFD(int fd) { CDMABUFFormatTable::CDMABUFFormatTable(SDMABUFTranche _rendererTranche, std::vector> tranches_) : m_rendererTranche(_rendererTranche), m_monitorTranches(tranches_) { + static const auto PSKIP_NON_KMS = CConfigValue("quirks:skip_non_kms_dmabuf_formats"); + std::vector formatsVec; std::set> formats; @@ -35,6 +38,17 @@ CDMABUFFormatTable::CDMABUFFormatTable(SDMABUFTranche _rendererTranche, std::vec m_rendererTranche.indices.clear(); for (auto const& fmt : m_rendererTranche.formats) { for (auto const& mod : fmt.modifiers) { + LOGM(Log::TRACE, "Render format 0x{:x} ({}) with mod 0x{:x} ({})", fmt.drmFormat, NFormatUtils::drmFormatName(fmt.drmFormat), mod, NFormatUtils::drmModifierName(mod)); + if (*PSKIP_NON_KMS && !m_monitorTranches.empty()) { + if (std::ranges::none_of(m_monitorTranches, [fmt, mod](const std::pair& pair) { + return std::ranges::any_of(pair.second.formats, [fmt, mod](const SDRMFormat& format) { + return format.drmFormat == fmt.drmFormat && std::ranges::any_of(format.modifiers, [mod](uint64_t modifier) { return mod == modifier; }); + }); + })) { + LOGM(Log::TRACE, " skipped"); + continue; + } + } auto format = std::make_pair<>(fmt.drmFormat, mod); auto [_, inserted] = formats.insert(format); if (inserted) { @@ -56,6 +70,9 @@ CDMABUFFormatTable::CDMABUFFormatTable(SDMABUFTranche _rendererTranche, std::vec tranche.indices.clear(); for (auto const& fmt : tranche.formats) { for (auto const& mod : fmt.modifiers) { + LOGM(Log::TRACE, "[DMA] Monitor format 0x{:x} ({}) with mod 0x{:x} ({})", fmt.drmFormat, NFormatUtils::drmFormatName(fmt.drmFormat), mod, + NFormatUtils::drmModifierName(mod)); + // FIXME: recheck this. DRM_FORMAT_MOD_INVALID is allowed by the proto "For legacy support". DRM_FORMAT_MOD_LINEAR should be the most compatible mod // apparently these can implode on planes, so don't use them if (mod == DRM_FORMAT_MOD_INVALID || mod == DRM_FORMAT_MOD_LINEAR) continue; @@ -83,7 +100,7 @@ CDMABUFFormatTable::CDMABUFFormatTable(SDMABUFTranche _rendererTranche, std::vec auto arr = sc(mmap(nullptr, m_tableSize, PROT_READ | PROT_WRITE, MAP_SHARED, fds[0].get(), 0)); if (arr == MAP_FAILED) { - LOGM(ERR, "mmap failed"); + LOGM(Log::ERR, "mmap failed"); return; } @@ -105,7 +122,7 @@ CLinuxDMABuffer::CLinuxDMABuffer(uint32_t id, wl_client* client, Aquamarine::SDM }); if (!m_buffer->m_success) - LOGM(ERR, "Possibly compositor bug: buffer failed to create"); + LOGM(Log::ERR, "Possibly compositor bug: buffer failed to create"); } CLinuxDMABuffer::~CLinuxDMABuffer() { @@ -147,10 +164,18 @@ CLinuxDMABUFParamsResource::CLinuxDMABUFParamsResource(UP(modHi) << 32) | modLo; + + const bool anyPlaneSet = std::ranges::any_of(m_attrs->fds, [](int planeFD) { return planeFD != -1; }); + if (m_resource->version() >= 5 && anyPlaneSet && m_attrs->modifier != modifier) { + r->error(ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT, "planes have different modifiers"); + return; + } + m_attrs->fds[plane] = fd; m_attrs->strides[plane] = stride; m_attrs->offsets[plane] = offset; - m_attrs->modifier = (sc(modHi) << 32) | modLo; + m_attrs->modifier = modifier; }); m_resource->setCreate([this](CZwpLinuxBufferParamsV1* r, int32_t w, int32_t h, uint32_t fmt, zwpLinuxBufferParamsV1Flags flags) { @@ -161,7 +186,14 @@ CLinuxDMABUFParamsResource::CLinuxDMABUFParamsResource(UP 0) { r->sendFailed(); - LOGM(ERR, "DMABUF flags are not supported"); + LOGM(Log::ERR, "DMABUF flags are not supported"); + return; + } + + if (m_resource->version() >= 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; } @@ -180,7 +212,7 @@ CLinuxDMABUFParamsResource::CLinuxDMABUFParamsResource(UP 0) { r->sendFailed(); - LOGM(ERR, "DMABUF flags are not supported"); + LOGM(Log::ERR, "DMABUF flags are not supported"); return; } @@ -200,19 +232,19 @@ void CLinuxDMABUFParamsResource::create(uint32_t id) { m_used = true; if UNLIKELY (!verify()) { - LOGM(ERR, "Failed creating a dmabuf: verify() said no"); + LOGM(Log::ERR, "Failed creating a dmabuf: verify() said no"); return; // if verify failed, we errored the resource. } if UNLIKELY (!commence()) { - LOGM(ERR, "Failed creating a dmabuf: commence() said no"); + LOGM(Log::ERR, "Failed creating a dmabuf: commence() said no"); m_resource->sendFailed(); return; } - LOGM(LOG, "Creating a dmabuf, with id {}: size {}, fmt {}, planes {}", id, m_attrs->size, NFormatUtils::drmFormatName(m_attrs->format), m_attrs->planes); + LOGM(Log::DEBUG, "Creating a dmabuf, with id {}: size {}, fmt {}, planes {}", id, m_attrs->size, NFormatUtils::drmFormatName(m_attrs->format), m_attrs->planes); for (int i = 0; i < m_attrs->planes; ++i) { - LOGM(LOG, " | plane {}: mod {} fd {} stride {} offset {}", i, m_attrs->modifier, m_attrs->fds[i], m_attrs->strides[i], m_attrs->offsets[i]); + LOGM(Log::DEBUG, " | plane {}: mod {} fd {} stride {} offset {}", i, m_attrs->modifier, m_attrs->fds[i], m_attrs->strides[i], m_attrs->offsets[i]); } auto& buf = PROTO::linuxDma->m_buffers.emplace_back(makeUnique(id, m_resource->client(), *m_attrs)); @@ -237,12 +269,12 @@ bool CLinuxDMABUFParamsResource::commence() { uint32_t handle = 0; if (drmPrimeFDToHandle(PROTO::linuxDma->m_mainDeviceFD.get(), m_attrs->fds.at(i), &handle)) { - LOGM(ERR, "Failed to import dmabuf fd"); + LOGM(Log::ERR, "Failed to import dmabuf fd {} on plane {}", m_attrs->fds.at(i), i); return false; } if (drmCloseBufferHandle(PROTO::linuxDma->m_mainDeviceFD.get(), handle)) { - LOGM(ERR, "Failed to close dmabuf handle"); + LOGM(Log::ERR, "Failed to close dmabuf handle"); return false; } } @@ -280,10 +312,11 @@ bool CLinuxDMABUFParamsResource::verify() { } for (size_t i = 0; i < sc(m_attrs->planes); ++i) { - if (sc(m_attrs->offsets.at(i)) + sc(m_attrs->strides.at(i)) * m_attrs->size.y > UINT32_MAX) { + const auto computedSize = sc(m_attrs->offsets.at(i)) + sc(m_attrs->strides.at(i)) * m_attrs->size.y; + if (computedSize > UINT32_MAX) { m_resource->error(ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, std::format("size overflow on plane {}: offset {} + stride {} * height {} = {}, overflows UINT32_MAX", i, sc(m_attrs->offsets.at(i)), - sc(m_attrs->strides.at(i)), m_attrs->size.y, sc(m_attrs->offsets.at(i)) + sc(m_attrs->strides.at(i)))); + sc(m_attrs->strides.at(i)), m_attrs->size.y, computedSize)); return false; } } @@ -318,7 +351,7 @@ void CLinuxDMABUFFeedbackResource::sendTranche(SDMABUFTranche& tranche) { m_resource->sendTrancheFlags(sc(tranche.flags)); wl_array indices = { - .size = tranche.indices.size() * sizeof(tranche.indices.at(0)), + .size = tranche.indices.size() * sizeof(std::vector::value_type), .data = tranche.indices.data(), }; m_resource->sendTrancheFormats(&indices); @@ -382,8 +415,7 @@ CLinuxDMABUFResource::CLinuxDMABUFResource(UP&& resource_) : } }); - if (m_resource->version() < 4) - sendMods(); + sendMods(); } bool CLinuxDMABUFResource::good() { @@ -392,27 +424,25 @@ bool CLinuxDMABUFResource::good() { void CLinuxDMABUFResource::sendMods() { for (auto const& fmt : PROTO::linuxDma->m_formatTable->m_rendererTranche.formats) { - for (auto const& mod : fmt.modifiers) { - if (m_resource->version() < 3) { - if (mod == DRM_FORMAT_MOD_INVALID || mod == DRM_FORMAT_MOD_LINEAR) - m_resource->sendFormat(fmt.drmFormat); - continue; + m_resource->sendFormat(fmt.drmFormat); + + if (m_resource->version() == 3) { + for (auto const& mod : fmt.modifiers) { + // TODO: https://gitlab.freedesktop.org/xorg/xserver/-/issues/1166 + + m_resource->sendModifier(fmt.drmFormat, mod >> 32, mod & 0xFFFFFFFF); } - - // TODO: https://gitlab.freedesktop.org/xorg/xserver/-/issues/1166 - - m_resource->sendModifier(fmt.drmFormat, mod >> 32, mod & 0xFFFFFFFF); } } } CLinuxDMABufV1Protocol::CLinuxDMABufV1Protocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - static auto P = g_pHookSystem->hookDynamic("ready", [this](void* self, SCallbackInfo& info, std::any d) { + static auto P = Event::bus()->m_events.ready.listen([this] { int rendererFD = g_pCompositor->m_drmRenderNode.fd >= 0 ? g_pCompositor->m_drmRenderNode.fd : g_pCompositor->m_drm.fd; auto dev = devIDFromFD(rendererFD); if (!dev.has_value()) { - LOGM(ERR, "failed to get drm dev, disabling linux dmabuf"); + LOGM(Log::ERR, "failed to get drm dev, disabling linux dmabuf"); removeGlobal(); return; } @@ -422,7 +452,7 @@ CLinuxDMABufV1Protocol::CLinuxDMABufV1Protocol(const wl_interface* iface, const SDMABUFTranche eglTranche = { .device = m_mainDevice, .flags = 0, // renderer isn't for ds so don't set flag. - .formats = g_pHyprOpenGL->getDRMFormats(), + .formats = g_pHyprRenderer->getDRMFormats(), }; std::vector> tches; @@ -440,29 +470,36 @@ CLinuxDMABufV1Protocol::CLinuxDMABufV1Protocol(const wl_interface* iface, const tches.emplace_back(std::make_pair<>(mon, tranche)); } - static auto monitorAdded = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any param) { - auto pMonitor = std::any_cast(param); - auto tranche = SDMABUFTranche{ - .device = m_mainDevice, - .flags = ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SCANOUT, - .formats = pMonitor->m_output->getRenderFormats(), + static auto monitorAdded = Event::bus()->m_events.monitor.added.listen([this](PHLMONITOR mon) { + auto tranche = SDMABUFTranche{ + .device = m_mainDevice, + .flags = ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SCANOUT, + .formats = mon->m_output->getRenderFormats(), }; - m_formatTable->m_monitorTranches.emplace_back(std::make_pair<>(pMonitor, tranche)); + m_formatTable->m_monitorTranches.emplace_back(std::make_pair<>(mon, tranche)); resetFormatTable(); }); - static auto monitorRemoved = g_pHookSystem->hookDynamic("monitorRemoved", [this](void* self, SCallbackInfo& info, std::any param) { - auto pMonitor = std::any_cast(param); - std::erase_if(m_formatTable->m_monitorTranches, [pMonitor](std::pair pair) { return pair.first == pMonitor; }); + static auto monitorRemoved = Event::bus()->m_events.monitor.removed.listen([this](PHLMONITOR mon) { + std::erase_if(m_formatTable->m_monitorTranches, [mon](std::pair pair) { return pair.first == mon; }); resetFormatTable(); }); + + static auto configReloaded = Event::bus()->m_events.config.reloaded.listen([this] { + static const auto PSKIP_NON_KMS = CConfigValue("quirks:skip_non_kms_dmabuf_formats"); + static auto prev = *PSKIP_NON_KMS; + if (prev != *PSKIP_NON_KMS) { + prev = *PSKIP_NON_KMS; + resetFormatTable(); + } + }); } m_formatTable = makeUnique(eglTranche, tches); drmDevice* device = nullptr; if (drmGetDeviceFromDevId(m_mainDevice, 0, &device) != 0) { - LOGM(ERR, "failed to get drm dev, disabling linux dmabuf"); + LOGM(Log::ERR, "failed to get drm dev, disabling linux dmabuf"); removeGlobal(); return; } @@ -472,7 +509,7 @@ CLinuxDMABufV1Protocol::CLinuxDMABufV1Protocol(const wl_interface* iface, const m_mainDeviceFD = CFileDescriptor{fcntl(g_pCompositor->m_drmRenderNode.fd, F_DUPFD_CLOEXEC, 0)}; drmFreeDevice(&device); if (!m_mainDeviceFD.isValid()) { - LOGM(ERR, "failed to open rendernode, disabling linux dmabuf"); + LOGM(Log::ERR, "failed to open rendernode, disabling linux dmabuf"); removeGlobal(); return; } @@ -485,12 +522,12 @@ CLinuxDMABufV1Protocol::CLinuxDMABufV1Protocol(const wl_interface* iface, const m_mainDeviceFD = CFileDescriptor{open(name, O_RDWR | O_CLOEXEC)}; drmFreeDevice(&device); if (!m_mainDeviceFD.isValid()) { - LOGM(ERR, "failed to open drm dev, disabling linux dmabuf"); + LOGM(Log::ERR, "failed to open drm dev, disabling linux dmabuf"); removeGlobal(); return; } } else { - LOGM(ERR, "DRM device {} has no render node, disabling linux dmabuf checks", device->nodes[DRM_NODE_PRIMARY] ? device->nodes[DRM_NODE_PRIMARY] : "null"); + LOGM(Log::ERR, "DRM device {} has no render node, disabling linux dmabuf checks", device->nodes[DRM_NODE_PRIMARY] ? device->nodes[DRM_NODE_PRIMARY] : "null"); drmFreeDevice(&device); } }); @@ -500,7 +537,7 @@ void CLinuxDMABufV1Protocol::resetFormatTable() { if (!m_formatTable) return; - LOGM(LOG, "Resetting format table"); + LOGM(Log::DEBUG, "Resetting format table"); // this might be a big copy auto newFormatTable = makeUnique(m_formatTable->m_rendererTranche, m_formatTable->m_monitorTranches); @@ -509,12 +546,12 @@ void CLinuxDMABufV1Protocol::resetFormatTable() { feedback->m_resource->sendFormatTable(newFormatTable->m_tableFD.get(), newFormatTable->m_tableSize); if (feedback->m_lastFeedbackWasScanout) { PHLMONITOR mon; - auto HLSurface = CWLSurface::fromResource(feedback->m_surface); + auto HLSurface = Desktop::View::CWLSurface::fromResource(feedback->m_surface); if (!HLSurface) { feedback->sendDefaultFeedback(); continue; } - if (auto w = HLSurface->getWindow(); w) + if (auto w = Desktop::View::CWindow::fromView(HLSurface->view()); w) if (auto m = w->m_monitor.lock(); m) mon = m->m_self.lock(); @@ -570,12 +607,12 @@ void CLinuxDMABufV1Protocol::updateScanoutTranche(SP surface } if (!feedbackResource) { - LOGM(LOG, "updateScanoutTranche: surface has no dmabuf_feedback"); + LOGM(Log::DEBUG, "updateScanoutTranche: surface has no dmabuf_feedback"); return; } if (!pMonitor) { - LOGM(LOG, "updateScanoutTranche: resetting feedback"); + LOGM(Log::DEBUG, "updateScanoutTranche: resetting feedback"); feedbackResource->sendDefaultFeedback(); return; } @@ -584,13 +621,13 @@ void CLinuxDMABufV1Protocol::updateScanoutTranche(SP surface std::ranges::find_if(m_formatTable->m_monitorTranches, [pMonitor](std::pair pair) { return pair.first == pMonitor; }); if (monitorTranchePair == m_formatTable->m_monitorTranches.end()) { - LOGM(LOG, "updateScanoutTranche: monitor has no tranche"); + LOGM(Log::DEBUG, "updateScanoutTranche: monitor has no tranche"); return; } auto& monitorTranche = (*monitorTranchePair).second; - LOGM(LOG, "updateScanoutTranche: sending a scanout tranche"); + LOGM(Log::DEBUG, "updateScanoutTranche: sending a scanout tranche"); struct wl_array deviceArr = { .size = sizeof(m_mainDevice), @@ -607,3 +644,7 @@ void CLinuxDMABufV1Protocol::updateScanoutTranche(SP surface feedbackResource->m_lastFeedbackWasScanout = true; } + +dev_t CLinuxDMABufV1Protocol::getMainDevice() { + return m_mainDevice; +} diff --git a/src/protocols/LinuxDMABUF.hpp b/src/protocols/LinuxDMABUF.hpp index 296ef04db..b1d591553 100644 --- a/src/protocols/LinuxDMABUF.hpp +++ b/src/protocols/LinuxDMABUF.hpp @@ -113,6 +113,7 @@ class CLinuxDMABufV1Protocol : public IWaylandProtocol { virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); void updateScanoutTranche(SP surface, PHLMONITOR pMonitor); + dev_t getMainDevice(); private: void destroyResource(CLinuxDMABUFResource* resource); diff --git a/src/protocols/LockNotify.cpp b/src/protocols/LockNotify.cpp index 1855f8917..46736ead7 100644 --- a/src/protocols/LockNotify.cpp +++ b/src/protocols/LockNotify.cpp @@ -63,7 +63,7 @@ void CLockNotifyProtocol::onGetNotification(CHyprlandLockNotifierV1* pMgr, uint3 void CLockNotifyProtocol::onLocked() { if UNLIKELY (m_isLocked) { - LOGM(ERR, "Not sending lock notification. Already locked!"); + LOGM(Log::ERR, "Not sending lock notification. Already locked!"); return; } @@ -76,7 +76,7 @@ void CLockNotifyProtocol::onLocked() { void CLockNotifyProtocol::onUnlocked() { if UNLIKELY (!m_isLocked) { - LOGM(ERR, "Not sending unlock notification. Not locked!"); + LOGM(Log::ERR, "Not sending unlock notification. Not locked!"); return; } diff --git a/src/protocols/MesaDRM.cpp b/src/protocols/MesaDRM.cpp index 789f90b6c..c73d98cc2 100644 --- a/src/protocols/MesaDRM.cpp +++ b/src/protocols/MesaDRM.cpp @@ -2,13 +2,14 @@ #include #include #include "../Compositor.hpp" +#include "render/Renderer.hpp" #include "types/WLBuffer.hpp" #include "../render/OpenGL.hpp" CMesaDRMBufferResource::CMesaDRMBufferResource(uint32_t id, wl_client* client, Aquamarine::SDMABUFAttrs attrs_) { - LOGM(LOG, "Creating a Mesa dmabuf, with id {}: size {}, fmt {}, planes {}", id, attrs_.size, attrs_.format, attrs_.planes); + LOGM(Log::DEBUG, "Creating a Mesa dmabuf, with id {}: size {}, fmt {}, planes {}", id, attrs_.size, attrs_.format, attrs_.planes); for (int i = 0; i < attrs_.planes; ++i) { - LOGM(LOG, " | plane {}: mod {} fd {} stride {} offset {}", i, attrs_.modifier, attrs_.fds[i], attrs_.strides[i], attrs_.offsets[i]); + LOGM(Log::DEBUG, " | plane {}: mod {} fd {} stride {} offset {}", i, attrs_.modifier, attrs_.fds[i], attrs_.strides[i], attrs_.offsets[i]); } m_buffer = makeShared(id, client, attrs_); @@ -20,7 +21,7 @@ CMesaDRMBufferResource::CMesaDRMBufferResource(uint32_t id, wl_client* client, A }); if (!m_buffer->m_success) - LOGM(ERR, "Possibly compositor bug: buffer failed to create"); + LOGM(Log::ERR, "Possibly compositor bug: buffer failed to create"); } CMesaDRMBufferResource::~CMesaDRMBufferResource() { @@ -61,7 +62,7 @@ CMesaDRMResource::CMesaDRMResource(SP resource_) : m_resource(resource_) uint64_t mod = DRM_FORMAT_MOD_INVALID; - auto fmts = g_pHyprOpenGL->getDRMFormats(); + auto fmts = g_pHyprRenderer->getDRMFormats(); for (auto const& f : fmts) { if (f.drmFormat != fmt) continue; @@ -101,7 +102,7 @@ CMesaDRMResource::CMesaDRMResource(SP resource_) : m_resource(resource_) m_resource->sendDevice(PROTO::mesaDRM->m_nodeName.c_str()); m_resource->sendCapabilities(WL_DRM_CAPABILITY_PRIME); - auto fmts = g_pHyprOpenGL->getDRMFormats(); + auto fmts = g_pHyprRenderer->getDRMFormats(); for (auto const& fmt : fmts) { m_resource->sendFormat(fmt.drmFormat); } @@ -116,7 +117,7 @@ CMesaDRMProtocol::CMesaDRMProtocol(const wl_interface* iface, const int& ver, co int drmFD = g_pCompositor->m_drmRenderNode.fd >= 0 ? g_pCompositor->m_drmRenderNode.fd : g_pCompositor->m_drm.fd; if (drmGetDevice2(drmFD, 0, &dev) != 0) { - LOGM(ERR, "Failed to get device from fd {}, disabling MesaDRM", drmFD); + LOGM(Log::ERR, "Failed to get device from fd {}, disabling MesaDRM", drmFD); removeGlobal(); return; } @@ -124,10 +125,10 @@ CMesaDRMProtocol::CMesaDRMProtocol(const wl_interface* iface, const int& ver, co if (dev->available_nodes & (1 << DRM_NODE_RENDER) && dev->nodes[DRM_NODE_RENDER]) { m_nodeName = dev->nodes[DRM_NODE_RENDER]; } else if (dev->available_nodes & (1 << DRM_NODE_PRIMARY) && dev->nodes[DRM_NODE_PRIMARY]) { - LOGM(WARN, "No DRM render node, falling back to primary {}", dev->nodes[DRM_NODE_PRIMARY]); + LOGM(Log::WARN, "No DRM render node, falling back to primary {}", dev->nodes[DRM_NODE_PRIMARY]); m_nodeName = dev->nodes[DRM_NODE_PRIMARY]; } else { - LOGM(ERR, "No usable DRM node (render or primary) found, disabling MesaDRM"); + LOGM(Log::ERR, "No usable DRM node (render or primary) found, disabling MesaDRM"); drmFreeDevice(&dev); removeGlobal(); return; diff --git a/src/protocols/OutputManagement.cpp b/src/protocols/OutputManagement.cpp index 6ae7c8206..45bd480ff 100644 --- a/src/protocols/OutputManagement.cpp +++ b/src/protocols/OutputManagement.cpp @@ -2,8 +2,9 @@ #include #include "../Compositor.hpp" #include "../managers/input/InputManager.hpp" -#include "../managers/HookSystemManager.hpp" -#include "../config/ConfigManager.hpp" +#include "../event/EventBus.hpp" +#include "../helpers/Monitor.hpp" +#include "../config/shared/monitor/MonitorRuleManager.hpp" using namespace Aquamarine; @@ -11,14 +12,14 @@ COutputManager::COutputManager(SP resource_) : m_resource( if UNLIKELY (!good()) return; - LOGM(LOG, "New OutputManager registered"); + LOGM(Log::DEBUG, "New OutputManager registered"); m_resource->setOnDestroy([this](CZwlrOutputManagerV1* r) { PROTO::outputManagement->destroyResource(this); }); m_resource->setStop([this](CZwlrOutputManagerV1* r) { m_stopped = true; }); m_resource->setCreateConfiguration([this](CZwlrOutputManagerV1* r, uint32_t id, uint32_t serial) { - LOGM(LOG, "Creating new configuration"); + LOGM(Log::DEBUG, "Creating new configuration"); const auto RESOURCE = PROTO::outputManagement->m_configurations.emplace_back( makeShared(makeShared(m_resource->client(), m_resource->version(), id), m_self.lock())); @@ -37,7 +38,7 @@ COutputManager::COutputManager(SP resource_) : m_resource( if (m == g_pCompositor->m_unsafeOutput) continue; - LOGM(LOG, " | sending output head for {}", m->m_name); + LOGM(Log::DEBUG, " | sending output head for {}", m->m_name); makeAndSendNewHead(m); } @@ -171,9 +172,9 @@ void COutputHead::sendAllData() { if (m->m_mode == m_monitor->m_output->state->state().mode) { if (m->m_mode) - LOGM(LOG, " | sending current mode for {}: {}x{}@{}", m_monitor->m_name, m->m_mode->pixelSize.x, m->m_mode->pixelSize.y, m->m_mode->refreshRate); + LOGM(Log::DEBUG, " | sending current mode for {}: {}x{}@{}", m_monitor->m_name, m->m_mode->pixelSize.x, m->m_mode->pixelSize.y, m->m_mode->refreshRate); else - LOGM(LOG, " | sending current mode for {}: null (fake)", m_monitor->m_name); + LOGM(Log::DEBUG, " | sending current mode for {}: null (fake)", m_monitor->m_name); m_resource->sendCurrentMode(m->m_resource.get()); break; } @@ -202,9 +203,9 @@ void COutputHead::updateMode() { if (m->m_mode == m_monitor->m_currentMode) { if (m->m_mode) - LOGM(LOG, " | sending current mode for {}: {}x{}@{}", m_monitor->m_name, m->m_mode->pixelSize.x, m->m_mode->pixelSize.y, m->m_mode->refreshRate); + LOGM(Log::DEBUG, " | sending current mode for {}: {}x{}@{}", m_monitor->m_name, m->m_mode->pixelSize.x, m->m_mode->pixelSize.y, m->m_mode->refreshRate); else - LOGM(LOG, " | sending current mode for {}: null (fake)", m_monitor->m_name); + LOGM(Log::DEBUG, " | sending current mode for {}: null (fake)", m_monitor->m_name); m_resource->sendCurrentMode(m->m_resource.get()); break; } @@ -243,7 +244,7 @@ void COutputMode::sendAllData() { if (!m_mode) return; - LOGM(LOG, " | sending mode {}x{}@{}mHz, pref: {}", m_mode->pixelSize.x, m_mode->pixelSize.y, m_mode->refreshRate, m_mode->preferred); + LOGM(Log::DEBUG, " | sending mode {}x{}@{}mHz, pref: {}", m_mode->pixelSize.x, m_mode->pixelSize.y, m_mode->refreshRate, m_mode->preferred); m_resource->sendSize(m_mode->pixelSize.x, m_mode->pixelSize.y); if (m_mode->refreshRate > 0) @@ -271,14 +272,14 @@ COutputConfiguration::COutputConfiguration(SP resour const auto HEAD = PROTO::outputManagement->headFromResource(outputHead); if (!HEAD) { - LOGM(ERR, "No head in setEnableHead??"); + LOGM(Log::ERR, "No head in setEnableHead??"); return; } const auto PMONITOR = HEAD->monitor(); if (!PMONITOR) { - LOGM(ERR, "No monitor in setEnableHead??"); + LOGM(Log::ERR, "No monitor in setEnableHead??"); return; } @@ -293,25 +294,25 @@ COutputConfiguration::COutputConfiguration(SP resour m_heads.emplace_back(RESOURCE); - LOGM(LOG, "enableHead on {}. For now, doing nothing. Waiting for apply().", PMONITOR->m_name); + LOGM(Log::DEBUG, "enableHead on {}. For now, doing nothing. Waiting for apply().", PMONITOR->m_name); }); m_resource->setDisableHead([this](CZwlrOutputConfigurationV1* r, wl_resource* outputHead) { const auto HEAD = PROTO::outputManagement->headFromResource(outputHead); if (!HEAD) { - LOGM(ERR, "No head in setDisableHead??"); + LOGM(Log::ERR, "No head in setDisableHead??"); return; } const auto PMONITOR = HEAD->monitor(); if (!PMONITOR) { - LOGM(ERR, "No monitor in setDisableHead??"); + LOGM(Log::ERR, "No monitor in setDisableHead??"); return; } - LOGM(LOG, "disableHead on {}", PMONITOR->m_name); + LOGM(Log::DEBUG, "disableHead on {}", PMONITOR->m_name); SWlrManagerSavedOutputState newState; if (m_owner->m_monitorStates.contains(PMONITOR->m_name)) @@ -319,7 +320,7 @@ COutputConfiguration::COutputConfiguration(SP resour newState.enabled = false; - g_pConfigManager->m_wantsMonitorReload = true; + Config::monitorRuleMgr()->scheduleReload(); m_owner->m_monitorStates[PMONITOR->m_name] = newState; }); @@ -351,14 +352,14 @@ bool COutputConfiguration::good() { bool COutputConfiguration::applyTestConfiguration(bool test) { if (test) { - LOGM(WARN, "TODO: STUB: applyTestConfiguration for test not implemented, returning true."); + LOGM(Log::WARN, "TODO: STUB: applyTestConfiguration for test not implemented, returning true."); return true; } - LOGM(LOG, "Applying configuration"); + LOGM(Log::DEBUG, "Applying configuration"); if (!m_owner) { - LOGM(ERR, "applyTestConfiguration: no owner?!"); + LOGM(Log::ERR, "applyTestConfiguration: no owner?!"); return false; } @@ -373,7 +374,7 @@ bool COutputConfiguration::applyTestConfiguration(bool test) { if (!PMONITOR) continue; - LOGM(LOG, "Saving config for monitor {}", PMONITOR->m_name); + LOGM(Log::DEBUG, "Saving config for monitor {}", PMONITOR->m_name); SWlrManagerSavedOutputState newState; if (m_owner->m_monitorStates.contains(PMONITOR->m_name)) @@ -385,47 +386,47 @@ bool COutputConfiguration::applyTestConfiguration(bool test) { newState.resolution = head->m_state.mode->getMode()->pixelSize; newState.refresh = head->m_state.mode->getMode()->refreshRate; newState.committedProperties |= eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_MODE; - LOGM(LOG, " > Mode: {:.0f}x{:.0f}@{}mHz", newState.resolution.x, newState.resolution.y, newState.refresh); + LOGM(Log::DEBUG, " > Mode: {:.0f}x{:.0f}@{}mHz", newState.resolution.x, newState.resolution.y, newState.refresh); } else if (head->m_state.committedProperties & eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_CUSTOM_MODE) { newState.resolution = head->m_state.customMode.size; newState.refresh = head->m_state.customMode.refresh; newState.committedProperties |= eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_CUSTOM_MODE; - LOGM(LOG, " > Custom mode: {:.0f}x{:.0f}@{}mHz", newState.resolution.x, newState.resolution.y, newState.refresh); + LOGM(Log::DEBUG, " > Custom mode: {:.0f}x{:.0f}@{}mHz", newState.resolution.x, newState.resolution.y, newState.refresh); } if (head->m_state.committedProperties & eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_POSITION) { newState.position = head->m_state.position; newState.committedProperties |= eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_POSITION; - LOGM(LOG, " > Position: {:.0f}, {:.0f}", head->m_state.position.x, head->m_state.position.y); + LOGM(Log::DEBUG, " > Position: {:.0f}, {:.0f}", head->m_state.position.x, head->m_state.position.y); } if (head->m_state.committedProperties & eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC) { newState.adaptiveSync = head->m_state.adaptiveSync; newState.committedProperties |= eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC; - LOGM(LOG, " > vrr: {}", newState.adaptiveSync); + LOGM(Log::DEBUG, " > vrr: {}", newState.adaptiveSync); } if (head->m_state.committedProperties & eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_SCALE) { newState.scale = head->m_state.scale; newState.committedProperties |= eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_SCALE; - LOGM(LOG, " > scale: {:.2f}", newState.scale); + LOGM(Log::DEBUG, " > scale: {:.2f}", newState.scale); } if (head->m_state.committedProperties & eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_TRANSFORM) { newState.transform = head->m_state.transform; newState.committedProperties |= eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_TRANSFORM; - LOGM(LOG, " > transform: {}", (uint8_t)newState.transform); + LOGM(Log::DEBUG, " > transform: {}", (uint8_t)newState.transform); } // reset properties for next set. head->m_state.committedProperties = 0; - g_pConfigManager->m_wantsMonitorReload = true; + Config::monitorRuleMgr()->scheduleReload(); m_owner->m_monitorStates[PMONITOR->m_name] = newState; } - LOGM(LOG, "Saved configuration"); + LOGM(Log::DEBUG, "Saved configuration"); return true; } @@ -440,12 +441,12 @@ COutputConfigurationHead::COutputConfigurationHead(SPmodeFromResource(outputMode); if (!MODE || !MODE->getMode()) { - LOGM(ERR, "No mode in setMode??"); + LOGM(Log::ERR, "No mode in setMode??"); return; } if (!m_monitor) { - LOGM(ERR, "setMode on inert resource"); + LOGM(Log::ERR, "setMode on inert resource"); return; } @@ -457,12 +458,12 @@ COutputConfigurationHead::COutputConfigurationHead(SPm_name, MODE->getMode()->pixelSize.x, MODE->getMode()->pixelSize.y, MODE->getMode()->refreshRate); + LOGM(Log::DEBUG, " | configHead for {}: set mode to {}x{}@{}", m_monitor->m_name, MODE->getMode()->pixelSize.x, MODE->getMode()->pixelSize.y, MODE->getMode()->refreshRate); }); m_resource->setSetCustomMode([this](CZwlrOutputConfigurationHeadV1* r, int32_t w, int32_t h, int32_t refresh) { if (!m_monitor) { - LOGM(ERR, "setCustomMode on inert resource"); + LOGM(Log::ERR, "setCustomMode on inert resource"); return; } @@ -477,19 +478,19 @@ COutputConfigurationHead::COutputConfigurationHead(SPm_name, m_monitor->m_refreshRate); + LOGM(Log::DEBUG, " | configHead for {}: refreshRate 0, using old refresh rate of {:.2f}Hz", m_monitor->m_name, m_monitor->m_refreshRate); refresh = std::round(m_monitor->m_refreshRate * 1000.F); } m_state.committedProperties |= OUTPUT_HEAD_COMMITTED_CUSTOM_MODE; m_state.customMode = {{w, h}, sc(refresh)}; - LOGM(LOG, " | configHead for {}: set custom mode to {}x{}@{}", m_monitor->m_name, w, h, refresh); + LOGM(Log::DEBUG, " | configHead for {}: set custom mode to {}x{}@{}", m_monitor->m_name, w, h, refresh); }); m_resource->setSetPosition([this](CZwlrOutputConfigurationHeadV1* r, int32_t x, int32_t y) { if (!m_monitor) { - LOGM(ERR, "setMode on inert resource"); + LOGM(Log::ERR, "setMode on inert resource"); return; } @@ -501,12 +502,12 @@ COutputConfigurationHead::COutputConfigurationHead(SPm_name, x, y); + LOGM(Log::DEBUG, " | configHead for {}: set pos to {}, {}", m_monitor->m_name, x, y); }); m_resource->setSetTransform([this](CZwlrOutputConfigurationHeadV1* r, int32_t transform) { if (!m_monitor) { - LOGM(ERR, "setMode on inert resource"); + LOGM(Log::ERR, "setMode on inert resource"); return; } @@ -523,12 +524,12 @@ COutputConfigurationHead::COutputConfigurationHead(SP(transform); - LOGM(LOG, " | configHead for {}: set transform to {}", m_monitor->m_name, transform); + LOGM(Log::DEBUG, " | configHead for {}: set transform to {}", m_monitor->m_name, transform); }); m_resource->setSetScale([this](CZwlrOutputConfigurationHeadV1* r, wl_fixed_t scale_) { if (!m_monitor) { - LOGM(ERR, "setMode on inert resource"); + LOGM(Log::ERR, "setMode on inert resource"); return; } @@ -547,12 +548,12 @@ COutputConfigurationHead::COutputConfigurationHead(SPm_name, scale); + LOGM(Log::DEBUG, " | configHead for {}: set scale to {:.2f}", m_monitor->m_name, scale); }); m_resource->setSetAdaptiveSync([this](CZwlrOutputConfigurationHeadV1* r, uint32_t as) { if (!m_monitor) { - LOGM(ERR, "setMode on inert resource"); + LOGM(Log::ERR, "setMode on inert resource"); return; } @@ -569,7 +570,7 @@ COutputConfigurationHead::COutputConfigurationHead(SPm_name, as); + LOGM(Log::DEBUG, " | configHead for {}: set adaptiveSync to {}", m_monitor->m_name, as); }); } @@ -578,7 +579,7 @@ bool COutputConfigurationHead::good() { } COutputManagementProtocol::COutputManagementProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - static auto P = g_pHookSystem->hookDynamic("monitorLayoutChanged", [this](void* self, SCallbackInfo& info, std::any param) { + static auto P = Event::bus()->m_events.monitor.layoutChanged.listen([this] { updateAllOutputs(); sendPendingSuccessEvents(); }); @@ -657,7 +658,7 @@ void COutputManagementProtocol::sendPendingSuccessEvents() { if (m_pendingConfigurationSuccessEvents.empty()) return; - LOGM(LOG, "Sending {} pending configuration success events", m_pendingConfigurationSuccessEvents.size()); + LOGM(Log::DEBUG, "Sending {} pending configuration success events", m_pendingConfigurationSuccessEvents.size()); for (auto const& config : m_pendingConfigurationSuccessEvents) { if (!config) diff --git a/src/protocols/PointerConstraints.cpp b/src/protocols/PointerConstraints.cpp index 5ecaa43bc..a78f3548b 100644 --- a/src/protocols/PointerConstraints.cpp +++ b/src/protocols/PointerConstraints.cpp @@ -1,7 +1,7 @@ #include "PointerConstraints.hpp" -#include "../desktop/WLSurface.hpp" +#include "../desktop/view/WLSurface.hpp" #include "../desktop/state/FocusState.hpp" -#include "../desktop/Window.hpp" +#include "../desktop/view/Window.hpp" #include "../config/ConfigValue.hpp" #include "../managers/SeatManager.hpp" #include "core/Compositor.hpp" @@ -17,7 +17,7 @@ CPointerConstraint::CPointerConstraint(SP resource_, SPsetOnDestroy([this](CZwpLockedPointerV1* p) { PROTO::constraints->destroyPointerConstraint(this); }); resource_->setDestroy([this](CZwpLockedPointerV1* p) { PROTO::constraints->destroyPointerConstraint(this); }); - m_hlSurface = CWLSurface::fromResource(surf); + m_hlSurface = Desktop::View::CWLSurface::fromResource(surf); if (!m_hlSurface) return; @@ -35,7 +35,7 @@ CPointerConstraint::CPointerConstraint(SP resource_, SPgetWindow(); + const auto PWINDOW = Desktop::View::CWindow::fromView(m_hlSurface->view()); if (PWINDOW) { const auto ISXWL = PWINDOW->m_isX11; scale = ISXWL && *PXWLFORCESCALEZERO ? PWINDOW->m_X11SurfaceScaledBy : 1.f; @@ -56,7 +56,7 @@ CPointerConstraint::CPointerConstraint(SP resource_, SPsetOnDestroy([this](CZwpConfinedPointerV1* p) { PROTO::constraints->destroyPointerConstraint(this); }); resource_->setDestroy([this](CZwpConfinedPointerV1* p) { PROTO::constraints->destroyPointerConstraint(this); }); - m_hlSurface = CWLSurface::fromResource(surf); + m_hlSurface = Desktop::View::CWLSurface::fromResource(surf); if (!m_hlSurface) return; @@ -159,7 +159,7 @@ void CPointerConstraint::onSetRegion(wl_resource* wlRegion) { g_pInputManager->simulateMouseMovement(); // to warp the cursor if anything's amiss } -SP CPointerConstraint::owner() { +SP CPointerConstraint::owner() { return m_hlSurface.lock(); } @@ -217,14 +217,14 @@ void CPointerConstraintsProtocol::destroyPointerConstraint(CPointerConstraint* h void CPointerConstraintsProtocol::onNewConstraint(SP constraint, CZwpPointerConstraintsV1* pMgr) { if UNLIKELY (!constraint->good()) { - LOGM(ERR, "Couldn't create constraint??"); + LOGM(Log::ERR, "Couldn't create constraint??"); pMgr->noMemory(); m_constraints.pop_back(); return; } if UNLIKELY (!constraint->owner()) { - LOGM(ERR, "New constraint has no CWLSurface owner??"); + LOGM(Log::ERR, "New constraint has no CWLSurface owner??"); return; } @@ -233,7 +233,7 @@ void CPointerConstraintsProtocol::onNewConstraint(SP constra const auto DUPES = std::ranges::count_if(m_constraints, [OWNER](const auto& c) { return c->owner() == OWNER; }); if UNLIKELY (DUPES > 1) { - LOGM(ERR, "Constraint for surface duped"); + LOGM(Log::ERR, "Constraint for surface duped"); pMgr->error(ZWP_POINTER_CONSTRAINTS_V1_ERROR_ALREADY_CONSTRAINED, "Surface already confined"); m_constraints.pop_back(); return; diff --git a/src/protocols/PointerConstraints.hpp b/src/protocols/PointerConstraints.hpp index 1691b7c02..b190c0419 100644 --- a/src/protocols/PointerConstraints.hpp +++ b/src/protocols/PointerConstraints.hpp @@ -6,10 +6,10 @@ #include #include "WaylandProtocol.hpp" #include "pointer-constraints-unstable-v1.hpp" +#include "../desktop/view/WLSurface.hpp" #include "../helpers/math/Math.hpp" #include "../helpers/signal/Signal.hpp" -class CWLSurface; class CWLSurfaceResource; class CPointerConstraint { @@ -18,23 +18,23 @@ class CPointerConstraint { CPointerConstraint(SP resource_, SP surf, wl_resource* region, zwpPointerConstraintsV1Lifetime lifetime_); ~CPointerConstraint(); - bool good(); + bool good(); - void deactivate(); - void activate(); - bool isActive(); + void deactivate(); + void activate(); + bool isActive(); - SP owner(); + SP owner(); - CRegion logicConstraintRegion(); - bool isLocked(); - Vector2D logicPositionHint(); + CRegion logicConstraintRegion(); + bool isLocked(); + Vector2D logicPositionHint(); private: SP m_resourceLocked; SP m_resourceConfined; - WP m_hlSurface; + WP m_hlSurface; CRegion m_region; bool m_hintSet = false; diff --git a/src/protocols/PointerGestures.cpp b/src/protocols/PointerGestures.cpp index 005767789..eb14bbf87 100644 --- a/src/protocols/PointerGestures.cpp +++ b/src/protocols/PointerGestures.cpp @@ -75,7 +75,7 @@ void CPointerGesturesProtocol::onGetPinchGesture(CZwpPointerGesturesV1* pMgr, ui if UNLIKELY (!RESOURCE->good()) { pMgr->noMemory(); - LOGM(ERR, "Couldn't create gesture"); + LOGM(Log::ERR, "Couldn't create gesture"); return; } } @@ -86,7 +86,7 @@ void CPointerGesturesProtocol::onGetSwipeGesture(CZwpPointerGesturesV1* pMgr, ui if UNLIKELY (!RESOURCE->good()) { pMgr->noMemory(); - LOGM(ERR, "Couldn't create gesture"); + LOGM(Log::ERR, "Couldn't create gesture"); return; } } @@ -97,7 +97,7 @@ void CPointerGesturesProtocol::onGetHoldGesture(CZwpPointerGesturesV1* pMgr, uin if UNLIKELY (!RESOURCE->good()) { pMgr->noMemory(); - LOGM(ERR, "Couldn't create gesture"); + LOGM(Log::ERR, "Couldn't create gesture"); return; } } diff --git a/src/protocols/PointerWarp.cpp b/src/protocols/PointerWarp.cpp index bde0b9132..a297a04dd 100644 --- a/src/protocols/PointerWarp.cpp +++ b/src/protocols/PointerWarp.cpp @@ -1,10 +1,10 @@ #include "PointerWarp.hpp" #include "core/Compositor.hpp" #include "core/Seat.hpp" -#include "../desktop/WLSurface.hpp" +#include "../desktop/view/WLSurface.hpp" #include "../managers/SeatManager.hpp" #include "../managers/PointerManager.hpp" -#include "../desktop/Window.hpp" +#include "../desktop/view/Window.hpp" CPointerWarpProtocol::CPointerWarpProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { ; @@ -27,11 +27,11 @@ void CPointerWarpProtocol::bindManager(wl_client* client, void* data, uint32_t v if (g_pSeatManager->m_state.pointerFocus != PSURFACE) return; - auto SURFBOXV = CWLSurface::fromResource(PSURFACE)->getSurfaceBoxGlobal(); - if (!SURFBOXV.has_value()) + auto WINDOW = Desktop::View::CWindow::fromView(Desktop::View::CWLSurface::fromResource(PSURFACE)->view()); + if (!WINDOW) return; - const auto SURFBOX = SURFBOXV->expand(1); + const auto SURFBOX = WINDOW->getWindowMainSurfaceBox().expand(1); const auto LOCALPOS = Vector2D{wl_fixed_to_double(x), wl_fixed_to_double(y)}; const auto GLOBALPOS = LOCALPOS + SURFBOX.pos(); if (!SURFBOX.containsPoint(GLOBALPOS)) @@ -41,7 +41,7 @@ void CPointerWarpProtocol::bindManager(wl_client* client, void* data, uint32_t v if (!g_pSeatManager->serialValid(PSEAT, serial, false)) return; - LOGM(LOG, "warped pointer to {}", GLOBALPOS); + LOGM(Log::DEBUG, "warped pointer to {}", GLOBALPOS); g_pPointerManager->warpTo(GLOBALPOS); g_pSeatManager->sendPointerMotion(Time::millis(Time::steadyNow()), LOCALPOS); diff --git a/src/protocols/PresentationTime.cpp b/src/protocols/PresentationTime.cpp index 9849eb351..ab15d7010 100644 --- a/src/protocols/PresentationTime.cpp +++ b/src/protocols/PresentationTime.cpp @@ -1,7 +1,7 @@ #include "PresentationTime.hpp" #include #include "../helpers/Monitor.hpp" -#include "../managers/HookSystemManager.hpp" +#include "../event/EventBus.hpp" #include "core/Compositor.hpp" #include "core/Output.hpp" #include @@ -40,46 +40,45 @@ bool CPresentationFeedback::good() { return m_resource->resource(); } -void CPresentationFeedback::sendQueued(WP data, const Time::steady_tp& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags) { +void CPresentationFeedback::sendQueued(WP data, const timespec& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags) { auto client = m_resource->client(); if LIKELY (PROTO::outputs.contains(data->m_monitor->m_name) && data->m_wasPresented) { - if LIKELY (auto outputResource = PROTO::outputs.at(data->m_monitor->m_name)->outputResourceFrom(client); outputResource) - m_resource->sendSyncOutput(outputResource->getResource()->resource()); + if LIKELY (auto outputResources = PROTO::outputs.at(data->m_monitor->m_name)->outputResourcesFrom(client); !outputResources.empty()) { + for (const auto& r : outputResources) { + m_resource->sendSyncOutput(r->getResource()->resource()); + } + } } - uint32_t flags = 0; - if (!data->m_monitor->m_tearingState.activelyTearing) - flags |= WP_PRESENTATION_FEEDBACK_KIND_VSYNC; - if (data->m_zeroCopy) - flags |= WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY; - if (reportedFlags & Aquamarine::IOutput::AQ_OUTPUT_PRESENT_HW_CLOCK) - flags |= WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; - if (reportedFlags & Aquamarine::IOutput::AQ_OUTPUT_PRESENT_HW_COMPLETION) - flags |= WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION; + if (data->m_wasPresented) { + uint32_t flags = 0; + if (!data->m_monitor->m_tearingState.activelyTearing) + flags |= WP_PRESENTATION_FEEDBACK_KIND_VSYNC; + if (data->m_zeroCopy) + flags |= WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY; + if (reportedFlags & Aquamarine::IOutput::AQ_OUTPUT_PRESENT_HW_CLOCK) + flags |= WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; + if (reportedFlags & Aquamarine::IOutput::AQ_OUTPUT_PRESENT_HW_COMPLETION) + flags |= WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION; - const auto TIMESPEC = Time::toTimespec(when); + time_t tv_sec = 0; + if (sizeof(time_t) > 4) + tv_sec = when.tv_sec >> 32; - time_t tv_sec = 0; - if (sizeof(time_t) > 4) - tv_sec = TIMESPEC.tv_sec >> 32; + uint32_t refreshNs = m_resource->version() == 1 && data->m_monitor->m_vrrActive && data->m_monitor->m_output->vrrCapable ? 0 : untilRefreshNs; - uint32_t refreshNs = m_resource->version() == 1 && data->m_monitor->m_vrrActive && data->m_monitor->m_output->vrrCapable ? 0 : untilRefreshNs; - - if (data->m_wasPresented) - m_resource->sendPresented(sc(tv_sec), sc(TIMESPEC.tv_sec & 0xFFFFFFFF), sc(TIMESPEC.tv_nsec), refreshNs, sc(seq >> 32), + m_resource->sendPresented(sc(tv_sec), sc(when.tv_sec & 0xFFFFFFFF), sc(when.tv_nsec), refreshNs, sc(seq >> 32), sc(seq & 0xFFFFFFFF), sc(flags)); - else + } else m_resource->sendDiscarded(); m_done = true; } CPresentationProtocol::CPresentationProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - static auto P = g_pHookSystem->hookDynamic("monitorRemoved", [this](void* self, SCallbackInfo& info, std::any param) { - const auto PMONITOR = PHLMONITORREF{std::any_cast(param)}; - std::erase_if(m_queue, [PMONITOR](const auto& other) { return !other->m_surface || other->m_monitor == PMONITOR; }); - }); + static auto P = Event::bus()->m_events.monitor.removed.listen( + [this](PHLMONITOR mon) { std::erase_if(m_queue, [mon](const auto& other) { return !other->m_surface || other->m_monitor == mon; }); }); } void CPresentationProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { @@ -88,6 +87,7 @@ void CPresentationProtocol::bindManager(wl_client* client, void* data, uint32_t RESOURCE->setDestroy([this](CWpPresentation* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); }); RESOURCE->setFeedback([this](CWpPresentation* pMgr, wl_resource* surf, uint32_t id) { this->onGetFeedback(pMgr, surf, id); }); + RESOURCE->sendClockId(CLOCK_MONOTONIC); } void CPresentationProtocol::onManagerResourceDestroy(wl_resource* res) { @@ -110,7 +110,7 @@ void CPresentationProtocol::onGetFeedback(CWpPresentation* pMgr, wl_resource* su } } -void CPresentationProtocol::onPresented(PHLMONITOR pMonitor, const Time::steady_tp& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags) { +void CPresentationProtocol::onPresented(PHLMONITOR pMonitor, const timespec& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags) { for (auto const& feedback : m_feedbacks) { if (!feedback->m_surface) continue; @@ -126,7 +126,7 @@ void CPresentationProtocol::onPresented(PHLMONITOR pMonitor, const Time::steady_ } if (m_feedbacks.size() > 10000) { - LOGM(ERR, "FIXME: presentation has a feedback leak, and has grown to {} pending entries!!! Dropping!!!!!", m_feedbacks.size()); + LOGM(Log::ERR, "FIXME: presentation has a feedback leak, and has grown to {} pending entries!!! Dropping!!!!!", m_feedbacks.size()); // Move the elements from the 9000th position to the end of the vector. std::vector> newFeedbacks; @@ -146,3 +146,7 @@ void CPresentationProtocol::onPresented(PHLMONITOR pMonitor, const Time::steady_ void CPresentationProtocol::queueData(UP&& data) { m_queue.emplace_back(std::move(data)); } + +bool CPresentationProtocol::hasPendingFeedbacks() const { + return !m_feedbacks.empty(); +} diff --git a/src/protocols/PresentationTime.hpp b/src/protocols/PresentationTime.hpp index c348c175d..6bac9919e 100644 --- a/src/protocols/PresentationTime.hpp +++ b/src/protocols/PresentationTime.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include "WaylandProtocol.hpp" @@ -37,7 +38,7 @@ class CPresentationFeedback { bool good(); - void sendQueued(WP data, const Time::steady_tp& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags); + void sendQueued(WP data, const timespec& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags); private: UP m_resource; @@ -53,8 +54,9 @@ class CPresentationProtocol : public IWaylandProtocol { virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); - void onPresented(PHLMONITOR pMonitor, const Time::steady_tp& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags); + void onPresented(PHLMONITOR pMonitor, const timespec& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags); void queueData(UP&& data); + bool hasPendingFeedbacks() const; private: void onManagerResourceDestroy(wl_resource* res); diff --git a/src/protocols/PrimarySelection.cpp b/src/protocols/PrimarySelection.cpp index dd0eefad3..7da1fa0ab 100644 --- a/src/protocols/PrimarySelection.cpp +++ b/src/protocols/PrimarySelection.cpp @@ -15,16 +15,16 @@ CPrimarySelectionOffer::CPrimarySelectionOffer(SP r m_resource->setReceive([this](CZwpPrimarySelectionOfferV1* r, const char* mime, int32_t fd) { CFileDescriptor sendFd{fd}; if (!m_source) { - LOGM(WARN, "Possible bug: Receive on an offer w/o a source"); + LOGM(Log::WARN, "Possible bug: Receive on an offer w/o a source"); return; } if (m_dead) { - LOGM(WARN, "Possible bug: Receive on an offer that's dead"); + LOGM(Log::WARN, "Possible bug: Receive on an offer that's dead"); return; } - LOGM(LOG, "Offer {:x} asks to send data from source {:x}", (uintptr_t)this, (uintptr_t)m_source.get()); + LOGM(Log::DEBUG, "Offer {:x} asks to send data from source {:x}", (uintptr_t)this, (uintptr_t)m_source.get()); m_source->send(mime, std::move(sendFd)); }); @@ -80,7 +80,7 @@ std::vector CPrimarySelectionSource::mimes() { void CPrimarySelectionSource::send(const std::string& mime, CFileDescriptor fd) { if (std::ranges::find(m_mimeTypes, mime) == m_mimeTypes.end()) { - LOGM(ERR, "Compositor/App bug: CPrimarySelectionSource::sendAskSend with non-existent mime"); + LOGM(Log::ERR, "Compositor/App bug: CPrimarySelectionSource::sendAskSend with non-existent mime"); return; } @@ -89,7 +89,7 @@ void CPrimarySelectionSource::send(const std::string& mime, CFileDescriptor fd) void CPrimarySelectionSource::accepted(const std::string& mime) { if (std::ranges::find(m_mimeTypes, mime) == m_mimeTypes.end()) - LOGM(ERR, "Compositor/App bug: CPrimarySelectionSource::sendAccepted with non-existent mime"); + LOGM(Log::ERR, "Compositor/App bug: CPrimarySelectionSource::sendAccepted with non-existent mime"); // primary sel has no accepted } @@ -115,24 +115,24 @@ CPrimarySelectionDevice::CPrimarySelectionDevice(SP("misc:middle_click_paste"); if (!*PPRIMARYSEL) { - LOGM(LOG, "Ignoring primary selection: disabled in config"); + LOGM(Log::DEBUG, "Ignoring primary selection: disabled in config"); g_pSeatManager->setCurrentPrimarySelection(nullptr); return; } auto source = sourceR ? CPrimarySelectionSource::fromResource(sourceR) : CSharedPointer{}; if (!source) { - LOGM(LOG, "wlr reset selection received"); + LOGM(Log::DEBUG, "wlr reset selection received"); g_pSeatManager->setCurrentPrimarySelection(nullptr); return; } if (source && source->used()) - LOGM(WARN, "setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this."); + LOGM(Log::WARN, "setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this."); source->markUsed(); - LOGM(LOG, "wlr manager requests selection to {:x}", (uintptr_t)source.get()); + LOGM(Log::DEBUG, "wlr manager requests selection to {:x}", (uintptr_t)source.get()); g_pSeatManager->setCurrentPrimarySelection(source); }); } @@ -181,7 +181,7 @@ CPrimarySelectionManager::CPrimarySelectionManager(SPm_device = RESOURCE; } - LOGM(LOG, "New primary selection data device bound at {:x}", (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New primary selection data device bound at {:x}", (uintptr_t)RESOURCE.get()); }); m_resource->setCreateSource([this](CZwpPrimarySelectionDeviceManagerV1* r, uint32_t id) { @@ -197,13 +197,13 @@ CPrimarySelectionManager::CPrimarySelectionManager(SPm_self = RESOURCE; m_sources.emplace_back(RESOURCE); - LOGM(LOG, "New primary selection data source bound at {:x}", (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New primary selection data source bound at {:x}", (uintptr_t)RESOURCE.get()); }); } @@ -224,7 +224,7 @@ void CPrimarySelectionProtocol::bindManager(wl_client* client, void* data, uint3 return; } - LOGM(LOG, "New primary_seletion_manager at {:x}", (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New primary_seletion_manager at {:x}", (uintptr_t)RESOURCE.get()); // we need to do it here because protocols come before seatMgr if (!m_listeners.onPointerFocusChange) @@ -262,7 +262,7 @@ void CPrimarySelectionProtocol::sendSelectionToDevice(SPsendDataOffer(OFFER); OFFER->sendData(); @@ -277,7 +277,7 @@ void CPrimarySelectionProtocol::setSelection(SP source) { } if (!source) { - LOGM(LOG, "resetting selection"); + LOGM(Log::DEBUG, "resetting selection"); if (!g_pSeatManager->m_state.pointerFocusResource) return; @@ -289,7 +289,7 @@ void CPrimarySelectionProtocol::setSelection(SP source) { return; } - LOGM(LOG, "New selection for data source {:x}", (uintptr_t)source.get()); + LOGM(Log::DEBUG, "New selection for data source {:x}", (uintptr_t)source.get()); if (!g_pSeatManager->m_state.pointerFocusResource) return; @@ -297,7 +297,7 @@ void CPrimarySelectionProtocol::setSelection(SP source) { auto DESTDEVICE = dataDeviceForClient(g_pSeatManager->m_state.pointerFocusResource->client()); if (!DESTDEVICE) { - LOGM(LOG, "CWLDataDeviceProtocol::setSelection: cannot send selection to a client without a data_device"); + LOGM(Log::DEBUG, "CWLDataDeviceProtocol::setSelection: cannot send selection to a client without a data_device"); g_pSeatManager->m_selection.currentPrimarySelection.reset(); return; } @@ -313,7 +313,7 @@ void CPrimarySelectionProtocol::updateSelection() { auto DESTDEVICE = dataDeviceForClient(g_pSeatManager->m_state.pointerFocusResource->client()); if (!selection || !DESTDEVICE) { - LOGM(LOG, "CPrimarySelectionProtocol::updateSelection: cannot send selection to a client without a data_device"); + LOGM(Log::DEBUG, "CPrimarySelectionProtocol::updateSelection: cannot send selection to a client without a data_device"); return; } diff --git a/src/protocols/RelativePointer.cpp b/src/protocols/RelativePointer.cpp index 67bff46ed..39a963099 100644 --- a/src/protocols/RelativePointer.cpp +++ b/src/protocols/RelativePointer.cpp @@ -22,8 +22,12 @@ wl_client* CRelativePointer::client() { } void CRelativePointer::sendRelativeMotion(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel) { - m_resource->sendRelativeMotion(time >> 32, time & 0xFFFFFFFF, wl_fixed_from_double(delta.x), wl_fixed_from_double(delta.y), wl_fixed_from_double(deltaUnaccel.x), - wl_fixed_from_double(deltaUnaccel.y)); + sendRelativeMotion(time >> 32, time & 0xFFFFFFFF, wl_fixed_from_double(delta.x), wl_fixed_from_double(delta.y), wl_fixed_from_double(deltaUnaccel.x), + wl_fixed_from_double(deltaUnaccel.y)); +} + +void CRelativePointer::sendRelativeMotion(uint32_t timeHi, uint32_t timeLo, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dxUnaccel, wl_fixed_t dyUnaccel) { + m_resource->sendRelativeMotion(timeHi, timeLo, dx, dy, dxUnaccel, dyUnaccel); } CRelativePointerProtocol::CRelativePointerProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { @@ -58,16 +62,21 @@ void CRelativePointerProtocol::onGetRelativePointer(CZwpRelativePointerManagerV1 } void CRelativePointerProtocol::sendRelativeMotion(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel) { - if (!g_pSeatManager->m_state.pointerFocusResource) return; - const auto FOCUSED = g_pSeatManager->m_state.pointerFocusResource->client(); + const auto FOCUSED = g_pSeatManager->m_state.pointerFocusResource->client(); + const auto TIMEHI = sc(time >> 32); + const auto TIMELO = sc(time & 0xFFFFFFFF); + const auto DX = wl_fixed_from_double(delta.x); + const auto DY = wl_fixed_from_double(delta.y); + const auto DXUNACCEL = wl_fixed_from_double(deltaUnaccel.x); + const auto DYUNACCEL = wl_fixed_from_double(deltaUnaccel.y); for (auto const& rp : m_relativePointers) { if (FOCUSED != rp->client()) continue; - rp->sendRelativeMotion(time, delta, deltaUnaccel); + rp->sendRelativeMotion(TIMEHI, TIMELO, DX, DY, DXUNACCEL, DYUNACCEL); } } diff --git a/src/protocols/RelativePointer.hpp b/src/protocols/RelativePointer.hpp index 3d6a3e30f..d422aab57 100644 --- a/src/protocols/RelativePointer.hpp +++ b/src/protocols/RelativePointer.hpp @@ -11,6 +11,7 @@ class CRelativePointer { CRelativePointer(SP resource_); void sendRelativeMotion(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel); + void sendRelativeMotion(uint32_t timeHi, uint32_t timeLo, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dxUnaccel, wl_fixed_t dyUnaccel); bool good(); wl_client* client(); @@ -42,4 +43,4 @@ class CRelativePointerProtocol : public IWaylandProtocol { namespace PROTO { inline UP relativePointer; -}; \ No newline at end of file +}; diff --git a/src/protocols/Screencopy.cpp b/src/protocols/Screencopy.cpp index 84487a187..75c230268 100644 --- a/src/protocols/Screencopy.cpp +++ b/src/protocols/Screencopy.cpp @@ -1,431 +1,14 @@ #include "Screencopy.hpp" -#include "../Compositor.hpp" -#include "../managers/eventLoop/EventLoopManager.hpp" -#include "../managers/PointerManager.hpp" -#include "../managers/input/InputManager.hpp" -#include "../managers/EventManager.hpp" -#include "../managers/permissions/DynamicPermissionManager.hpp" -#include "../render/Renderer.hpp" -#include "../render/OpenGL.hpp" -#include "../helpers/Monitor.hpp" +#include "../managers/screenshare/ScreenshareManager.hpp" #include "core/Output.hpp" -#include "types/WLBuffer.hpp" +#include "../render/Renderer.hpp" #include "types/Buffer.hpp" -#include "ColorManagement.hpp" #include "../helpers/Format.hpp" #include "../helpers/time/Time.hpp" -#include "XDGShell.hpp" +#include -#include -#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(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(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(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(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(ERR, "No frame in copyFrame??"); - return; - } - - if UNLIKELY (!g_pCompositor->monitorExists(m_monitor.lock())) { - LOGM(ERR, "Client requested sharing of a monitor that is gone"); - m_resource->sendFailed(); - return; - } - - const auto PBUFFER = CWLBufferResource::fromResource(buffer_); - if UNLIKELY (!PBUFFER) { - LOGM(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(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(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(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(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(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(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(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(wlTransformToHyprutils(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->m_wlSurface || !popup->m_wlSurface->resource() || !popup->m_mapped) - return; - - const auto popRel = popup->coordsRelativeToParent(); - popup->m_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->m_mapped && !l->m_fadingOut) || l->m_alpha->value() == 0.f) - 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(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(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(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(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(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(TRACE, "Copied frame via shm"); - - return true; -} - -bool CScreencopyFrame::good() { - return m_resource->resource(); -} - -CScreencopyClient::~CScreencopyClient() { - g_pHookSystem->unhook(m_tickCallback); -} +using namespace Hyprgraphics::Egl; +using namespace Screenshare; CScreencopyClient::CScreencopyClient(SP resource_) : m_resource(resource_) { if UNLIKELY (!good()) @@ -434,58 +17,151 @@ CScreencopyClient::CScreencopyClient(SP resource_) : m m_resource->setDestroy([this](CZwlrScreencopyManagerV1* pMgr) { PROTO::screencopy->destroyResource(this); }); m_resource->setOnDestroy([this](CZwlrScreencopyManagerV1* pMgr) { PROTO::screencopy->destroyResource(this); }); m_resource->setCaptureOutput( - [this](CZwlrScreencopyManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* output) { this->captureOutput(frame, overlayCursor, output, {}); }); + [this](CZwlrScreencopyManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* output) { captureOutput(frame, overlayCursor, output, {}); }); m_resource->setCaptureOutputRegion([this](CZwlrScreencopyManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* output, int32_t x, int32_t y, int32_t w, - int32_t h) { this->captureOutput(frame, overlayCursor, output, {x, y, w, h}); }); + int32_t h) { captureOutput(frame, overlayCursor, output, {x, y, w, h}); }); - m_lastMeasure.reset(); - m_lastFrame.reset(); - m_tickCallback = g_pHookSystem->hookDynamic("tick", [&](void* self, SCallbackInfo& info, std::any data) { onTick(); }); + m_savedClient = m_resource->client(); } void CScreencopyClient::captureOutput(uint32_t frame, int32_t overlayCursor_, wl_resource* output, CBox box) { + const auto PMONITORRES = CWLOutputResource::fromResource(output); + if (!PMONITORRES || !PMONITORRES->m_monitor) { + LOGM(Log::ERR, "Tried to capture invalid output/monitor in {:x}", (uintptr_t)this); + m_resource->error(-1, "invalid output"); + return; + } + + const auto PMONITOR = PMONITORRES->m_monitor.lock(); + auto session = box.w == 0 && box.h == 0 ? Screenshare::mgr()->getManagedSession(m_resource->client(), PMONITOR) : + Screenshare::mgr()->getManagedSession(m_resource->client(), PMONITOR, box); + const auto FRAME = PROTO::screencopy->m_frames.emplace_back( - makeShared(makeShared(m_resource->client(), m_resource->version(), frame), overlayCursor_, output, box)); + makeShared(makeShared(m_resource->client(), m_resource->version(), frame), session, !!overlayCursor_)); if (!FRAME->good()) { - LOGM(ERR, "Couldn't alloc frame for sharing! (no memory)"); + LOGM(Log::ERR, "Couldn't alloc frame for sharing! (no memory)"); m_resource->noMemory(); PROTO::screencopy->destroyResource(FRAME.get()); return; } - FRAME->m_self = FRAME; FRAME->m_client = m_self; -} - -void CScreencopyClient::onTick() { - if (m_lastMeasure.getMillis() < 500) - return; - - m_framesInLastHalfSecond = m_frameCounter; - m_frameCounter = 0; - m_lastMeasure.reset(); - - const auto LASTFRAMEDELTA = m_lastFrame.getMillis() / 1000.0; - const bool FRAMEAWAITING = std::ranges::any_of(PROTO::screencopy->m_frames, [&](const auto& frame) { return frame->m_client.get() == this; }); - - if (m_framesInLastHalfSecond > 3 && !m_sentScreencast) { - EMIT_HOOK_EVENT("screencast", (std::vector{1, sc(m_framesInLastHalfSecond), sc(m_clientOwner)})); - g_pEventManager->postEvent(SHyprIPCEvent{"screencast", "1," + std::to_string(m_clientOwner)}); - m_sentScreencast = true; - } else if (m_framesInLastHalfSecond < 4 && m_sentScreencast && LASTFRAMEDELTA > 1.0 && !FRAMEAWAITING) { - EMIT_HOOK_EVENT("screencast", (std::vector{0, sc(m_framesInLastHalfSecond), sc(m_clientOwner)})); - g_pEventManager->postEvent(SHyprIPCEvent{"screencast", "0," + std::to_string(m_clientOwner)}); - m_sentScreencast = false; - } + FRAME->m_self = FRAME; } bool CScreencopyClient::good() { - return m_resource->resource(); + return m_resource && m_resource->resource(); } -wl_client* CScreencopyClient::client() { - return m_resource ? m_resource->client() : nullptr; +CScreencopyFrame::CScreencopyFrame(SP resource_, WP session, bool overlayCursor) : + m_resource(resource_), m_session(session), m_overlayCursor(overlayCursor) { + if UNLIKELY (!good()) + return; + + m_resource->setOnDestroy([this](CZwlrScreencopyFrameV1* pMgr) { PROTO::screencopy->destroyResource(this); }); + m_resource->setDestroy([this](CZwlrScreencopyFrameV1* pFrame) { PROTO::screencopy->destroyResource(this); }); + m_resource->setCopy([this](CZwlrScreencopyFrameV1* pFrame, wl_resource* res) { shareFrame(pFrame, res, false); }); + m_resource->setCopyWithDamage([this](CZwlrScreencopyFrameV1* pFrame, wl_resource* res) { shareFrame(pFrame, res, true); }); + + m_frame = m_session->nextFrame(overlayCursor); + + auto formats = m_session->allowedFormats(); + if (formats.empty()) { + LOGM(Log::ERR, "No format supported by renderer in screencopy protocol"); + m_resource->sendFailed(); + return; + } + + DRMFormat format = formats.at(0); + auto bufSize = m_frame->bufferSize(); + + const auto PSHMINFO = getPixelFormatFromDRM(format); + + if (!PSHMINFO) { + LOGM(Log::ERR, "No pixel format for drm format"); + m_resource->sendFailed(); + return; + } + + const auto stride = minStride(PSHMINFO, bufSize.x); + m_resource->sendBuffer(NFormatUtils::drmToShm(format), bufSize.x, bufSize.y, stride); + + if (m_resource->version() >= 3) { + if LIKELY (format != DRM_FORMAT_INVALID) + m_resource->sendLinuxDmabuf(format, bufSize.x, bufSize.y); + + m_resource->sendBufferDone(); + } +} + +void CScreencopyFrame::shareFrame(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer, bool withDamage) { + if UNLIKELY (!good()) { + LOGM(Log::ERR, "No frame in shareFrame??"); + return; + } + + if UNLIKELY (m_session.expired() || !m_session->monitor()) { + LOGM(Log::ERR, "Session stopped for frame {:x}", (uintptr_t)this); + m_resource->sendFailed(); + return; + } + + if UNLIKELY (m_buffer) { + LOGM(Log::ERR, "Buffer used in {:x}", (uintptr_t)this); + m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_ALREADY_USED, "frame already used"); + m_resource->sendFailed(); + return; + } + + const auto PBUFFERRES = CWLBufferResource::fromResource(buffer); + if UNLIKELY (!PBUFFERRES || !PBUFFERRES->m_buffer) { + LOGM(Log::ERR, "Invalid buffer in {:x}", (uintptr_t)this); + m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); + m_resource->sendFailed(); + return; + } + + const auto& PBUFFER = PBUFFERRES->m_buffer.lock(); + + if (!withDamage) + g_pHyprRenderer->damageMonitor(m_session->monitor()); + + auto error = m_frame->share(PBUFFER, {}, [this, withDamage, self = m_self](eScreenshareResult result) { + if (self.expired() || !good()) + return; + switch (result) { + case RESULT_COPIED: { + m_resource->sendFlags(sc(0)); + if (withDamage) + m_frame->damage().forEachRect([&](const auto& rect) { m_resource->sendDamage(rect.x1, rect.y1, rect.x2 - rect.x1, rect.y2 - rect.y1); }); + + const auto [sec, nsec] = Time::secNsec(m_timestamp); + uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0; + uint32_t tvSecLo = sec & 0xFFFFFFFF; + m_resource->sendReady(tvSecHi, tvSecLo, nsec); + break; + } + case RESULT_NOT_COPIED: + LOGM(Log::ERR, "Frame share failed in {:x}", (uintptr_t)this); + m_resource->sendFailed(); + break; + case RESULT_TIMESTAMP: m_timestamp = Time::steadyNow(); break; + } + }); + + switch (error) { + case ERROR_NONE: m_buffer = CHLBufferReference(PBUFFER); break; + case ERROR_NO_BUFFER: m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); break; + case ERROR_BUFFER_SIZE: + case ERROR_BUFFER_FORMAT: m_resource->sendFailed(); break; + case ERROR_UNKNOWN: + case ERROR_STOPPED: m_resource->sendFailed(); break; + } +} + +bool CScreencopyFrame::good() { + return m_resource && m_resource->resource(); } CScreencopyProtocol::CScreencopyProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { @@ -496,7 +172,7 @@ void CScreencopyProtocol::bindManager(wl_client* client, void* data, uint32_t ve const auto CLIENT = m_clients.emplace_back(makeShared(makeShared(client, ver, id))); if (!CLIENT->good()) { - LOGM(LOG, "Failed to bind client! (out of memory)"); + LOGM(Log::DEBUG, "Failed to bind client! (out of memory)"); CLIENT->m_resource->noMemory(); m_clients.pop_back(); return; @@ -504,68 +180,14 @@ void CScreencopyProtocol::bindManager(wl_client* client, void* data, uint32_t ve CLIENT->m_self = CLIENT; - LOGM(LOG, "Bound client successfully!"); + LOGM(Log::DEBUG, "Bound client successfully!"); } void CScreencopyProtocol::destroyResource(CScreencopyClient* client) { - std::erase_if(m_clients, [&](const auto& other) { return other.get() == client; }); std::erase_if(m_frames, [&](const auto& other) { return other->m_client.get() == client; }); - std::erase_if(m_framesAwaitingWrite, [&](const auto& other) { return !other || other->m_client.get() == client; }); + std::erase_if(m_clients, [&](const auto& other) { return other.get() == client; }); } void CScreencopyProtocol::destroyResource(CScreencopyFrame* frame) { std::erase_if(m_frames, [&](const auto& other) { return other.get() == frame; }); - std::erase_if(m_framesAwaitingWrite, [&](const auto& other) { return !other || other.get() == frame; }); -} - -void CScreencopyProtocol::onOutputCommit(PHLMONITOR pMonitor) { - if (m_framesAwaitingWrite.empty()) { - for (auto client : m_clients) { - if (client->m_framesInLastHalfSecond > 0) - return; - } - g_pHyprRenderer->m_directScanoutBlocked = false; - return; // nothing to share - } - - std::vector> framesToRemove; - // reserve number of elements to avoid reallocations - framesToRemove.reserve(m_framesAwaitingWrite.size()); - - // share frame if correct output - for (auto const& f : m_framesAwaitingWrite) { - if (!f) - continue; - - // check permissions - const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(f->m_resource->client(), PERMISSION_TYPE_SCREENCOPY); - - if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) { - if (!f->m_tempFb.isAllocated()) - f->storeTempFB(); // make a snapshot before the popup - - continue; // pending an answer, don't do anything yet. - } - - // otherwise share. If it's denied, it will be black. - - if (!f->m_monitor || !f->m_buffer) { - framesToRemove.emplace_back(f); - continue; - } - - if (f->m_monitor != pMonitor) - continue; - - f->share(); - - f->m_client->m_lastFrame.reset(); - ++f->m_client->m_frameCounter; - - framesToRemove.emplace_back(f); - } - - for (auto const& f : framesToRemove) { - std::erase(m_framesAwaitingWrite, f); - } } diff --git a/src/protocols/Screencopy.hpp b/src/protocols/Screencopy.hpp index 54b0d28c6..b73c090dd 100644 --- a/src/protocols/Screencopy.hpp +++ b/src/protocols/Screencopy.hpp @@ -1,50 +1,32 @@ #pragma once -#include "../defines.hpp" -#include "./types/Buffer.hpp" -#include "wlr-screencopy-unstable-v1.hpp" #include "WaylandProtocol.hpp" +#include "wlr-screencopy-unstable-v1.hpp" -#include -#include -#include "../managers/HookSystemManager.hpp" -#include "../helpers/time/Timer.hpp" #include "../helpers/time/Time.hpp" -#include "../render/Framebuffer.hpp" -#include "../managers/eventLoop/EventLoopTimer.hpp" +#include "./types/Buffer.hpp" #include +#include + class CMonitor; class IHLBuffer; - -enum eClientOwners { - CLIENT_SCREENCOPY = 0, - CLIENT_TOPLEVEL_EXPORT +namespace Screenshare { + class CScreenshareSession; + class CScreenshareFrame; }; class CScreencopyClient { public: CScreencopyClient(SP resource_); - ~CScreencopyClient(); - bool good(); - wl_client* client(); - - WP m_self; - eClientOwners m_clientOwner = CLIENT_SCREENCOPY; - - CTimer m_lastFrame; - int m_frameCounter = 0; + bool good(); private: SP m_resource; + WP m_self; - int m_framesInLastHalfSecond = 0; - CTimer m_lastMeasure; - bool m_sentScreencast = false; - - SP m_tickCallback; - void onTick(); + wl_client* m_savedClient = nullptr; void captureOutput(uint32_t frame, int32_t overlayCursor, wl_resource* output, CBox box); @@ -53,38 +35,27 @@ class CScreencopyClient { class CScreencopyFrame { public: - CScreencopyFrame(SP resource, int32_t overlay_cursor, wl_resource* output, CBox box); + CScreencopyFrame(SP resource, WP session, bool overlayCursor); - bool good(); - - WP m_self; - WP m_client; + bool good(); private: - SP m_resource; + SP m_resource; + WP m_self; + WP m_client; - PHLMONITORREF m_monitor; - bool m_overlayCursor = false; - bool m_withDamage = false; + WP m_session; + UP m_frame; - CHLBufferReference m_buffer; - bool m_bufferDMA = false; - uint32_t m_shmFormat = 0; - uint32_t m_dmabufFormat = 0; - int m_shmStride = 0; - CBox m_box = {}; + CHLBufferReference m_buffer; + Time::steady_tp m_timestamp; + bool m_overlayCursor = true; - // if we have a pending perm, hold the buffer. - CFramebuffer m_tempFb; - - void copy(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer); - void copyDmabuf(std::function callback); - bool copyShm(); - void renderMon(); - void storeTempFB(); - void share(); + // + void shareFrame(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer, bool withDamage); friend class CScreencopyProtocol; + friend class CScreencopyClient; }; class CScreencopyProtocol : public IWaylandProtocol { @@ -95,21 +66,10 @@ class CScreencopyProtocol : public IWaylandProtocol { void destroyResource(CScreencopyClient* resource); void destroyResource(CScreencopyFrame* resource); - void onOutputCommit(PHLMONITOR pMonitor); - private: std::vector> m_frames; - std::vector> m_framesAwaitingWrite; std::vector> m_clients; - void shareAllFrames(PHLMONITOR pMonitor); - void shareFrame(CScreencopyFrame* frame); - void sendFrameDamage(CScreencopyFrame* frame); - bool copyFrameDmabuf(CScreencopyFrame* frame); - bool copyFrameShm(CScreencopyFrame* frame, const Time::steady_tp& now); - - uint32_t drmFormatForMonitor(PHLMONITOR pMonitor); - friend class CScreencopyFrame; friend class CScreencopyClient; }; diff --git a/src/protocols/SecurityContext.cpp b/src/protocols/SecurityContext.cpp index 00f7b4d86..6c7f82269 100644 --- a/src/protocols/SecurityContext.cpp +++ b/src/protocols/SecurityContext.cpp @@ -53,15 +53,15 @@ CSecurityContext::CSecurityContext(SP resource_, int liste return; m_resource->setDestroy([this](CWpSecurityContextV1* r) { - LOGM(LOG, "security_context at 0x{:x}: resource destroyed, keeping context until fd hangup", (uintptr_t)this); + LOGM(Log::DEBUG, "security_context at 0x{:x}: resource destroyed, keeping context until fd hangup", (uintptr_t)this); m_resource = nullptr; }); m_resource->setOnDestroy([this](CWpSecurityContextV1* r) { - LOGM(LOG, "security_context at 0x{:x}: resource destroyed, keeping context until fd hangup", (uintptr_t)this); + LOGM(Log::DEBUG, "security_context at 0x{:x}: resource destroyed, keeping context until fd hangup", (uintptr_t)this); m_resource = nullptr; }); - LOGM(LOG, "New security_context at 0x{:x}", (uintptr_t)this); + LOGM(Log::DEBUG, "New security_context at 0x{:x}", (uintptr_t)this); m_resource->setSetSandboxEngine([this](CWpSecurityContextV1* r, const char* engine) { if UNLIKELY (!m_sandboxEngine.empty()) { @@ -75,7 +75,7 @@ CSecurityContext::CSecurityContext(SP resource_, int liste } m_sandboxEngine = engine ? engine : "(null)"; - LOGM(LOG, "security_context at 0x{:x} sets engine to {}", (uintptr_t)this, m_sandboxEngine); + LOGM(Log::DEBUG, "security_context at 0x{:x} sets engine to {}", (uintptr_t)this, m_sandboxEngine); }); m_resource->setSetAppId([this](CWpSecurityContextV1* r, const char* appid) { @@ -90,7 +90,7 @@ CSecurityContext::CSecurityContext(SP resource_, int liste } m_appID = appid ? appid : "(null)"; - LOGM(LOG, "security_context at 0x{:x} sets appid to {}", (uintptr_t)this, m_appID); + LOGM(Log::DEBUG, "security_context at 0x{:x} sets appid to {}", (uintptr_t)this, m_appID); }); m_resource->setSetInstanceId([this](CWpSecurityContextV1* r, const char* instance) { @@ -105,13 +105,13 @@ CSecurityContext::CSecurityContext(SP resource_, int liste } m_instanceID = instance ? instance : "(null)"; - LOGM(LOG, "security_context at 0x{:x} sets instance to {}", (uintptr_t)this, m_instanceID); + LOGM(Log::DEBUG, "security_context at 0x{:x} sets instance to {}", (uintptr_t)this, m_instanceID); }); m_resource->setCommit([this](CWpSecurityContextV1* r) { m_committed = true; - LOGM(LOG, "security_context at 0x{:x} commits", (uintptr_t)this); + LOGM(Log::DEBUG, "security_context at 0x{:x} commits", (uintptr_t)this); m_listenSource = wl_event_loop_add_fd(g_pCompositor->m_wlEventLoop, m_listenFD.get(), WL_EVENT_READABLE, ::onListenFdEvent, this); m_closeSource = wl_event_loop_add_fd(g_pCompositor->m_wlEventLoop, m_closeFD.get(), 0, ::onCloseFdEvent, this); @@ -136,7 +136,7 @@ bool CSecurityContext::good() { void CSecurityContext::onListen(uint32_t mask) { if UNLIKELY (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { - LOGM(ERR, "security_context at 0x{:x} got an error in listen", (uintptr_t)this); + LOGM(Log::ERR, "security_context at 0x{:x} got an error in listen", (uintptr_t)this); PROTO::securityContext->destroyContext(this); return; } @@ -146,19 +146,19 @@ void CSecurityContext::onListen(uint32_t mask) { CFileDescriptor clientFD{accept(m_listenFD.get(), nullptr, nullptr)}; if UNLIKELY (!clientFD.isValid()) { - LOGM(ERR, "security_context at 0x{:x} couldn't accept", (uintptr_t)this); + LOGM(Log::ERR, "security_context at 0x{:x} couldn't accept", (uintptr_t)this); return; } auto newClient = CSecurityContextSandboxedClient::create(std::move(clientFD)); if UNLIKELY (!newClient) { - LOGM(ERR, "security_context at 0x{:x} couldn't create a client", (uintptr_t)this); + LOGM(Log::ERR, "security_context at 0x{:x} couldn't create a client", (uintptr_t)this); return; } PROTO::securityContext->m_sandboxedClients.emplace_back(newClient); - LOGM(LOG, "security_context at 0x{:x} got a new wl_client 0x{:x}", (uintptr_t)this, (uintptr_t)newClient->m_client); + LOGM(Log::DEBUG, "security_context at 0x{:x} got a new wl_client 0x{:x}", (uintptr_t)this, (uintptr_t)newClient->m_client); } void CSecurityContext::onClose(uint32_t mask) { diff --git a/src/protocols/SessionLock.cpp b/src/protocols/SessionLock.cpp index 3dab394bb..de2b96f22 100644 --- a/src/protocols/SessionLock.cpp +++ b/src/protocols/SessionLock.cpp @@ -26,13 +26,13 @@ CSessionLockSurface::CSessionLockSurface(SP resource_, m_listeners.surfaceCommit = m_surface->m_events.commit.listen([this] { if (!m_surface->m_current.texture) { - LOGM(ERR, "SessionLock attached a null buffer"); + LOGM(Log::ERR, "SessionLock attached a null buffer"); m_resource->error(EXT_SESSION_LOCK_SURFACE_V1_ERROR_NULL_BUFFER, "Null buffer attached"); return; } if (!m_ackdConfigure) { - LOGM(ERR, "SessionLock committed without an ack"); + LOGM(Log::ERR, "SessionLock committed without an ack"); m_resource->error(EXT_SESSION_LOCK_SURFACE_V1_ERROR_COMMIT_BEFORE_FIRST_ACK, "Committed surface before first ack"); return; } @@ -47,7 +47,7 @@ CSessionLockSurface::CSessionLockSurface(SP resource_, }); m_listeners.surfaceDestroy = m_surface->m_events.destroy.listen([this] { - LOGM(WARN, "SessionLockSurface object remains but surface is being destroyed???"); + LOGM(Log::WARN, "SessionLockSurface object remains but surface is being destroyed???"); m_surface->unmap(); m_listeners.surfaceCommit.reset(); m_listeners.surfaceDestroy.reset(); @@ -62,11 +62,11 @@ CSessionLockSurface::CSessionLockSurface(SP resource_, if (m_surface) m_surface->enter(m_monitor.lock()); + + m_listeners.monitorMode = m_monitor->m_events.modeChanged.listen([this] { sendConfigure(); }); } sendConfigure(); - - m_listeners.monitorMode = m_monitor->m_events.modeChanged.listen([this] { sendConfigure(); }); } CSessionLockSurface::~CSessionLockSurface() { @@ -79,7 +79,7 @@ CSessionLockSurface::~CSessionLockSurface() { void CSessionLockSurface::sendConfigure() { if (!m_monitor) { - LOGM(ERR, "sendConfigure: monitor is gone"); + LOGM(Log::ERR, "sendConfigure: monitor is gone"); return; } @@ -112,7 +112,7 @@ CSessionLock::CSessionLock(SP resource_) : m_resource(resourc m_resource->setGetLockSurface([this](CExtSessionLockV1* r, uint32_t id, wl_resource* surf, wl_resource* output) { if (m_inert) { - LOGM(ERR, "Lock is trying to send getLockSurface after it's inert"); + LOGM(Log::ERR, "Lock is trying to send getLockSurface after it's inert"); return; } @@ -184,7 +184,7 @@ void CSessionLockProtocol::destroyResource(CSessionLockSurface* surf) { void CSessionLockProtocol::onLock(CExtSessionLockManagerV1* pMgr, uint32_t id) { - LOGM(LOG, "New sessionLock with id {}", id); + LOGM(Log::DEBUG, "New sessionLock with id {}", id); const auto CLIENT = pMgr->client(); const auto RESOURCE = m_locks.emplace_back(makeShared(makeShared(CLIENT, pMgr->version(), id))); @@ -201,10 +201,19 @@ void CSessionLockProtocol::onLock(CExtSessionLockManagerV1* pMgr, uint32_t id) { } void CSessionLockProtocol::onGetLockSurface(CExtSessionLockV1* lock, uint32_t id, wl_resource* surface, wl_resource* output) { - LOGM(LOG, "New sessionLockSurface with id {}", id); + LOGM(Log::DEBUG, "New sessionLockSurface with id {}", id); - auto PSURFACE = CWLSurfaceResource::fromResource(surface); - auto PMONITOR = CWLOutputResource::fromResource(output)->m_monitor.lock(); + auto PSURFACE = CWLSurfaceResource::fromResource(surface); + auto OUTPUTRES = CWLOutputResource::fromResource(output); + if (!OUTPUTRES) { + LOGM(Log::ERR, "onGetLockSurface: invalid output resource"); + return; + } + auto PMONITOR = OUTPUTRES->m_monitor.lock(); + if (!PMONITOR) { + LOGM(Log::ERR, "onGetLockSurface: monitor is gone for output resource"); + return; + } SP sessionLock; for (auto const& l : m_locks) { diff --git a/src/protocols/ShortcutsInhibit.cpp b/src/protocols/ShortcutsInhibit.cpp index e42bf172b..6e6bf0026 100644 --- a/src/protocols/ShortcutsInhibit.cpp +++ b/src/protocols/ShortcutsInhibit.cpp @@ -62,7 +62,7 @@ void CKeyboardShortcutsInhibitProtocol::onInhibit(CZwpKeyboardShortcutsInhibitMa if UNLIKELY (!RESOURCE->good()) { pMgr->noMemory(); m_inhibitors.pop_back(); - LOGM(ERR, "Failed to create an inhibitor resource"); + LOGM(Log::ERR, "Failed to create an inhibitor resource"); return; } } diff --git a/src/protocols/SinglePixel.cpp b/src/protocols/SinglePixel.cpp index 6ca48a356..a75dce29a 100644 --- a/src/protocols/SinglePixel.cpp +++ b/src/protocols/SinglePixel.cpp @@ -1,27 +1,25 @@ #include "SinglePixel.hpp" -#include "../desktop/LayerSurface.hpp" +#include "../desktop/view/LayerSurface.hpp" #include #include "render/Renderer.hpp" CSinglePixelBuffer::CSinglePixelBuffer(uint32_t id, wl_client* client, CHyprColor col_) { - LOGM(LOG, "New single-pixel buffer with color 0x{:x}", col_.getAsHex()); + LOGM(Log::DEBUG, "New single-pixel buffer with color 0x{:x}", col_.getAsHex()); m_color = col_.getAsHex(); - g_pHyprRenderer->makeEGLCurrent(); - m_opaque = col_.a >= 1.F; - m_texture = makeShared(DRM_FORMAT_ARGB8888, rc(&m_color), 4, Vector2D{1, 1}); + m_texture = g_pHyprRenderer->createTexture(DRM_FORMAT_ARGB8888, rc(&m_color), 4, Vector2D{1, 1}); m_resource = CWLBufferResource::create(makeShared(client, 1, id)); - m_success = m_texture->m_texID; + m_success = m_texture->ok(); size = {1, 1}; if (!m_success) - Debug::log(ERR, "Failed creating a single pixel texture: null texture id"); + Log::logger->log(Log::ERR, "Failed creating a single pixel texture: null texture id"); } CSinglePixelBuffer::~CSinglePixelBuffer() { @@ -88,7 +86,7 @@ CSinglePixelBufferManagerResource::CSinglePixelBufferManagerResource(UPsetCreateU32RgbaBuffer([this](CWpSinglePixelBufferManagerV1* res, uint32_t id, uint32_t r, uint32_t g, uint32_t b, uint32_t a) { CHyprColor color{r / sc(std::numeric_limits::max()), g / sc(std::numeric_limits::max()), - b / sc(std::numeric_limits::max()), a / sc(std::numeric_limits::max())}; + b / sc(std::numeric_limits::max()), a / sc(std::numeric_limits::max())}; const auto& RESOURCE = PROTO::singlePixel->m_buffers.emplace_back(makeUnique(id, m_resource->client(), color)); if UNLIKELY (!RESOURCE->good()) { diff --git a/src/protocols/Tablet.cpp b/src/protocols/Tablet.cpp index b4f3b3eba..00f811a4c 100644 --- a/src/protocols/Tablet.cpp +++ b/src/protocols/Tablet.cpp @@ -549,7 +549,7 @@ void CTabletV2Protocol::proximityIn(SP tool, SP tablet, SP continue; if (t->m_seat.expired()) { - LOGM(ERR, "proximityIn on a tool without a seat parent"); + LOGM(Log::ERR, "proximityIn on a tool without a seat parent"); return; } @@ -571,7 +571,7 @@ void CTabletV2Protocol::proximityIn(SP tool, SP tablet, SP } if (!tabletResource || !toolResource) { - LOGM(ERR, "proximityIn on a tool and tablet without valid resource(s)??"); + LOGM(Log::ERR, "proximityIn on a tool and tablet without valid resource(s)??"); return; } @@ -582,7 +582,7 @@ void CTabletV2Protocol::proximityIn(SP tool, SP tablet, SP toolResource->m_resource->sendProximityIn(serial, tabletResource->m_resource.get(), surf->getResource()->resource()); toolResource->queueFrame(); - LOGM(ERR, "proximityIn: found no resource to send enter"); + LOGM(Log::ERR, "proximityIn: found no resource to send enter"); } void CTabletV2Protocol::proximityOut(SP tool) { @@ -623,7 +623,7 @@ void CTabletV2Protocol::mode(SP pad, uint32_t group, uint32_t mode, if (t->m_pad != pad) continue; if (t->m_groups.size() <= group) { - LOGM(ERR, "BUG THIS: group >= t->groups.size()"); + LOGM(Log::ERR, "BUG THIS: group >= t->groups.size()"); return; } auto serial = g_pSeatManager->nextSerial(g_pSeatManager->seatResourceForClient(t->m_resource->client())); @@ -640,9 +640,9 @@ void CTabletV2Protocol::buttonPad(SP pad, uint32_t button, uint32_t } void CTabletV2Protocol::strip(SP pad, uint32_t strip, double position, bool finger, uint32_t timeMs) { - LOGM(ERR, "FIXME: STUB: CTabletV2Protocol::strip not implemented"); + LOGM(Log::ERR, "FIXME: STUB: CTabletV2Protocol::strip not implemented"); } void CTabletV2Protocol::ring(SP pad, uint32_t ring, double position, bool finger, uint32_t timeMs) { - LOGM(ERR, "FIXME: STUB: CTabletV2Protocol::ring not implemented"); + LOGM(Log::ERR, "FIXME: STUB: CTabletV2Protocol::ring not implemented"); } diff --git a/src/protocols/TearingControl.cpp b/src/protocols/TearingControl.cpp index ee65cc9e3..3fd346a7d 100644 --- a/src/protocols/TearingControl.cpp +++ b/src/protocols/TearingControl.cpp @@ -1,13 +1,12 @@ #include "TearingControl.hpp" #include "../managers/ProtocolManager.hpp" -#include "../desktop/Window.hpp" +#include "../desktop/view/Window.hpp" +#include "../event/EventBus.hpp" #include "../Compositor.hpp" #include "core/Compositor.hpp" -#include "../managers/HookSystemManager.hpp" CTearingControlProtocol::CTearingControlProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - static auto P = - g_pHookSystem->hookDynamic("destroyWindow", [this](void* self, SCallbackInfo& info, std::any param) { this->onWindowDestroy(std::any_cast(param)); }); + static auto P = Event::bus()->m_events.window.destroy.listen([this](PHLWINDOW window) { onWindowDestroy(window); }); } void CTearingControlProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { @@ -54,7 +53,7 @@ CTearingControl::CTearingControl(SP resource_, SPsetSetPresentationHint([this](CWpTearingControlV1* res, wpTearingControlV1PresentationHint hint) { this->onHint(hint); }); for (auto const& w : g_pCompositor->m_windows) { - if (w->m_wlSurface->resource() == surf_) { + if (w->wlSurface()->resource() == surf_) { m_window = w; break; } diff --git a/src/protocols/TearingControl.hpp b/src/protocols/TearingControl.hpp index b58ec8e3b..ec31b6f65 100644 --- a/src/protocols/TearingControl.hpp +++ b/src/protocols/TearingControl.hpp @@ -3,7 +3,6 @@ #include "WaylandProtocol.hpp" #include "tearing-control-v1.hpp" -class CWindow; class CTearingControlProtocol; class CWLSurfaceResource; diff --git a/src/protocols/TextInputV1.cpp b/src/protocols/TextInputV1.cpp index 7143b0816..d77bb7363 100644 --- a/src/protocols/TextInputV1.cpp +++ b/src/protocols/TextInputV1.cpp @@ -14,7 +14,7 @@ CTextInputV1::CTextInputV1(SP resource_) : m_resource(resource_ m_resource->setActivate([this](CZwpTextInputV1* pMgr, wl_resource* seat, wl_resource* surface) { if UNLIKELY (!surface) { - LOGM(WARN, "Text-input-v1 PTI{:x}: No surface to activate text input on!", (uintptr_t)this); + LOGM(Log::WARN, "Text-input-v1 PTI{:x}: No surface to activate text input on!", (uintptr_t)this); return; } @@ -103,10 +103,10 @@ void CTextInputV1Protocol::bindManager(wl_client* client, void* data, uint32_t v RESOURCE->setOnDestroy([](CZwpTextInputManagerV1* pMgr) { PROTO::textInputV1->destroyResource(pMgr); }); RESOURCE->setCreateTextInput([this](CZwpTextInputManagerV1* pMgr, uint32_t id) { const auto PTI = m_clients.emplace_back(makeShared(makeShared(pMgr->client(), pMgr->version(), id))); - LOGM(LOG, "New TI V1 at {:x}", (uintptr_t)PTI.get()); + LOGM(Log::DEBUG, "New TI V1 at {:x}", (uintptr_t)PTI.get()); if UNLIKELY (!PTI->good()) { - LOGM(ERR, "Could not alloc wl_resource for TIV1"); + LOGM(Log::ERR, "Could not alloc wl_resource for TIV1"); pMgr->noMemory(); PROTO::textInputV1->destroyResource(PTI.get()); return; diff --git a/src/protocols/TextInputV3.cpp b/src/protocols/TextInputV3.cpp index 8a5ee478b..595467c47 100644 --- a/src/protocols/TextInputV3.cpp +++ b/src/protocols/TextInputV3.cpp @@ -13,7 +13,7 @@ CTextInputV3::CTextInputV3(SP resource_) : m_resource(resource_ if UNLIKELY (!m_resource->resource()) return; - LOGM(LOG, "New tiv3 at {:016x}", (uintptr_t)this); + LOGM(Log::DEBUG, "New tiv3 at {:016x}", (uintptr_t)this); m_resource->setDestroy([this](CZwpTextInputV3* r) { PROTO::textInputV3->destroyTextInput(this); }); m_resource->setOnDestroy([this](CZwpTextInputV3* r) { PROTO::textInputV3->destroyTextInput(this); }); @@ -132,7 +132,7 @@ void CTextInputV3Protocol::onGetTextInput(CZwpTextInputManagerV3* pMgr, uint32_t if UNLIKELY (!RESOURCE->good()) { pMgr->noMemory(); m_textInputs.pop_back(); - LOGM(ERR, "Failed to create a tiv3 resource"); + LOGM(Log::ERR, "Failed to create a tiv3 resource"); return; } diff --git a/src/protocols/ToplevelExport.cpp b/src/protocols/ToplevelExport.cpp index c66c1f2b7..5536e3d7c 100644 --- a/src/protocols/ToplevelExport.cpp +++ b/src/protocols/ToplevelExport.cpp @@ -1,18 +1,15 @@ #include "ToplevelExport.hpp" #include "../Compositor.hpp" #include "ForeignToplevelWlr.hpp" -#include "../managers/PointerManager.hpp" -#include "../managers/SeatManager.hpp" -#include "types/WLBuffer.hpp" -#include "types/Buffer.hpp" +#include "../managers/screenshare/ScreenshareManager.hpp" #include "../helpers/Format.hpp" -#include "../managers/EventManager.hpp" -#include "../managers/input/InputManager.hpp" -#include "../managers/permissions/DynamicPermissionManager.hpp" #include "../render/Renderer.hpp" -#include #include +#include + +using namespace Hyprgraphics::Egl; +using namespace Screenshare; CToplevelExportClient::CToplevelExportClient(SP resource_) : m_resource(resource_) { if UNLIKELY (!good()) @@ -21,364 +18,140 @@ CToplevelExportClient::CToplevelExportClient(SPsetOnDestroy([this](CHyprlandToplevelExportManagerV1* pMgr) { PROTO::toplevelExport->destroyResource(this); }); m_resource->setDestroy([this](CHyprlandToplevelExportManagerV1* pMgr) { PROTO::toplevelExport->destroyResource(this); }); m_resource->setCaptureToplevel([this](CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, uint32_t handle) { - this->captureToplevel(pMgr, frame, overlayCursor, g_pCompositor->getWindowFromHandle(handle)); + captureToplevel(frame, overlayCursor, g_pCompositor->getWindowFromHandle(handle)); }); m_resource->setCaptureToplevelWithWlrToplevelHandle([this](CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* handle) { - this->captureToplevel(pMgr, frame, overlayCursor, PROTO::foreignToplevelWlr->windowFromHandleResource(handle)); + captureToplevel(frame, overlayCursor, PROTO::foreignToplevelWlr->windowFromHandleResource(handle)); }); - m_lastMeasure.reset(); - m_lastFrame.reset(); - m_tickCallback = g_pHookSystem->hookDynamic("tick", [&](void* self, SCallbackInfo& info, std::any data) { onTick(); }); + m_savedClient = m_resource->client(); } -void CToplevelExportClient::captureToplevel(CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor_, PHLWINDOW handle) { +void CToplevelExportClient::captureToplevel(uint32_t frame, int32_t overlayCursor_, PHLWINDOW handle) { + if UNLIKELY (!handle) { + LOGM(Log::ERR, "Couldn't capture (window doesn't exist)"); + return; + } + + auto session = Screenshare::mgr()->getManagedSession(m_resource->client(), handle); + // create a frame const auto FRAME = PROTO::toplevelExport->m_frames.emplace_back( - makeShared(makeShared(m_resource->client(), m_resource->version(), frame), overlayCursor_, handle)); + makeShared(makeShared(m_resource->client(), m_resource->version(), frame), session, !!overlayCursor_)); if UNLIKELY (!FRAME->good()) { - LOGM(ERR, "Couldn't alloc frame for sharing! (no memory)"); + LOGM(Log::ERR, "Couldn't alloc frame for sharing! (no memory)"); m_resource->noMemory(); PROTO::toplevelExport->destroyResource(FRAME.get()); return; } - FRAME->m_self = FRAME; FRAME->m_client = m_self; -} - -void CToplevelExportClient::onTick() { - if (m_lastMeasure.getMillis() < 500) - return; - - m_framesInLastHalfSecond = m_frameCounter; - m_frameCounter = 0; - m_lastMeasure.reset(); - - const auto LASTFRAMEDELTA = m_lastFrame.getMillis() / 1000.0; - const bool FRAMEAWAITING = std::ranges::any_of(PROTO::toplevelExport->m_frames, [&](const auto& frame) { return frame->m_client.get() == this; }); - - if (m_framesInLastHalfSecond > 3 && !m_sentScreencast) { - EMIT_HOOK_EVENT("screencast", (std::vector{1, sc(m_framesInLastHalfSecond), sc(m_clientOwner)})); - g_pEventManager->postEvent(SHyprIPCEvent{"screencast", "1," + std::to_string(m_clientOwner)}); - m_sentScreencast = true; - } else if (m_framesInLastHalfSecond < 4 && m_sentScreencast && LASTFRAMEDELTA > 1.0 && !FRAMEAWAITING) { - EMIT_HOOK_EVENT("screencast", (std::vector{0, sc(m_framesInLastHalfSecond), sc(m_clientOwner)})); - g_pEventManager->postEvent(SHyprIPCEvent{"screencast", "0," + std::to_string(m_clientOwner)}); - m_sentScreencast = false; - } + FRAME->m_self = FRAME; } bool CToplevelExportClient::good() { - return m_resource->resource(); + return m_resource && m_resource->resource(); } -CToplevelExportFrame::CToplevelExportFrame(SP resource_, int32_t overlayCursor_, PHLWINDOW pWindow_) : m_resource(resource_), m_window(pWindow_) { +CToplevelExportFrame::CToplevelExportFrame(SP resource_, WP session, bool overlayCursor) : + m_resource(resource_), m_session(session) { if UNLIKELY (!good()) return; - m_cursorOverlayRequested = !!overlayCursor_; - - if UNLIKELY (!m_window) { - LOGM(ERR, "Client requested sharing of window handle {:x} which does not exist!", m_window); - m_resource->sendFailed(); - return; - } - - if UNLIKELY (!m_window->m_isMapped) { - LOGM(ERR, "Client requested sharing of window handle {:x} which is not shareable!", m_window); - m_resource->sendFailed(); - return; - } - m_resource->setOnDestroy([this](CHyprlandToplevelExportFrameV1* pFrame) { PROTO::toplevelExport->destroyResource(this); }); m_resource->setDestroy([this](CHyprlandToplevelExportFrameV1* pFrame) { PROTO::toplevelExport->destroyResource(this); }); - m_resource->setCopy([this](CHyprlandToplevelExportFrameV1* pFrame, wl_resource* res, int32_t ignoreDamage) { this->copy(pFrame, res, ignoreDamage); }); + m_resource->setCopy([this](CHyprlandToplevelExportFrameV1* pFrame, wl_resource* res, int32_t ignoreDamage) { shareFrame(res, !!ignoreDamage); }); - const auto PMONITOR = m_window->m_monitor.lock(); + m_frame = m_session->nextFrame(overlayCursor); - g_pHyprRenderer->makeEGLCurrent(); - - m_shmFormat = g_pHyprOpenGL->getPreferredReadFormat(PMONITOR); - if UNLIKELY (m_shmFormat == DRM_FORMAT_INVALID) { - LOGM(ERR, "No format supported by renderer in capture toplevel"); + auto formats = m_session->allowedFormats(); + if (formats.empty()) { + LOGM(Log::ERR, "No format supported by renderer in toplevel export protocol"); m_resource->sendFailed(); return; } - const auto PSHMINFO = NFormatUtils::getPixelFormatFromDRM(m_shmFormat); - if UNLIKELY (!PSHMINFO) { - LOGM(ERR, "No pixel format supported by renderer in capture toplevel"); - m_resource->sendFailed(); - return; - } + DRMFormat format = formats.at(0); + auto bufSize = m_frame->bufferSize(); - m_dmabufFormat = g_pHyprOpenGL->getPreferredReadFormat(PMONITOR); + const auto PSHMINFO = getPixelFormatFromDRM(format); + const auto stride = minStride(PSHMINFO, bufSize.x); + m_resource->sendBuffer(NFormatUtils::drmToShm(format), bufSize.x, bufSize.y, stride); - m_box = {0, 0, sc(m_window->m_realSize->value().x * PMONITOR->m_scale), sc(m_window->m_realSize->value().y * PMONITOR->m_scale)}; - - m_box.transform(wlTransformToHyprutils(PMONITOR->m_transform), PMONITOR->m_transformedSize.x, PMONITOR->m_transformedSize.y).round(); - - m_shmStride = NFormatUtils::minStride(PSHMINFO, m_box.w); - - m_resource->sendBuffer(NFormatUtils::drmToShm(m_shmFormat), m_box.width, m_box.height, m_shmStride); - - if LIKELY (m_dmabufFormat != DRM_FORMAT_INVALID) - m_resource->sendLinuxDmabuf(m_dmabufFormat, m_box.width, m_box.height); + if LIKELY (format != DRM_FORMAT_INVALID) + m_resource->sendLinuxDmabuf(format, bufSize.x, bufSize.y); m_resource->sendBufferDone(); } -void CToplevelExportFrame::copy(CHyprlandToplevelExportFrameV1* pFrame, wl_resource* buffer_, int32_t ignoreDamage) { +bool CToplevelExportFrame::good() { + return m_resource && m_resource->resource(); +} + +void CToplevelExportFrame::shareFrame(wl_resource* buffer, bool ignoreDamage) { if UNLIKELY (!good()) { - LOGM(ERR, "No frame in copyFrame??"); + LOGM(Log::ERR, "No frame in shareFrame??"); return; } - if UNLIKELY (!validMapped(m_window)) { - LOGM(ERR, "Client requested sharing of window handle {:x} which is gone!", m_window); + if UNLIKELY (m_session.expired() || !m_session->monitor()) { + LOGM(Log::ERR, "Session stopped for frame {:x}", (uintptr_t)this); m_resource->sendFailed(); return; } - if UNLIKELY (!m_window->m_isMapped) { - LOGM(ERR, "Client requested sharing of window handle {:x} which is not shareable (2)!", m_window); - m_resource->sendFailed(); - return; - } - - const auto PBUFFER = CWLBufferResource::fromResource(buffer_); - if UNLIKELY (!PBUFFER) { - m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); - PROTO::toplevelExport->destroyResource(this); - return; - } - - if UNLIKELY (PBUFFER->m_buffer->size != m_box.size()) { - m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer dimensions"); - PROTO::toplevelExport->destroyResource(this); - return; - } - if UNLIKELY (m_buffer) { + LOGM(Log::ERR, "Buffer used in {:x}", (uintptr_t)this); m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_ALREADY_USED, "frame already used"); - PROTO::toplevelExport->destroyResource(this); + m_resource->sendFailed(); return; } - if (auto attrs = PBUFFER->m_buffer->dmabuf(); attrs.success) { - m_bufferDMA = true; - - if (attrs.format != m_dmabufFormat) { - m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer format"); - PROTO::toplevelExport->destroyResource(this); - return; - } - } else if (auto attrs = PBUFFER->m_buffer->shm(); attrs.success) { - if (attrs.format != m_shmFormat) { - m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer format"); - PROTO::toplevelExport->destroyResource(this); - return; - } else if (attrs.stride != m_shmStride) { - m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer stride"); - PROTO::toplevelExport->destroyResource(this); - return; - } - } else { - m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer type"); - PROTO::toplevelExport->destroyResource(this); + const auto PBUFFERRES = CWLBufferResource::fromResource(buffer); + if UNLIKELY (!PBUFFERRES || !PBUFFERRES->m_buffer) { + LOGM(Log::ERR, "Invalid buffer in {:x}", (uintptr_t)this); + m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); + m_resource->sendFailed(); return; } - m_buffer = CHLBufferReference(PBUFFER->m_buffer.lock()); + const auto& PBUFFER = PBUFFERRES->m_buffer.lock(); - m_ignoreDamage = ignoreDamage; + if (ignoreDamage) + g_pHyprRenderer->damageMonitor(m_session->monitor()); - if (ignoreDamage && validMapped(m_window)) - share(); - else - PROTO::toplevelExport->m_framesAwaitingWrite.emplace_back(m_self); -} - -void CToplevelExportFrame::share() { - if (!m_buffer || !validMapped(m_window)) - return; - - if (m_bufferDMA) { - if (!copyDmabuf(Time::steadyNow())) { - m_resource->sendFailed(); - return; - } - } else { - if (!copyShm(Time::steadyNow())) { - m_resource->sendFailed(); + auto error = m_frame->share(PBUFFER, {}, [this, ignoreDamage, self = m_self](eScreenshareResult result) { + if (self.expired() || !good()) return; + switch (result) { + case RESULT_COPIED: { + m_resource->sendFlags(sc(0)); + if (!ignoreDamage) + m_frame->damage().forEachRect([&](const auto& rect) { m_resource->sendDamage(rect.x1, rect.y1, rect.x2 - rect.x1, rect.y2 - rect.y1); }); + + const auto [sec, nsec] = Time::secNsec(m_timestamp); + uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0; + uint32_t tvSecLo = sec & 0xFFFFFFFF; + m_resource->sendReady(tvSecHi, tvSecLo, nsec); + break; + } + case RESULT_NOT_COPIED: + LOGM(Log::ERR, "Frame share failed in {:x}", (uintptr_t)this); + m_resource->sendFailed(); + break; + case RESULT_TIMESTAMP: m_timestamp = Time::steadyNow(); break; } + }); + + switch (error) { + case ERROR_NONE: m_buffer = CHLBufferReference(PBUFFER); break; + case ERROR_NO_BUFFER: m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); break; + case ERROR_BUFFER_SIZE: + case ERROR_BUFFER_FORMAT: m_resource->sendFailed(); break; + case ERROR_UNKNOWN: + case ERROR_STOPPED: m_resource->sendFailed(); break; } - - m_resource->sendFlags(sc(0)); - - if (!m_ignoreDamage) - m_resource->sendDamage(0, 0, m_box.width, m_box.height); - - const auto [sec, nsec] = Time::secNsec(Time::steadyNow()); - - uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0; - uint32_t tvSecLo = sec & 0xFFFFFFFF; - m_resource->sendReady(tvSecHi, tvSecLo, nsec); -} - -bool CToplevelExportFrame::copyShm(const Time::steady_tp& now) { - const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_resource->client(), PERMISSION_TYPE_SCREENCOPY); - auto shm = m_buffer->shm(); - auto [pixelData, fmt, bufLen] = m_buffer->beginDataPtr(0); // no need for end, cuz it's shm - - // render the client - const auto PMONITOR = m_window->m_monitor.lock(); - CRegion fakeDamage{0, 0, PMONITOR->m_pixelSize.x * 10, PMONITOR->m_pixelSize.y * 10}; - - g_pHyprRenderer->makeEGLCurrent(); - - CFramebuffer outFB; - outFB.alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, PMONITOR->m_output->state->state().drmFormat); - - auto overlayCursor = shouldOverlayCursor(); - - if (overlayCursor) { - g_pPointerManager->lockSoftwareForMonitor(PMONITOR->m_self.lock()); - g_pPointerManager->damageCursor(PMONITOR->m_self.lock()); - } - - if (!g_pHyprRenderer->beginRender(PMONITOR, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &outFB)) - return false; - - g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 1.0)); - - // render client at 0,0 - if (PERM == PERMISSION_RULE_ALLOW_MODE_ALLOW) { - if (!m_window->m_ruleApplicator->noScreenShare().valueOrDefault()) { - g_pHyprRenderer->m_bBlockSurfaceFeedback = g_pHyprRenderer->shouldRenderWindow(m_window); // block the feedback to avoid spamming the surface if it's visible - g_pHyprRenderer->renderWindow(m_window, PMONITOR, now, false, RENDER_PASS_ALL, true, true); - g_pHyprRenderer->m_bBlockSurfaceFeedback = false; - } - if (overlayCursor) - g_pPointerManager->renderSoftwareCursorsFor(PMONITOR->m_self.lock(), now, fakeDamage, g_pInputManager->getMouseCoordsInternal() - m_window->m_realPosition->value()); - } else if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY) { - CBox texbox = CBox{PMONITOR->m_transformedSize / 2.F, g_pHyprOpenGL->m_screencopyDeniedTexture->m_size}.translate(-g_pHyprOpenGL->m_screencopyDeniedTexture->m_size / 2.F); - g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_screencopyDeniedTexture, texbox, {}); - } - - const auto PFORMAT = NFormatUtils::getPixelFormatFromDRM(shm.format); - if (!PFORMAT) { - g_pHyprRenderer->endRender(); - return false; - } - - g_pHyprOpenGL->m_renderData.blockScreenShader = true; - g_pHyprRenderer->endRender(); - - g_pHyprRenderer->makeEGLCurrent(); - g_pHyprOpenGL->m_renderData.pMonitor = PMONITOR; - outFB.bind(); - - glBindFramebuffer(GL_READ_FRAMEBUFFER, outFB.getFBID()); - glPixelStorei(GL_PACK_ALIGNMENT, 1); - - auto glFormat = PFORMAT->flipRB ? GL_BGRA_EXT : GL_RGBA; - - auto origin = Vector2D(0, 0); - switch (PMONITOR->m_transform) { - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - case WL_OUTPUT_TRANSFORM_90: { - origin.y = PMONITOR->m_pixelSize.y - m_box.height; - break; - } - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - case WL_OUTPUT_TRANSFORM_180: { - origin.x = PMONITOR->m_pixelSize.x - m_box.width; - origin.y = PMONITOR->m_pixelSize.y - m_box.height; - break; - } - case WL_OUTPUT_TRANSFORM_FLIPPED: - case WL_OUTPUT_TRANSFORM_270: { - origin.x = PMONITOR->m_pixelSize.x - m_box.width; - break; - } - default: break; - } - - glReadPixels(origin.x, origin.y, m_box.width, m_box.height, glFormat, PFORMAT->glType, pixelData); - - if (overlayCursor) { - g_pPointerManager->unlockSoftwareForMonitor(PMONITOR->m_self.lock()); - g_pPointerManager->damageCursor(PMONITOR->m_self.lock()); - } - - outFB.unbind(); - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - - return true; -} - -bool CToplevelExportFrame::copyDmabuf(const Time::steady_tp& now) { - const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_resource->client(), PERMISSION_TYPE_SCREENCOPY); - const auto PMONITOR = m_window->m_monitor.lock(); - - CRegion fakeDamage{0, 0, INT16_MAX, INT16_MAX}; - - auto overlayCursor = shouldOverlayCursor(); - - if (overlayCursor) { - g_pPointerManager->lockSoftwareForMonitor(PMONITOR->m_self.lock()); - g_pPointerManager->damageCursor(PMONITOR->m_self.lock()); - } - - if (!g_pHyprRenderer->beginRender(PMONITOR, fakeDamage, RENDER_MODE_TO_BUFFER, m_buffer.m_buffer)) - return false; - - g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 1.0)); - if (PERM == PERMISSION_RULE_ALLOW_MODE_ALLOW) { - if (!m_window->m_ruleApplicator->noScreenShare().valueOrDefault()) { - g_pHyprRenderer->m_bBlockSurfaceFeedback = g_pHyprRenderer->shouldRenderWindow(m_window); // block the feedback to avoid spamming the surface if it's visible - g_pHyprRenderer->renderWindow(m_window, PMONITOR, now, false, RENDER_PASS_ALL, true, true); - g_pHyprRenderer->m_bBlockSurfaceFeedback = false; - } - - if (overlayCursor) - g_pPointerManager->renderSoftwareCursorsFor(PMONITOR->m_self.lock(), now, fakeDamage, g_pInputManager->getMouseCoordsInternal() - m_window->m_realPosition->value()); - } else if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY) { - CBox texbox = CBox{PMONITOR->m_transformedSize / 2.F, g_pHyprOpenGL->m_screencopyDeniedTexture->m_size}.translate(-g_pHyprOpenGL->m_screencopyDeniedTexture->m_size / 2.F); - g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_screencopyDeniedTexture, texbox, {}); - } - - g_pHyprOpenGL->m_renderData.blockScreenShader = true; - g_pHyprRenderer->endRender(); - - if (overlayCursor) { - g_pPointerManager->unlockSoftwareForMonitor(PMONITOR->m_self.lock()); - g_pPointerManager->damageCursor(PMONITOR->m_self.lock()); - } - - return true; -} - -bool CToplevelExportFrame::shouldOverlayCursor() const { - if (!m_cursorOverlayRequested) - return false; - - auto pointerSurfaceResource = g_pSeatManager->m_state.pointerFocus.lock(); - - if (!pointerSurfaceResource) - return false; - - auto pointerSurface = CWLSurface::fromResource(pointerSurfaceResource); - - return pointerSurface && pointerSurface->getWindow() == 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) { @@ -389,7 +162,7 @@ void CToplevelExportProtocol::bindManager(wl_client* client, void* data, uint32_ const auto CLIENT = m_clients.emplace_back(makeShared(makeShared(client, ver, id))); if (!CLIENT->good()) { - LOGM(LOG, "Failed to bind client! (out of memory)"); + LOGM(Log::DEBUG, "Failed to bind client! (out of memory)"); wl_client_post_no_memory(client); m_clients.pop_back(); return; @@ -397,73 +170,14 @@ void CToplevelExportProtocol::bindManager(wl_client* client, void* data, uint32_ CLIENT->m_self = CLIENT; - LOGM(LOG, "Bound client successfully!"); + LOGM(Log::DEBUG, "Bound client successfully!"); } void CToplevelExportProtocol::destroyResource(CToplevelExportClient* client) { - std::erase_if(m_clients, [&](const auto& other) { return other.get() == client; }); std::erase_if(m_frames, [&](const auto& other) { return other->m_client.get() == client; }); - std::erase_if(m_framesAwaitingWrite, [&](const auto& other) { return !other || other->m_client.get() == client; }); + std::erase_if(m_clients, [&](const auto& other) { return other.get() == client; }); } void CToplevelExportProtocol::destroyResource(CToplevelExportFrame* frame) { std::erase_if(m_frames, [&](const auto& other) { return other.get() == frame; }); - std::erase_if(m_framesAwaitingWrite, [&](const auto& other) { return !other || other.get() == frame; }); -} - -void CToplevelExportProtocol::onOutputCommit(PHLMONITOR pMonitor) { - if (m_framesAwaitingWrite.empty()) - return; // nothing to share - - std::vector> framesToRemove; - // reserve number of elements to avoid reallocations - framesToRemove.reserve(m_framesAwaitingWrite.size()); - - // share frame if correct output - for (auto const& f : m_framesAwaitingWrite) { - if (!f) - continue; - - // check permissions - const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(f->m_resource->client(), PERMISSION_TYPE_SCREENCOPY); - - if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) - continue; // pending an answer, don't do anything yet. - - if (!validMapped(f->m_window)) { - framesToRemove.emplace_back(f); - continue; - } - - if (!f->m_window) - continue; - - const auto PWINDOW = f->m_window; - - if (pMonitor != PWINDOW->m_monitor.lock()) - continue; - - CBox geometry = {PWINDOW->m_realPosition->value().x, PWINDOW->m_realPosition->value().y, PWINDOW->m_realSize->value().x, PWINDOW->m_realSize->value().y}; - - if (geometry.intersection({pMonitor->m_position, pMonitor->m_size}).empty()) - continue; - - f->share(); - - f->m_client->m_lastFrame.reset(); - ++f->m_client->m_frameCounter; - - framesToRemove.push_back(f); - } - - for (auto const& f : framesToRemove) { - std::erase(m_framesAwaitingWrite, f); - } -} - -void CToplevelExportProtocol::onWindowUnmap(PHLWINDOW pWindow) { - for (auto const& f : m_frames) { - if (f->m_window == pWindow) - f->m_window.reset(); - } } diff --git a/src/protocols/ToplevelExport.hpp b/src/protocols/ToplevelExport.hpp index 8ccd881bf..5d30f09be 100644 --- a/src/protocols/ToplevelExport.hpp +++ b/src/protocols/ToplevelExport.hpp @@ -1,73 +1,59 @@ #pragma once -#include "../defines.hpp" -#include "hyprland-toplevel-export-v1.hpp" #include "WaylandProtocol.hpp" -#include "Screencopy.hpp" +#include "hyprland-toplevel-export-v1.hpp" + #include "../helpers/time/Time.hpp" +#include "./types/Buffer.hpp" +#include #include class CMonitor; -class CWindow; +namespace Screenshare { + class CScreenshareSession; + class CScreenshareFrame; +}; class CToplevelExportClient { public: CToplevelExportClient(SP resource_); - bool good(); - - WP m_self; - eClientOwners m_clientOwner = CLIENT_TOPLEVEL_EXPORT; - - CTimer m_lastFrame; - int m_frameCounter = 0; + bool good(); private: SP m_resource; + WP m_self; - int m_framesInLastHalfSecond = 0; - CTimer m_lastMeasure; - bool m_sentScreencast = false; + wl_client* m_savedClient = nullptr; - SP m_tickCallback; - void onTick(); - - void captureToplevel(CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, PHLWINDOW handle); + void captureToplevel(uint32_t frame, int32_t overlayCursor, PHLWINDOW handle); friend class CToplevelExportProtocol; }; class CToplevelExportFrame { public: - CToplevelExportFrame(SP resource_, int32_t overlayCursor, PHLWINDOW pWindow); + CToplevelExportFrame(SP resource, WP session, bool overlayCursor); - bool good(); - - WP m_self; - WP m_client; + bool good(); private: - SP m_resource; + SP m_resource; + WP m_self; + WP m_client; - PHLWINDOW m_window; - bool m_cursorOverlayRequested = false; - bool m_ignoreDamage = false; + WP m_session; + UP m_frame; - CHLBufferReference m_buffer; - bool m_bufferDMA = false; - uint32_t m_shmFormat = 0; - uint32_t m_dmabufFormat = 0; - int m_shmStride = 0; - CBox m_box = {}; + CHLBufferReference m_buffer; + Time::steady_tp m_timestamp; - void copy(CHyprlandToplevelExportFrameV1* pFrame, wl_resource* buffer, int32_t ignoreDamage); - bool copyDmabuf(const Time::steady_tp& now); - bool copyShm(const Time::steady_tp& now); - void share(); - bool shouldOverlayCursor() const; + // + void shareFrame(wl_resource* buffer, bool ignoreDamage); friend class CToplevelExportProtocol; + friend class CToplevelExportClient; }; class CToplevelExportProtocol : IWaylandProtocol { @@ -78,18 +64,13 @@ class CToplevelExportProtocol : IWaylandProtocol { void destroyResource(CToplevelExportClient* client); void destroyResource(CToplevelExportFrame* frame); - void onWindowUnmap(PHLWINDOW pWindow); void onOutputCommit(PHLMONITOR pMonitor); private: std::vector> m_clients; std::vector> m_frames; - std::vector> m_framesAwaitingWrite; - 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); + void onWindowUnmap(PHLWINDOW pWindow); friend class CToplevelExportClient; friend class CToplevelExportFrame; diff --git a/src/protocols/ToplevelMapping.cpp b/src/protocols/ToplevelMapping.cpp index 4cc822f0b..823956dfd 100644 --- a/src/protocols/ToplevelMapping.cpp +++ b/src/protocols/ToplevelMapping.cpp @@ -17,7 +17,7 @@ CToplevelMappingManager::CToplevelMappingManager(SP(makeShared(m_resource->client(), m_resource->version(), handle))); if UNLIKELY (!NEWHANDLE->m_resource->resource()) { - LOGM(ERR, "Couldn't alloc mapping handle! (no memory)"); + LOGM(Log::ERR, "Couldn't alloc mapping handle! (no memory)"); m_resource->noMemory(); return; } @@ -36,7 +36,7 @@ CToplevelMappingManager::CToplevelMappingManager(SP(makeShared(m_resource->client(), m_resource->version(), handle))); if UNLIKELY (!NEWHANDLE->m_resource->resource()) { - LOGM(ERR, "Couldn't alloc mapping handle! (no memory)"); + LOGM(Log::ERR, "Couldn't alloc mapping handle! (no memory)"); m_resource->noMemory(); return; } @@ -62,7 +62,7 @@ void CToplevelMappingProtocol::bindManager(wl_client* client, void* data, uint32 const auto RESOURCE = m_managers.emplace_back(makeUnique(makeShared(client, ver, id))).get(); if UNLIKELY (!RESOURCE->good()) { - LOGM(ERR, "Couldn't create a toplevel mapping manager"); + LOGM(Log::ERR, "Couldn't create a toplevel mapping manager"); wl_client_post_no_memory(client); m_managers.pop_back(); return; diff --git a/src/protocols/VirtualKeyboard.cpp b/src/protocols/VirtualKeyboard.cpp index 2acc22985..22f696329 100644 --- a/src/protocols/VirtualKeyboard.cpp +++ b/src/protocols/VirtualKeyboard.cpp @@ -2,7 +2,7 @@ #include #include #include "../config/ConfigValue.hpp" -#include "../config/ConfigManager.hpp" +#include "../config/legacy/ConfigManager.hpp" #include "../devices/IKeyboard.hpp" #include "../helpers/time/Time.hpp" #include "../helpers/MiscFunctions.hpp" @@ -75,14 +75,14 @@ CVirtualKeyboardV1Resource::CVirtualKeyboardV1Resource(SP auto xkbContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS); CFileDescriptor keymapFd{fd}; if UNLIKELY (!xkbContext) { - LOGM(ERR, "xkbContext creation failed"); + LOGM(Log::ERR, "xkbContext creation failed"); r->noMemory(); return; } auto keymapData = mmap(nullptr, len, PROT_READ, MAP_PRIVATE, keymapFd.get(), 0); if UNLIKELY (keymapData == MAP_FAILED) { - LOGM(ERR, "keymapData alloc failed"); + LOGM(Log::ERR, "keymapData alloc failed"); xkb_context_unref(xkbContext); r->noMemory(); return; @@ -92,7 +92,7 @@ CVirtualKeyboardV1Resource::CVirtualKeyboardV1Resource(SP munmap(keymapData, len); if UNLIKELY (!xkbKeymap) { - LOGM(ERR, "xkbKeymap creation failed"); + LOGM(Log::ERR, "xkbKeymap creation failed"); xkb_context_unref(xkbContext); r->noMemory(); return; @@ -135,7 +135,7 @@ void CVirtualKeyboardV1Resource::releasePressed() { } void CVirtualKeyboardV1Resource::destroy() { - const auto RELEASEPRESSED = g_pConfigManager->getDeviceInt(m_name, "release_pressed_on_close", "input:virtualkeyboard:release_pressed_on_close"); + const auto RELEASEPRESSED = Config::mgr()->getDeviceInt(m_name, "release_pressed_on_close", "input:virtualkeyboard:release_pressed_on_close"); if (RELEASEPRESSED) releasePressed(); m_events.destroy.emit(); @@ -171,7 +171,7 @@ void CVirtualKeyboardProtocol::onCreateKeeb(CZwpVirtualKeyboardManagerV1* pMgr, return; } - LOGM(LOG, "New VKeyboard at id {}", id); + LOGM(Log::DEBUG, "New VKeyboard at id {}", id); m_events.newKeyboard.emit(RESOURCE); } diff --git a/src/protocols/VirtualPointer.cpp b/src/protocols/VirtualPointer.cpp index 9e258b7a2..075f7cb9b 100644 --- a/src/protocols/VirtualPointer.cpp +++ b/src/protocols/VirtualPointer.cpp @@ -145,7 +145,7 @@ void CVirtualPointerProtocol::onCreatePointer(CZwlrVirtualPointerManagerV1* pMgr return; } - LOGM(LOG, "New VPointer at id {}", id); + LOGM(Log::DEBUG, "New VPointer at id {}", id); m_events.newPointer.emit(RESOURCE); } diff --git a/src/protocols/WaylandProtocol.cpp b/src/protocols/WaylandProtocol.cpp index 650992485..4cb4f9910 100644 --- a/src/protocols/WaylandProtocol.cpp +++ b/src/protocols/WaylandProtocol.cpp @@ -24,7 +24,7 @@ IWaylandProtocol::IWaylandProtocol(const wl_interface* iface, const int& ver, co m_name(name), m_global(wl_global_create(g_pCompositor->m_wlDisplay, iface, ver, this, &bindManagerInternal)) { if UNLIKELY (!m_global) { - LOGM(ERR, "could not create a global [{}]", m_name); + LOGM(Log::ERR, "could not create a global [{}]", m_name); return; } @@ -33,7 +33,7 @@ IWaylandProtocol::IWaylandProtocol(const wl_interface* iface, const int& ver, co m_liDisplayDestroy.parent = this; wl_display_add_destroy_listener(g_pCompositor->m_wlDisplay, &m_liDisplayDestroy.listener); - LOGM(LOG, "Registered global [{}]", m_name); + LOGM(Log::DEBUG, "Registered global [{}]", m_name); } IWaylandProtocol::~IWaylandProtocol() { diff --git a/src/protocols/WaylandProtocol.hpp b/src/protocols/WaylandProtocol.hpp index d46d6aafb..5c187c7d3 100644 --- a/src/protocols/WaylandProtocol.hpp +++ b/src/protocols/WaylandProtocol.hpp @@ -4,6 +4,7 @@ #include "../helpers/memory/Memory.hpp" #include +#include #define RESOURCE_OR_BAIL(resname) \ const auto resname = (CWaylandResource*)wl_resource_get_user_data(resource); \ @@ -28,16 +29,16 @@ #define LOGM(level, ...) \ do { \ std::ostringstream oss; \ - if (level == WARN || level == ERR || level == CRIT) { \ + if (level == Log::WARN || level == Log::ERR || level == Log::CRIT) { \ oss << "[" << __FILE__ << ":" << __LINE__ << "] "; \ - } else if (level == LOG || level == INFO || level == TRACE) { \ + } else if (level == Log::DEBUG || level == Log::INFO || level == Log::TRACE) { \ oss << "[" << EXTRACT_CLASS_NAME() << "] "; \ } \ - if constexpr (std::tuple_size::value == 1 && std::is_same_v) { \ + if constexpr (std::tuple_size_v == 1 && std::is_same_v) { \ oss << __VA_ARGS__; \ - Debug::log(level, oss.str()); \ + Log::logger->log(level, oss.str()); \ } else { \ - Debug::log(level, std::format("{}{}", oss.str(), std::format(__VA_ARGS__))); \ + Log::logger->log(level, std::format("{}{}", oss.str(), std::format(__VA_ARGS__))); \ } \ } while (0) diff --git a/src/protocols/XDGActivation.cpp b/src/protocols/XDGActivation.cpp index f25ffca8b..3d094fca1 100644 --- a/src/protocols/XDGActivation.cpp +++ b/src/protocols/XDGActivation.cpp @@ -19,7 +19,7 @@ CXDGActivationToken::CXDGActivationToken(SP resource_) : // TODO: should we send a protocol error of already_used here // if it was used? the protocol spec doesn't say _when_ it should be sent... if UNLIKELY (m_committed) { - LOGM(WARN, "possible protocol error, two commits from one token. Ignoring."); + LOGM(Log::WARN, "possible protocol error, two commits from one token. Ignoring."); return; } @@ -27,7 +27,7 @@ CXDGActivationToken::CXDGActivationToken(SP resource_) : // send done with a new token m_token = g_pTokenManager->registerNewToken({}, std::chrono::months{12}); - LOGM(LOG, "assigned new xdg-activation token {}", m_token); + LOGM(Log::DEBUG, "assigned new xdg-activation token {}", m_token); m_resource->sendDone(m_token.c_str()); @@ -70,7 +70,7 @@ void CXDGActivationProtocol::bindManager(wl_client* client, void* data, uint32_t auto TOKEN = std::ranges::find_if(m_sentTokens, [token](const auto& t) { return t.token == token; }); if UNLIKELY (TOKEN == m_sentTokens.end()) { - LOGM(WARN, "activate event for non-existent token {}??", token); + LOGM(Log::WARN, "activate event for non-existent token {}??", token); return; } @@ -81,7 +81,7 @@ void CXDGActivationProtocol::bindManager(wl_client* client, void* data, uint32_t const auto PWINDOW = g_pCompositor->getWindowFromSurface(surf); if UNLIKELY (!PWINDOW) { - LOGM(WARN, "activate event for non-window or gone surface with token {}, ignoring", token); + LOGM(Log::WARN, "activate event for non-window or gone surface with token {}, ignoring", token); return; } diff --git a/src/protocols/XDGBell.cpp b/src/protocols/XDGBell.cpp index 884564735..b53621d51 100644 --- a/src/protocols/XDGBell.cpp +++ b/src/protocols/XDGBell.cpp @@ -1,6 +1,6 @@ #include "XDGBell.hpp" #include "core/Compositor.hpp" -#include "../desktop/Window.hpp" +#include "../desktop/view/Window.hpp" #include "../managers/EventManager.hpp" #include "../Compositor.hpp" @@ -31,10 +31,10 @@ CXDGSystemBellManagerResource::CXDGSystemBellManagerResource(UPm_windows) { - if (!w->m_isMapped || w->m_isX11 || !w->m_xdgSurface || !w->m_wlSurface) + if (!w->m_isMapped || w->m_isX11 || !w->m_xdgSurface || !w->wlSurface()) continue; - if (w->m_wlSurface->resource() == SURFACE) { + if (w->wlSurface()->resource() == SURFACE) { g_pEventManager->postEvent(SHyprIPCEvent{ .event = "bell", .data = std::format("{:x}", rc(w.get())), diff --git a/src/protocols/XDGDecoration.cpp b/src/protocols/XDGDecoration.cpp index 0339db6b2..e711ab3bb 100644 --- a/src/protocols/XDGDecoration.cpp +++ b/src/protocols/XDGDecoration.cpp @@ -16,7 +16,7 @@ CXDGDecoration::CXDGDecoration(SP resource_, wl_resou default: modeString = "INVALID"; break; } - LOGM(LOG, "setMode: {}. {} MODE_SERVER_SIDE as reply.", modeString, (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE ? "Sending" : "Ignoring and sending")); + LOGM(Log::DEBUG, "setMode: {}. {} MODE_SERVER_SIDE as reply.", modeString, (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE ? "Sending" : "Ignoring and sending")); auto sendMode = xdgModeOnRequestCSD(mode); m_resource->sendConfigure(sendMode); mostRecentlySent = sendMode; @@ -24,7 +24,7 @@ CXDGDecoration::CXDGDecoration(SP resource_, wl_resou }); m_resource->setUnsetMode([this](CZxdgToplevelDecorationV1*) { - LOGM(LOG, "unsetMode. Sending MODE_SERVER_SIDE."); + LOGM(Log::DEBUG, "unsetMode. Sending MODE_SERVER_SIDE."); auto sendMode = xdgModeOnReleaseCSD(); m_resource->sendConfigure(sendMode); mostRecentlySent = sendMode; diff --git a/src/protocols/XDGDialog.cpp b/src/protocols/XDGDialog.cpp index c64a8379c..ad6b3eeb8 100644 --- a/src/protocols/XDGDialog.cpp +++ b/src/protocols/XDGDialog.cpp @@ -1,6 +1,6 @@ #include "XDGDialog.hpp" #include "XDGShell.hpp" -#include "../desktop/WLSurface.hpp" +#include "../desktop/view/WLSurface.hpp" #include "../Compositor.hpp" #include @@ -26,11 +26,16 @@ void CXDGDialogV1Resource::updateWindow() { if UNLIKELY (!m_toplevel || !m_toplevel->m_parent || !m_toplevel->m_parent->m_owner) return; - auto HLSurface = CWLSurface::fromResource(m_toplevel->m_parent->m_owner->m_surface.lock()); - if UNLIKELY (!HLSurface || !HLSurface->getWindow()) + const auto HLSURFACE = Desktop::View::CWLSurface::fromResource(m_toplevel->m_parent->m_owner->m_surface.lock()); + if UNLIKELY (!HLSURFACE) return; - HLSurface->getWindow()->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_MODAL); + const auto WINDOW = Desktop::View::CWindow::fromView(HLSURFACE->view()); + + if UNLIKELY (!WINDOW) + return; + + WINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_MODAL); } bool CXDGDialogV1Resource::good() { diff --git a/src/protocols/XDGForeignV2.cpp b/src/protocols/XDGForeignV2.cpp new file mode 100644 index 000000000..f67cb86cf --- /dev/null +++ b/src/protocols/XDGForeignV2.cpp @@ -0,0 +1,175 @@ +#include "protocols/XDGForeignV2.hpp" +#include "managers/TokenManager.hpp" +#include "protocols/XDGShell.hpp" +#include "xdg-foreign-unstable-v2.hpp" +#include +#include +#include +#include +#include +#include +#include "protocols/core/Compositor.hpp" + +CXDGExportedResourceV2::CXDGExportedResourceV2(SP resource, SP toplevel, const std::string& handle) : + m_resource(resource), m_toplevel(toplevel), m_handle(handle) { + + if UNLIKELY (!good()) + return; + + m_resource->setData(this); + m_resource->sendHandle(handle.c_str()); + m_listeners.topLevelDestroyed = toplevel->m_events.destroy.listen([this] { + m_topLevelDestroyed = true; + m_listeners.topLevelDestroyed.reset(); + m_events.destroy.emit(); + }); + m_resource->setOnDestroy([this](CZxdgExportedV2*) { PROTO::xdgForeignExporter->destroyExported(this); }); + m_resource->setDestroy([this](CZxdgExportedV2*) { PROTO::xdgForeignExporter->destroyExported(this); }); +} + +CXDGExportedResourceV2::~CXDGExportedResourceV2() { + if (!m_topLevelDestroyed) + m_events.destroy.emit(); +} + +bool CXDGExportedResourceV2::good() const { + return m_resource->resource(); +} + +WP CXDGExportedResourceV2::xdgSurf() const { + return m_toplevel; +} + +std::string_view CXDGExportedResourceV2::handle() const { + return m_handle; +} + +CXDGForeignExporterProtocolV2::CXDGForeignExporterProtocolV2(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {} + +void CXDGForeignExporterProtocolV2::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = m_exporters.emplace_back(makeUnique(client, ver, id)).get(); + + if UNLIKELY (!RESOURCE->resource()) { + wl_client_post_no_memory(client); + m_exporters.pop_back(); + return; + } + + RESOURCE->setExportToplevel([this](CZxdgExporterV2* exporter, uint32_t id, wl_resource* surface) { + auto wlSurf = CWLSurfaceResource::fromResource(surface); + + if (wlSurf->m_role != SURFACE_ROLE_XDG_SHELL) { + exporter->error(zxdgExporterV2Error::ZXDG_EXPORTER_V2_ERROR_INVALID_SURFACE, "surface must be an xdg_toplevel"); + return; + } + + auto xdgSurfResource = sc(wlSurf->m_role.get())->m_xdgSurface.lock(); + if (xdgSurfResource->m_toplevel.expired()) + return; + + auto xdgSurf = xdgSurfResource->m_toplevel.lock(); + const std::string HANDLE = g_pTokenManager->getRandomUUID(); + const auto [ELM, EMPLACED] = + m_exported.emplace(HANDLE, makeShared(makeShared(exporter->client(), exporter->version(), id), xdgSurf, HANDLE)); + + // This should only happen if we have our generated handles collide. + if UNLIKELY (!EMPLACED) { + wl_client_post_no_memory(exporter->client()); + return; + } + + if UNLIKELY (!ELM->second->good()) { + wl_client_post_no_memory(exporter->client()); + destroyExported(ELM->second.get()); + } + }); + + RESOURCE->setDestroy([this](CZxdgExporterV2* e) { onExporterDestroyed(e); }); + RESOURCE->setOnDestroy([this](CZxdgExporterV2* e) { onExporterDestroyed(e); }); +} + +SP CXDGForeignExporterProtocolV2::getExported(const std::string& handle) const { + return m_exported.contains(handle) ? m_exported.at(handle) : nullptr; +} + +void CXDGForeignExporterProtocolV2::onExporterDestroyed(CZxdgExporterV2* exporter) { + std::erase_if(m_exporters, [exporter](const auto& other) { return exporter == other.get(); }); +} + +void CXDGForeignExporterProtocolV2::destroyExported(CXDGExportedResourceV2* r) { + PROTO::xdgForeignExporter->m_exported.erase(r->m_handle); +} + +CXDGImportedResourceV2::CXDGImportedResourceV2(SP imported, SP exported, const std::string& handle) : + m_resource(imported), m_exported(exported), m_handle(handle) { + if UNLIKELY (!m_resource->resource() || m_exported.expired()) + return; + + m_resource->setData(this); + + m_resource->setSetParentOf([this](CZxdgImportedV2* r, wl_resource* surf) { + const auto CHILDSURF = CWLSurfaceResource::fromResource(surf); + + if (CHILDSURF->m_role != SURFACE_ROLE_XDG_SHELL) { + m_resource->error(zxdgImportedV2Error::ZXDG_IMPORTED_V2_ERROR_INVALID_SURFACE, "surface must be an xdg_toplevel"); + return; + } + + const auto CHILDXDGSURF = sc(CHILDSURF->m_role.get())->m_xdgSurface.lock(); + if (CHILDXDGSURF->m_toplevel.expired()) + return; + + if LIKELY (auto exportedTopLevel = m_exported->xdgSurf(); !exportedTopLevel.expired()) + CHILDXDGSURF->m_toplevel->setNewParent(exportedTopLevel.lock()); + }); + + m_listeners.exportedDestroyed = m_exported->m_events.destroy.listen([this]() { PROTO::xdgForeignImporter->destroyImported(this); }); + m_resource->setDestroy([this](CZxdgImportedV2*) { PROTO::xdgForeignImporter->destroyImported(this); }); + m_resource->setOnDestroy([this](CZxdgImportedV2*) { PROTO::xdgForeignImporter->destroyImported(this); }); +} + +CXDGImportedResourceV2::~CXDGImportedResourceV2() { + m_resource->sendDestroyed(); +} + +CXDGForeignImporterProtocolV2::CXDGForeignImporterProtocolV2(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void CXDGForeignImporterProtocolV2::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = m_importers.emplace_back(makeUnique(client, ver, id)).get(); + + if UNLIKELY (!RESOURCE->resource()) { + wl_client_post_no_memory(client); + m_importers.pop_back(); + return; + } + + RESOURCE->setImportToplevel([this](CZxdgImporterV2* importer, uint32_t id, const char* _handle) { + const std::string HANDLE = _handle; + auto exported = PROTO::xdgForeignExporter->getExported(HANDLE); + auto imported = + m_imports.emplace_back(makeUnique(makeShared(importer->client(), importer->version(), id), exported, HANDLE)).get(); + + if UNLIKELY (!imported->m_resource->resource()) { + wl_client_post_no_memory(importer->client()); + m_imports.pop_back(); + return; + } + + // Couldn't find the handle. + if UNLIKELY (imported->m_exported.expired()) + destroyImported(imported); + }); + + RESOURCE->setDestroy([this](CZxdgImporterV2* r) { onImporterDestroyed(r); }); + RESOURCE->setOnDestroy([this](CZxdgImporterV2* r) { onImporterDestroyed(r); }); +} + +void CXDGForeignImporterProtocolV2::onImporterDestroyed(CZxdgImporterV2* importer) { + std::erase_if(m_importers, [importer](const auto& other) { return importer == other.get(); }); +} + +void CXDGForeignImporterProtocolV2::destroyImported(CXDGImportedResourceV2* imported) { + std::erase_if(m_imports, [imported](const auto& other) { return imported == other.get(); }); +} \ No newline at end of file diff --git a/src/protocols/XDGForeignV2.hpp b/src/protocols/XDGForeignV2.hpp new file mode 100644 index 000000000..78c8dbf71 --- /dev/null +++ b/src/protocols/XDGForeignV2.hpp @@ -0,0 +1,92 @@ +#include +#include +#include +#include +#include +#include "WaylandProtocol.hpp" +#include "xdg-foreign-unstable-v2.hpp" +#include "../helpers/signal/Signal.hpp" + +class CXDGImportedResourceV2; +class CWLSurface; +class CXDGToplevelResource; + +class CXDGExportedResourceV2 { + public: + CXDGExportedResourceV2(SP resource_, SP surface_, const std::string& handle); + ~CXDGExportedResourceV2(); + + struct { + CSignalT<> destroy; + } m_events; + + bool good() const; + std::string_view handle() const; + WP xdgSurf() const; + + private: + bool m_topLevelDestroyed = false; + struct { + CHyprSignalListener topLevelDestroyed; + } m_listeners; + SP m_resource; + WP m_toplevel; + std::string m_handle; + + friend class CXDGForeignExporterProtocolV2; +}; + +// zxdg_imported_v2 +class CXDGImportedResourceV2 { + public: + CXDGImportedResourceV2(SP resource, SP exported, const std::string& handle); + ~CXDGImportedResourceV2(); + + private: + SP m_resource; + WP m_exported; + std::string m_handle; + + struct { + CHyprSignalListener exportedDestroyed; + } m_listeners; + + friend class CXDGForeignImporterProtocolV2; +}; + +class CXDGForeignExporterProtocolV2 : public IWaylandProtocol { + public: + CXDGForeignExporterProtocolV2(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) override; + + SP getExported(const std::string& handle) const; + + private: + void destroyExported(CXDGExportedResourceV2*); + void onExporterDestroyed(CZxdgExporterV2*); + std::vector> m_exporters; + std::unordered_map> m_exported; + + friend class CXDGExportedResourceV2; +}; + +class CXDGForeignImporterProtocolV2 : public IWaylandProtocol { + public: + CXDGForeignImporterProtocolV2(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) override; + + private: + void destroyImported(CXDGImportedResourceV2*); + void onImporterDestroyed(CZxdgImporterV2*); + std::vector> m_importers; + std::vector> m_imports; + + friend class CXDGImportedResourceV2; +}; + +namespace PROTO { + inline UP xdgForeignExporter; + inline UP xdgForeignImporter; +}; \ No newline at end of file diff --git a/src/protocols/XDGOutput.cpp b/src/protocols/XDGOutput.cpp index ccb78e987..3553a74b8 100644 --- a/src/protocols/XDGOutput.cpp +++ b/src/protocols/XDGOutput.cpp @@ -2,7 +2,7 @@ #include "../config/ConfigValue.hpp" #include "../helpers/Monitor.hpp" #include "../xwayland/XWayland.hpp" -#include "../managers/HookSystemManager.hpp" +#include "../event/EventBus.hpp" #include "core/Output.hpp" #define OUTPUT_MANAGER_VERSION 3 @@ -25,7 +25,7 @@ void CXDGOutputProtocol::bindManager(wl_client* client, void* data, uint32_t ver const auto RESOURCE = m_managerResources.emplace_back(makeUnique(client, ver, id)).get(); if UNLIKELY (!RESOURCE->resource()) { - LOGM(LOG, "Couldn't bind XDGOutputMgr"); + LOGM(Log::DEBUG, "Couldn't bind XDGOutputMgr"); wl_client_post_no_memory(client); return; } @@ -36,8 +36,8 @@ void CXDGOutputProtocol::bindManager(wl_client* client, void* data, uint32_t ver } CXDGOutputProtocol::CXDGOutputProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - static auto P = g_pHookSystem->hookDynamic("monitorLayoutChanged", [this](void* self, SCallbackInfo& info, std::any param) { this->updateAllOutputs(); }); - static auto P2 = g_pHookSystem->hookDynamic("configReloaded", [this](void* self, SCallbackInfo& info, std::any param) { this->updateAllOutputs(); }); + static auto P = Event::bus()->m_events.monitor.layoutChanged.listen([this] { updateAllOutputs(); }); + static auto P2 = Event::bus()->m_events.config.reloaded.listen([this] { updateAllOutputs(); }); } void CXDGOutputProtocol::onManagerGetXDGOutput(CZxdgOutputManagerV1* mgr, uint32_t id, wl_resource* outputResource) { @@ -61,11 +61,11 @@ void CXDGOutputProtocol::onManagerGetXDGOutput(CZxdgOutputManagerV1* mgr, uint32 } if UNLIKELY (!PMONITOR) { - LOGM(ERR, "New xdg_output from client {:x} ({}) has no CMonitor?!", (uintptr_t)CLIENT, pXDGOutput->m_isXWayland ? "xwayland" : "not xwayland"); + LOGM(Log::ERR, "New xdg_output from client {:x} ({}) has no CMonitor?!", (uintptr_t)CLIENT, pXDGOutput->m_isXWayland ? "xwayland" : "not xwayland"); return; } - LOGM(LOG, "New xdg_output for {}: client {:x} ({})", PMONITOR->m_name, (uintptr_t)CLIENT, pXDGOutput->m_isXWayland ? "xwayland" : "not xwayland"); + LOGM(Log::DEBUG, "New xdg_output for {}: client {:x} ({})", PMONITOR->m_name, (uintptr_t)CLIENT, pXDGOutput->m_isXWayland ? "xwayland" : "not xwayland"); const auto XDGVER = pXDGOutput->m_resource->version(); @@ -82,7 +82,7 @@ void CXDGOutputProtocol::onManagerGetXDGOutput(CZxdgOutputManagerV1* mgr, uint32 } void CXDGOutputProtocol::updateAllOutputs() { - LOGM(LOG, "updating all xdg_output heads"); + LOGM(Log::DEBUG, "updating all xdg_output heads"); for (auto const& o : m_xdgOutputs) { if (!o->m_monitor) diff --git a/src/protocols/XDGShell.cpp b/src/protocols/XDGShell.cpp index 58f297b99..774346d4b 100644 --- a/src/protocols/XDGShell.cpp +++ b/src/protocols/XDGShell.cpp @@ -7,7 +7,10 @@ #include "../helpers/Monitor.hpp" #include "core/Seat.hpp" #include "core/Compositor.hpp" +#include "../desktop/DesktopTypes.hpp" +#include "../desktop/view/Window.hpp" #include "protocols/core/Output.hpp" +#include #include #include @@ -48,7 +51,7 @@ CXDGPopupResource::CXDGPopupResource(SP resource_, SPsetReposition([this](CXdgPopup* r, wl_resource* positionerRes, uint32_t token) { - LOGM(LOG, "Popup {:x} asks for reposition", (uintptr_t)this); + LOGM(Log::DEBUG, "Popup {:x} asks for reposition", (uintptr_t)this); m_lastRepositionToken = token; auto pos = CXDGPositionerResource::fromResource(positionerRes); if (!pos) @@ -58,7 +61,7 @@ CXDGPopupResource::CXDGPopupResource(SP resource_, SPsetGrab([this](CXdgPopup* r, wl_resource* seat, uint32_t serial) { - LOGM(LOG, "xdg_popup {:x} requests grab", (uintptr_t)this); + LOGM(Log::DEBUG, "xdg_popup {:x} requests grab", (uintptr_t)this); PROTO::xdgShell->addOrStartGrab(m_self.lock()); }); @@ -76,7 +79,7 @@ void CXDGPopupResource::applyPositioning(const CBox& box, const Vector2D& t1coor m_geometry = m_positionerRules.getPosition(constraint, accumulateParentOffset() + t1coord); - LOGM(LOG, "Popup {:x} gets unconstrained to {} {}", (uintptr_t)this, m_geometry.pos(), m_geometry.size()); + LOGM(Log::DEBUG, "Popup {:x} gets unconstrained to {} {}", (uintptr_t)this, m_geometry.pos(), m_geometry.size()); configure(m_geometry); @@ -122,7 +125,7 @@ void CXDGPopupResource::repositioned() { if LIKELY (!m_lastRepositionToken) return; - LOGM(LOG, "repositioned: sending reposition token {}", m_lastRepositionToken); + LOGM(Log::DEBUG, "repositioned: sending reposition token {}", m_lastRepositionToken); m_resource->sendRepositioned(m_lastRepositionToken); m_lastRepositionToken = 0; @@ -217,48 +220,49 @@ CXDGToplevelResource::CXDGToplevelResource(SP resource_, SPsetSetParent([this](CXdgToplevel* r, wl_resource* parentR) { - auto oldParent = m_parent; - - if (m_parent) - std::erase(m_parent->m_children, m_self); - auto newp = parentR ? CXDGToplevelResource::fromResource(parentR) : nullptr; + setNewParent(newp); + }); +} - if (newp) { - // check for protocol constraints - if (newp == m_self) { - r->error(XDG_TOPLEVEL_ERROR_INVALID_PARENT, "Parent cannot be self"); - return; - } +void CXDGToplevelResource::setNewParent(SP newParent) { + auto oldParent = m_parent; - static std::function, WP)> exploreChildren = [](WP tl, - WP target) -> bool { - bool any = false; - for (const auto& c : tl->m_children) { - if (c == target) - return true; + if (m_parent) + std::erase(m_parent->m_children, m_self); - any = any || exploreChildren(c, target); - - if (any) - break; - } - return any; - }; - - if (exploreChildren(m_self, newp)) { - r->error(XDG_TOPLEVEL_ERROR_INVALID_PARENT, "Parent cannot be a descendant"); - return; - } + if (newParent) { + if (m_self == newParent) { + m_resource->error(XDG_TOPLEVEL_ERROR_INVALID_PARENT, "Parent cannot be self"); + return; } - m_parent = newp; + static std::function, WP)> exploreChildren = [](WP tl, WP target) -> bool { + bool any = false; + for (const auto& c : tl->m_children) { + if (c == target) + return true; - if (m_parent) - m_parent->m_children.emplace_back(m_self); + any = any || exploreChildren(c, target); - LOGM(LOG, "Toplevel {:x} sets parent to {:x}{}", (uintptr_t)this, (uintptr_t)newp.get(), (oldParent ? std::format(" (was {:x})", (uintptr_t)oldParent.get()) : "")); - }); + if (any) + break; + } + return any; + }; + if (exploreChildren(m_self, newParent)) { + m_resource->error(XDG_TOPLEVEL_ERROR_INVALID_PARENT, "Parent cannot be a descendant of itself"); + return; + } + } + + m_parent = newParent; + if (m_parent) { + m_parent->m_children.emplace_back(m_self); + if (m_parent->m_window && m_parent->m_window->m_pinned) + m_self->m_window->m_pinned = true; + } + LOGM(Log::DEBUG, "Toplevel {:x} sets parent to {:x}{}", (uintptr_t)this, (uintptr_t)newParent.get(), (oldParent ? std::format(" (was {:x})", (uintptr_t)oldParent.get()) : "")); } CXDGToplevelResource::~CXDGToplevelResource() { @@ -402,7 +406,7 @@ CXDGSurfaceResource::CXDGSurfaceResource(SP resource_, SPm_events.destroy.listen([this] { - LOGM(WARN, "wl_surface destroyed before its xdg_surface role object"); + LOGM(Log::WARN, "wl_surface destroyed before its xdg_surface role object"); m_listeners.surfaceDestroy.reset(); m_listeners.surfaceCommit.reset(); @@ -458,9 +462,12 @@ CXDGSurfaceResource::CXDGSurfaceResource(SP resource_, SPm_self = RESOURCE; - LOGM(LOG, "xdg_surface {:x} gets a toplevel {:x}", (uintptr_t)m_owner.get(), (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "xdg_surface {:x} gets a toplevel {:x}", (uintptr_t)m_owner.get(), (uintptr_t)RESOURCE.get()); - g_pCompositor->m_windows.emplace_back(CWindow::create(m_self.lock())); + PHLWINDOW createdWindow = g_pCompositor->m_windows.emplace_back(Desktop::View::CWindow::create(m_self.lock())); + + if (RESOURCE->m_parent && RESOURCE->m_parent->m_window->m_pinned) + createdWindow->m_pinned = true; for (auto const& p : m_popups) { if (!p) @@ -484,7 +491,7 @@ CXDGSurfaceResource::CXDGSurfaceResource(SP resource_, SPm_self = RESOURCE; - LOGM(LOG, "xdg_surface {:x} gets a popup {:x} owner {:x}", (uintptr_t)m_self.get(), (uintptr_t)RESOURCE.get(), (uintptr_t)parent.get()); + LOGM(Log::DEBUG, "xdg_surface {:x} gets a popup {:x} owner {:x}", (uintptr_t)m_self.get(), (uintptr_t)RESOURCE.get(), (uintptr_t)parent.get()); if (!parent) return; @@ -502,7 +509,7 @@ CXDGSurfaceResource::CXDGSurfaceResource(SP resource_, SPsetSetWindowGeometry([this](CXdgSurface* r, int32_t x, int32_t y, int32_t w, int32_t h) { - LOGM(LOG, "xdg_surface {:x} requests geometry {}x{} {}x{}", (uintptr_t)this, x, y, w, h); + LOGM(Log::DEBUG, "xdg_surface {:x} requests geometry {}x{} {}x{}", (uintptr_t)this, x, y, w, h); m_pending.geometry = {x, y, w, h}; }); } @@ -596,7 +603,7 @@ CXDGPositionerRules::CXDGPositionerRules(SP positioner) } CBox CXDGPositionerRules::getPosition(CBox constraint, const Vector2D& parentCoord) { - Debug::log(LOG, "GetPosition with constraint {} {} and parent {}", constraint.pos(), constraint.size(), parentCoord); + Log::logger->log(Log::DEBUG, "GetPosition with constraint {} {} and parent {}", constraint.pos(), constraint.size(), parentCoord); // padding constraint.expand(-4); @@ -742,7 +749,7 @@ CXDGWMBase::CXDGWMBase(SP resource_) : m_resource(resource_) { m_positioners.emplace_back(RESOURCE); - LOGM(LOG, "New xdg_positioner at {:x}", (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New xdg_positioner at {:x}", (uintptr_t)RESOURCE.get()); }); m_resource->setGetXdgSurface([this](CXdgWmBase* r, uint32_t id, wl_resource* surf) { @@ -773,7 +780,7 @@ CXDGWMBase::CXDGWMBase(SP resource_) : m_resource(resource_) { m_surfaces.emplace_back(RESOURCE); - LOGM(LOG, "New xdg_surface at {:x}", (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New xdg_surface at {:x}", (uintptr_t)RESOURCE.get()); }); m_resource->setPong([this](CXdgWmBase* r, uint32_t serial) { @@ -817,7 +824,7 @@ void CXDGShellProtocol::bindManager(wl_client* client, void* data, uint32_t ver, RESOURCE->m_self = RESOURCE; - LOGM(LOG, "New xdg_wm_base at {:x}", (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New xdg_wm_base at {:x}", (uintptr_t)RESOURCE.get()); } void CXDGShellProtocol::destroyResource(CXDGWMBase* resource) { diff --git a/src/protocols/XDGShell.hpp b/src/protocols/XDGShell.hpp index fbb6ba94a..3d94d2829 100644 --- a/src/protocols/XDGShell.hpp +++ b/src/protocols/XDGShell.hpp @@ -97,7 +97,6 @@ class CXDGToplevelResource { PHLWINDOWREF m_window; bool good(); - Vector2D layoutMinSize(); Vector2D layoutMaxSize(); @@ -107,6 +106,7 @@ class CXDGToplevelResource { uint32_t setFullscreen(bool fullscreen); uint32_t setActive(bool active); uint32_t setSuspeneded(bool sus); + void setNewParent(SP newParent); void close(); diff --git a/src/protocols/XDGTag.cpp b/src/protocols/XDGTag.cpp index 98c8651f3..84415d718 100644 --- a/src/protocols/XDGTag.cpp +++ b/src/protocols/XDGTag.cpp @@ -1,6 +1,6 @@ #include "XDGTag.hpp" #include "XDGShell.hpp" -#include "../desktop/Window.hpp" +#include "../desktop/view/Window.hpp" CXDGToplevelTagManagerResource::CXDGToplevelTagManagerResource(UP&& resource) : m_resource(std::move(resource)) { if UNLIKELY (!good()) diff --git a/src/protocols/XXColorManagement.cpp b/src/protocols/XXColorManagement.cpp deleted file mode 100644 index 8ec780b40..000000000 --- a/src/protocols/XXColorManagement.cpp +++ /dev/null @@ -1,666 +0,0 @@ -#include "XXColorManagement.hpp" -#include "../Compositor.hpp" -#include "ColorManagement.hpp" -#include "color-management-v1.hpp" -#include "types/ColorManagement.hpp" -#include "xx-color-management-v4.hpp" - -using namespace NColorManagement; - -static wpColorManagerV1TransferFunction getWPTransferFunction(xxColorManagerV4TransferFunction tf) { - switch (tf) { - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_BT709: - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_BT1361: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886; - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_GAMMA22: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22; - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_GAMMA28: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA28; - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_ST240: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST240; - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_LINEAR: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR; - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_LOG_100: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_100; - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_LOG_316: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_316; - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_XVYCC: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_XVYCC; - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_SRGB: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB; - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_EXT_SRGB: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_SRGB; - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_ST2084_PQ: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ; - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_ST428: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST428; - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_HLG: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_HLG; - default: UNREACHABLE(); - } -} - -static wpColorManagerV1Primaries getWPPrimaries(xxColorManagerV4Primaries primaries) { - return sc(primaries + 1); -} - -CXXColorManager::CXXColorManager(SP resource_) : m_resource(resource_) { - if UNLIKELY (!good()) - return; - - m_resource->sendSupportedFeature(XX_COLOR_MANAGER_V4_FEATURE_PARAMETRIC); - m_resource->sendSupportedFeature(XX_COLOR_MANAGER_V4_FEATURE_EXTENDED_TARGET_VOLUME); - m_resource->sendSupportedFeature(XX_COLOR_MANAGER_V4_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES); - m_resource->sendSupportedFeature(XX_COLOR_MANAGER_V4_FEATURE_SET_PRIMARIES); - m_resource->sendSupportedFeature(XX_COLOR_MANAGER_V4_FEATURE_SET_LUMINANCES); - - m_resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_SRGB); - m_resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_PAL_M); - m_resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_PAL); - m_resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_NTSC); - m_resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_GENERIC_FILM); - m_resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_BT2020); - // resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_CIE1931_XYZ); - m_resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_DCI_P3); - m_resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_DISPLAY_P3); - m_resource->sendSupportedPrimariesNamed(XX_COLOR_MANAGER_V4_PRIMARIES_ADOBE_RGB); - - m_resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_GAMMA22); - m_resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_GAMMA28); - m_resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_HLG); - m_resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_SRGB); - m_resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_ST2084_PQ); - m_resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_LINEAR); - m_resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_BT709); - m_resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_BT1361); - m_resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_ST240); - m_resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_LOG_100); - m_resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_LOG_316); - m_resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_XVYCC); - m_resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_EXT_SRGB); - m_resource->sendSupportedTfNamed(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_ST428); - - m_resource->sendSupportedIntent(XX_COLOR_MANAGER_V4_RENDER_INTENT_PERCEPTUAL); - // resource->sendSupportedIntent(XX_COLOR_MANAGER_V4_RENDER_INTENT_RELATIVE); - // resource->sendSupportedIntent(XX_COLOR_MANAGER_V4_RENDER_INTENT_ABSOLUTE); - // resource->sendSupportedIntent(XX_COLOR_MANAGER_V4_RENDER_INTENT_RELATIVE_BPC); - - m_resource->setDestroy([](CXxColorManagerV4* r) { LOGM(TRACE, "Destroy xx_color_manager at {:x} (generated default)", (uintptr_t)r); }); - m_resource->setGetOutput([](CXxColorManagerV4* r, uint32_t id, wl_resource* output) { - LOGM(TRACE, "Get output for id={}, output={}", id, (uintptr_t)output); - const auto RESOURCE = - PROTO::xxColorManagement->m_outputs.emplace_back(makeShared(makeShared(r->client(), r->version(), id))); - - if UNLIKELY (!RESOURCE->good()) { - r->noMemory(); - PROTO::xxColorManagement->m_outputs.pop_back(); - return; - } - - RESOURCE->m_self = RESOURCE; - }); - m_resource->setGetSurface([](CXxColorManagerV4* r, uint32_t id, wl_resource* surface) { - LOGM(TRACE, "Get surface for id={}, surface={}", id, (uintptr_t)surface); - auto SURF = CWLSurfaceResource::fromResource(surface); - - if (!SURF) { - LOGM(ERR, "No surface for resource {}", (uintptr_t)surface); - r->error(-1, "Invalid surface (2)"); - return; - } - - if (SURF->m_colorManagement) { - r->error(XX_COLOR_MANAGER_V4_ERROR_SURFACE_EXISTS, "CM Surface already exists"); - return; - } - - const auto RESOURCE = - PROTO::xxColorManagement->m_surfaces.emplace_back(makeShared(makeShared(r->client(), r->version(), id), SURF)); - if UNLIKELY (!RESOURCE->good()) { - r->noMemory(); - PROTO::xxColorManagement->m_surfaces.pop_back(); - return; - } - - RESOURCE->m_self = RESOURCE; - }); - m_resource->setGetFeedbackSurface([](CXxColorManagerV4* r, uint32_t id, wl_resource* surface) { - LOGM(TRACE, "Get feedback surface for id={}, surface={}", id, (uintptr_t)surface); - auto SURF = CWLSurfaceResource::fromResource(surface); - - if (!SURF) { - LOGM(ERR, "No surface for resource {}", (uintptr_t)surface); - r->error(-1, "Invalid surface (2)"); - return; - } - - const auto RESOURCE = PROTO::xxColorManagement->m_feedbackSurfaces.emplace_back( - makeShared(makeShared(r->client(), r->version(), id), SURF)); - - if UNLIKELY (!RESOURCE->good()) { - r->noMemory(); - PROTO::xxColorManagement->m_feedbackSurfaces.pop_back(); - return; - } - - RESOURCE->m_self = RESOURCE; - }); - m_resource->setNewIccCreator([](CXxColorManagerV4* r, uint32_t id) { - LOGM(WARN, "New ICC creator for id={} (unsupported)", id); - r->error(XX_COLOR_MANAGER_V4_ERROR_UNSUPPORTED_FEATURE, "ICC profiles are not supported"); - }); - m_resource->setNewParametricCreator([](CXxColorManagerV4* r, uint32_t id) { - LOGM(TRACE, "New parametric creator for id={}", id); - - const auto RESOURCE = PROTO::xxColorManagement->m_parametricCreators.emplace_back( - makeShared(makeShared(r->client(), r->version(), id))); - - if UNLIKELY (!RESOURCE->good()) { - r->noMemory(); - PROTO::xxColorManagement->m_parametricCreators.pop_back(); - return; - } - - RESOURCE->m_self = RESOURCE; - }); - - m_resource->setOnDestroy([this](CXxColorManagerV4* r) { PROTO::xxColorManagement->destroyResource(this); }); -} - -bool CXXColorManager::good() { - return m_resource->resource(); -} - -CXXColorManagementOutput::CXXColorManagementOutput(SP resource_) : m_resource(resource_) { - if UNLIKELY (!good()) - return; - - m_client = m_resource->client(); - - m_resource->setDestroy([this](CXxColorManagementOutputV4* r) { PROTO::xxColorManagement->destroyResource(this); }); - m_resource->setOnDestroy([this](CXxColorManagementOutputV4* r) { PROTO::xxColorManagement->destroyResource(this); }); - - m_resource->setGetImageDescription([this](CXxColorManagementOutputV4* r, uint32_t id) { - LOGM(TRACE, "Get image description for output={}, id={}", (uintptr_t)r, id); - if (m_imageDescription.valid()) - PROTO::xxColorManagement->destroyResource(m_imageDescription.get()); - - const auto RESOURCE = PROTO::xxColorManagement->m_imageDescriptions.emplace_back( - makeShared(makeShared(r->client(), r->version(), id), true)); - - if UNLIKELY (!RESOURCE->good()) { - r->noMemory(); - PROTO::xxColorManagement->m_imageDescriptions.pop_back(); - return; - } - - RESOURCE->m_self = RESOURCE; - }); -} - -bool CXXColorManagementOutput::good() { - return m_resource->resource(); -} - -wl_client* CXXColorManagementOutput::client() { - return m_client; -} - -CXXColorManagementSurface::CXXColorManagementSurface(SP surface_) : m_surface(surface_) { - // only for frog cm until wayland cm is adopted -} - -CXXColorManagementSurface::CXXColorManagementSurface(SP resource_, SP surface_) : m_surface(surface_), m_resource(resource_) { - if UNLIKELY (!good()) - return; - - m_client = m_resource->client(); - - if (!m_surface->m_colorManagement.valid()) { - const auto RESOURCE = PROTO::colorManagement->m_surfaces.emplace_back(makeShared(surface_)); - if UNLIKELY (!RESOURCE) { - m_resource->noMemory(); - PROTO::colorManagement->m_surfaces.pop_back(); - return; - } - - RESOURCE->m_self = RESOURCE; - - m_surface->m_colorManagement = RESOURCE; - - m_resource->setOnDestroy([this](CXxColorManagementSurfaceV4* r) { - LOGM(TRACE, "Destroy wp cm and xx cm for surface {}", (uintptr_t)m_surface); - if (m_surface.valid()) - PROTO::colorManagement->destroyResource(m_surface->m_colorManagement.get()); - PROTO::xxColorManagement->destroyResource(this); - }); - } else - m_resource->setOnDestroy([this](CXxColorManagementSurfaceV4* r) { - LOGM(TRACE, "Destroy xx cm surface {}", (uintptr_t)m_surface); - PROTO::xxColorManagement->destroyResource(this); - }); - - m_resource->setDestroy([this](CXxColorManagementSurfaceV4* r) { - LOGM(TRACE, "Destroy xx cm surface {}", (uintptr_t)m_surface); - PROTO::xxColorManagement->destroyResource(this); - }); - - m_resource->setSetImageDescription([this](CXxColorManagementSurfaceV4* r, wl_resource* image_description, uint32_t render_intent) { - LOGM(TRACE, "Set image description for surface={}, desc={}, intent={}", (uintptr_t)r, (uintptr_t)image_description, render_intent); - - const auto PO = sc(wl_resource_get_user_data(image_description)); - if (!PO) { // FIXME check validity - r->error(XX_COLOR_MANAGEMENT_SURFACE_V4_ERROR_IMAGE_DESCRIPTION, "Image description creation failed"); - return; - } - if (render_intent != XX_COLOR_MANAGER_V4_RENDER_INTENT_PERCEPTUAL) { - r->error(XX_COLOR_MANAGEMENT_SURFACE_V4_ERROR_RENDER_INTENT, "Unsupported render intent"); - return; - } - - const auto imageDescription = - std::ranges::find_if(PROTO::xxColorManagement->m_imageDescriptions, [&](const auto& other) { return other->resource()->resource() == image_description; }); - if (imageDescription == PROTO::xxColorManagement->m_imageDescriptions.end()) { - r->error(XX_COLOR_MANAGEMENT_SURFACE_V4_ERROR_IMAGE_DESCRIPTION, "Image description not found"); - return; - } - - if (m_surface.valid()) { - m_surface->m_colorManagement->m_imageDescription = imageDescription->get()->m_settings; - m_surface->m_colorManagement->setHasImageDescription(true); - } else - LOGM(ERR, "Set image description for invalid surface"); - }); - m_resource->setUnsetImageDescription([this](CXxColorManagementSurfaceV4* r) { - LOGM(TRACE, "Unset image description for surface={}", (uintptr_t)r); - if (m_surface.valid()) { - m_surface->m_colorManagement->m_imageDescription = SImageDescription{}; - m_surface->m_colorManagement->setHasImageDescription(false); - } else - LOGM(ERR, "Unset image description for invalid surface"); - }); -} - -bool CXXColorManagementSurface::good() { - return m_resource && m_resource->resource(); -} - -wl_client* CXXColorManagementSurface::client() { - return m_client; -} - -const SImageDescription& CXXColorManagementSurface::imageDescription() { - if (!hasImageDescription()) - LOGM(WARN, "Reading imageDescription while none set. Returns default or empty values"); - return m_imageDescription; -} - -bool CXXColorManagementSurface::hasImageDescription() { - return m_hasImageDescription; -} - -void CXXColorManagementSurface::setHasImageDescription(bool has) { - m_hasImageDescription = has; - m_needsNewMetadata = true; -} - -const hdr_output_metadata& CXXColorManagementSurface::hdrMetadata() { - return m_hdrMetadata; -} - -void CXXColorManagementSurface::setHDRMetadata(const hdr_output_metadata& metadata) { - m_hdrMetadata = metadata; - m_needsNewMetadata = false; -} - -bool CXXColorManagementSurface::needsHdrMetadataUpdate() { - return m_needsNewMetadata; -} - -CXXColorManagementFeedbackSurface::CXXColorManagementFeedbackSurface(SP resource_, SP surface_) : - m_surface(surface_), m_resource(resource_) { - if UNLIKELY (!good()) - return; - - m_client = m_resource->client(); - - m_resource->setDestroy([this](CXxColorManagementFeedbackSurfaceV4* r) { - LOGM(TRACE, "Destroy xx cm feedback surface {}", (uintptr_t)m_surface); - if (m_currentPreferred.valid()) - PROTO::xxColorManagement->destroyResource(m_currentPreferred.get()); - PROTO::xxColorManagement->destroyResource(this); - }); - m_resource->setOnDestroy([this](CXxColorManagementFeedbackSurfaceV4* r) { - LOGM(TRACE, "Destroy xx cm feedback surface {}", (uintptr_t)m_surface); - if (m_currentPreferred.valid()) - PROTO::xxColorManagement->destroyResource(m_currentPreferred.get()); - PROTO::xxColorManagement->destroyResource(this); - }); - - m_resource->setGetPreferred([this](CXxColorManagementFeedbackSurfaceV4* r, uint32_t id) { - LOGM(TRACE, "Get preferred for id {}", id); - - if (m_currentPreferred.valid()) - PROTO::xxColorManagement->destroyResource(m_currentPreferred.get()); - - const auto RESOURCE = PROTO::xxColorManagement->m_imageDescriptions.emplace_back( - makeShared(makeShared(r->client(), r->version(), id), true)); - - if UNLIKELY (!RESOURCE->good()) { - r->noMemory(); - PROTO::xxColorManagement->m_imageDescriptions.pop_back(); - return; - } - - RESOURCE->m_self = RESOURCE; - m_currentPreferred = RESOURCE; - - m_currentPreferred->m_settings = g_pCompositor->getPreferredImageDescription(); - - RESOURCE->resource()->sendReady(id); - }); -} - -bool CXXColorManagementFeedbackSurface::good() { - return m_resource->resource(); -} - -wl_client* CXXColorManagementFeedbackSurface::client() { - return m_client; -} - -CXXColorManagementParametricCreator::CXXColorManagementParametricCreator(SP resource_) : m_resource(resource_) { - if UNLIKELY (!good()) - return; - // - m_client = m_resource->client(); - - m_resource->setOnDestroy([this](CXxImageDescriptionCreatorParamsV4* r) { PROTO::xxColorManagement->destroyResource(this); }); - - m_resource->setCreate([this](CXxImageDescriptionCreatorParamsV4* r, uint32_t id) { - LOGM(TRACE, "Create image description from params for id {}", id); - - // FIXME actually check completeness - if (!m_valuesSet) { - r->error(XX_IMAGE_DESCRIPTION_CREATOR_PARAMS_V4_ERROR_INCOMPLETE_SET, "Missing required settings"); - return; - } - - // FIXME actually check consistency - if (!m_valuesSet) { - r->error(XX_IMAGE_DESCRIPTION_CREATOR_PARAMS_V4_ERROR_INCONSISTENT_SET, "Set is not consistent"); - return; - } - - const auto RESOURCE = PROTO::xxColorManagement->m_imageDescriptions.emplace_back( - makeShared(makeShared(r->client(), r->version(), id), false)); - - if UNLIKELY (!RESOURCE->good()) { - r->noMemory(); - PROTO::xxColorManagement->m_imageDescriptions.pop_back(); - return; - } - - // FIXME actually check support - if (!m_valuesSet) { - RESOURCE->resource()->sendFailed(XX_IMAGE_DESCRIPTION_V4_CAUSE_UNSUPPORTED, "unsupported"); - return; - } - - RESOURCE->m_self = RESOURCE; - RESOURCE->m_settings = m_settings; - RESOURCE->resource()->sendReady(id); - - PROTO::xxColorManagement->destroyResource(this); - }); - m_resource->setSetTfNamed([this](CXxImageDescriptionCreatorParamsV4* r, uint32_t tf) { - LOGM(TRACE, "Set image description transfer function to {}", tf); - if (m_valuesSet & PC_TF) { - r->error(XX_IMAGE_DESCRIPTION_CREATOR_PARAMS_V4_ERROR_ALREADY_SET, "Transfer function already set"); - return; - } - - switch (tf) { - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_GAMMA22: - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_GAMMA28: - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_HLG: - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_SRGB: - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_ST2084_PQ: - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_LINEAR: - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_BT709: - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_BT1361: - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_ST240: - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_LOG_100: - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_LOG_316: - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_XVYCC: - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_EXT_SRGB: - case XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_ST428: break; - default: r->error(XX_IMAGE_DESCRIPTION_CREATOR_PARAMS_V4_ERROR_INVALID_TF, "Unsupported transfer function"); return; - } - - m_settings.transferFunction = convertTransferFunction(getWPTransferFunction(sc(tf))); - m_valuesSet |= PC_TF; - }); - m_resource->setSetTfPower([this](CXxImageDescriptionCreatorParamsV4* r, uint32_t eexp) { - LOGM(TRACE, "Set image description tf power to {}", eexp); - if (m_valuesSet & PC_TF_POWER) { - r->error(XX_IMAGE_DESCRIPTION_CREATOR_PARAMS_V4_ERROR_ALREADY_SET, "Transfer function power already set"); - return; - } - m_settings.transferFunctionPower = eexp / 10000.0f; - m_valuesSet |= PC_TF_POWER; - }); - m_resource->setSetPrimariesNamed([this](CXxImageDescriptionCreatorParamsV4* r, uint32_t primaries) { - LOGM(TRACE, "Set image description primaries by name {}", primaries); - if (m_valuesSet & PC_PRIMARIES) { - r->error(XX_IMAGE_DESCRIPTION_CREATOR_PARAMS_V4_ERROR_ALREADY_SET, "Primaries already set"); - return; - } - - switch (primaries) { - case XX_COLOR_MANAGER_V4_PRIMARIES_SRGB: - case XX_COLOR_MANAGER_V4_PRIMARIES_PAL_M: - case XX_COLOR_MANAGER_V4_PRIMARIES_PAL: - case XX_COLOR_MANAGER_V4_PRIMARIES_NTSC: - case XX_COLOR_MANAGER_V4_PRIMARIES_GENERIC_FILM: - case XX_COLOR_MANAGER_V4_PRIMARIES_BT2020: - case XX_COLOR_MANAGER_V4_PRIMARIES_DCI_P3: - case XX_COLOR_MANAGER_V4_PRIMARIES_DISPLAY_P3: - case XX_COLOR_MANAGER_V4_PRIMARIES_ADOBE_RGB: - m_settings.primariesNameSet = true; - m_settings.primariesNamed = convertPrimaries(getWPPrimaries(sc(primaries))); - m_settings.primaries = getPrimaries(m_settings.primariesNamed); - m_valuesSet |= PC_PRIMARIES; - break; - default: r->error(XX_IMAGE_DESCRIPTION_CREATOR_PARAMS_V4_ERROR_INVALID_PRIMARIES, "Unsupported primaries"); - } - }); - m_resource->setSetPrimaries( - [this](CXxImageDescriptionCreatorParamsV4* r, int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) { - LOGM(TRACE, "Set image description primaries by values r:{},{} g:{},{} b:{},{} w:{},{}", r_x, r_y, g_x, g_y, b_x, b_y, w_x, w_y); - if (m_valuesSet & PC_PRIMARIES) { - r->error(XX_IMAGE_DESCRIPTION_CREATOR_PARAMS_V4_ERROR_ALREADY_SET, "Primaries already set"); - return; - } - m_settings.primariesNameSet = false; - m_settings.primaries = SPCPRimaries{.red = {.x = r_x, .y = r_y}, .green = {.x = g_x, .y = g_y}, .blue = {.x = b_x, .y = b_y}, .white = {.x = w_x, .y = w_y}}; - m_valuesSet |= PC_PRIMARIES; - }); - m_resource->setSetLuminances([this](CXxImageDescriptionCreatorParamsV4* r, uint32_t min_lum, uint32_t max_lum, uint32_t reference_lum) { - auto min = min_lum / 10000.0f; - LOGM(TRACE, "Set image description luminances to {} - {} ({})", min, max_lum, reference_lum); - if (m_valuesSet & PC_LUMINANCES) { - r->error(XX_IMAGE_DESCRIPTION_CREATOR_PARAMS_V4_ERROR_ALREADY_SET, "Luminances already set"); - return; - } - if (max_lum < reference_lum || reference_lum <= min) { - r->error(XX_IMAGE_DESCRIPTION_CREATOR_PARAMS_V4_ERROR_INVALID_LUMINANCE, "Invalid luminances"); - return; - } - m_settings.luminances = SImageDescription::SPCLuminances{.min = min, .max = max_lum, .reference = reference_lum}; - m_valuesSet |= PC_LUMINANCES; - }); - m_resource->setSetMasteringDisplayPrimaries( - [this](CXxImageDescriptionCreatorParamsV4* r, int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) { - LOGM(TRACE, "Set image description mastering primaries by values r:{},{} g:{},{} b:{},{} w:{},{}", r_x, r_y, g_x, g_y, b_x, b_y, w_x, w_y); - // if (valuesSet & PC_MASTERING_PRIMARIES) { - // r->error(XX_IMAGE_DESCRIPTION_CREATOR_PARAMS_V4_ERROR_ALREADY_SET, "Mastering primaries already set"); - // return; - // } - m_settings.masteringPrimaries = SPCPRimaries{.red = {.x = r_x, .y = r_y}, .green = {.x = g_x, .y = g_y}, .blue = {.x = b_x, .y = b_y}, .white = {.x = w_x, .y = w_y}}; - m_valuesSet |= PC_MASTERING_PRIMARIES; - }); - m_resource->setSetMasteringLuminance([this](CXxImageDescriptionCreatorParamsV4* r, uint32_t min_lum, uint32_t max_lum) { - auto min = min_lum / 10000.0f; - LOGM(TRACE, "Set image description mastering luminances to {} - {}", min, max_lum); - // if (valuesSet & PC_MASTERING_LUMINANCES) { - // r->error(XX_IMAGE_DESCRIPTION_CREATOR_PARAMS_V4_ERROR_ALREADY_SET, "Mastering luminances already set"); - // return; - // } - if (min > 0 && max_lum > 0 && max_lum <= min) { - r->error(XX_IMAGE_DESCRIPTION_CREATOR_PARAMS_V4_ERROR_INVALID_LUMINANCE, "Invalid luminances"); - return; - } - m_settings.masteringLuminances = SImageDescription::SPCMasteringLuminances{.min = min, .max = max_lum}; - m_valuesSet |= PC_MASTERING_LUMINANCES; - }); - m_resource->setSetMaxCll([this](CXxImageDescriptionCreatorParamsV4* r, uint32_t max_cll) { - LOGM(TRACE, "Set image description max content light level to {}", max_cll); - // if (valuesSet & PC_CLL) { - // r->error(XX_IMAGE_DESCRIPTION_CREATOR_PARAMS_V4_ERROR_ALREADY_SET, "Max CLL already set"); - // return; - // } - m_settings.maxCLL = max_cll; - m_valuesSet |= PC_CLL; - }); - m_resource->setSetMaxFall([this](CXxImageDescriptionCreatorParamsV4* r, uint32_t max_fall) { - LOGM(TRACE, "Set image description max frame-average light level to {}", max_fall); - // if (valuesSet & PC_FALL) { - // r->error(XX_IMAGE_DESCRIPTION_CREATOR_PARAMS_V4_ERROR_ALREADY_SET, "Max FALL already set"); - // return; - // } - m_settings.maxFALL = max_fall; - m_valuesSet |= PC_FALL; - }); -} - -bool CXXColorManagementParametricCreator::good() { - return m_resource->resource(); -} - -wl_client* CXXColorManagementParametricCreator::client() { - return m_client; -} - -CXXColorManagementImageDescription::CXXColorManagementImageDescription(SP resource_, bool allowGetInformation) : - m_resource(resource_), m_allowGetInformation(allowGetInformation) { - if UNLIKELY (!good()) - return; - - m_client = m_resource->client(); - - m_resource->setDestroy([this](CXxImageDescriptionV4* r) { PROTO::xxColorManagement->destroyResource(this); }); - m_resource->setOnDestroy([this](CXxImageDescriptionV4* r) { PROTO::xxColorManagement->destroyResource(this); }); - - m_resource->setGetInformation([this](CXxImageDescriptionV4* r, uint32_t id) { - LOGM(TRACE, "Get image information for image={}, id={}", (uintptr_t)r, id); - if (!m_allowGetInformation) { - r->error(XX_IMAGE_DESCRIPTION_V4_ERROR_NO_INFORMATION, "Image descriptions doesn't allow get_information request"); - return; - } - - auto RESOURCE = makeShared(makeShared(r->client(), r->version(), id), m_settings); - - if UNLIKELY (!RESOURCE->good()) - r->noMemory(); - - // CXXColorManagementImageDescriptionInfo should send everything in the constructor and be ready for destroying at this point - RESOURCE.reset(); - }); -} - -bool CXXColorManagementImageDescription::good() { - return m_resource->resource(); -} - -wl_client* CXXColorManagementImageDescription::client() { - return m_client; -} - -SP CXXColorManagementImageDescription::resource() { - return m_resource; -} - -CXXColorManagementImageDescriptionInfo::CXXColorManagementImageDescriptionInfo(SP resource_, const SImageDescription& settings_) : - m_resource(resource_), m_settings(settings_) { - if UNLIKELY (!good()) - return; - - m_client = m_resource->client(); - - const auto toProto = [](float value) { return sc(std::round(value * 10000)); }; - - 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); - m_resource->sendLuminances(std::round(m_settings.luminances.min * 10000), m_settings.luminances.max, m_settings.luminances.reference); - - // send expected display paramateres - m_resource->sendTargetPrimaries(toProto(m_settings.masteringPrimaries.red.x), toProto(m_settings.masteringPrimaries.red.y), toProto(m_settings.masteringPrimaries.green.x), - toProto(m_settings.masteringPrimaries.green.y), toProto(m_settings.masteringPrimaries.blue.x), toProto(m_settings.masteringPrimaries.blue.y), - toProto(m_settings.masteringPrimaries.white.x), toProto(m_settings.masteringPrimaries.white.y)); - m_resource->sendTargetLuminance(std::round(m_settings.masteringLuminances.min * 10000), m_settings.masteringLuminances.max); - m_resource->sendTargetMaxCll(m_settings.maxCLL); - m_resource->sendTargetMaxFall(m_settings.maxFALL); - - m_resource->sendDone(); -} - -bool CXXColorManagementImageDescriptionInfo::good() { - return m_resource->resource(); -} - -wl_client* CXXColorManagementImageDescriptionInfo::client() { - return m_client; -} - -CXXColorManagementProtocol::CXXColorManagementProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - ; -} - -void CXXColorManagementProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { - const auto RESOURCE = m_managers.emplace_back(makeShared(makeShared(client, ver, id))); - - if UNLIKELY (!RESOURCE->good()) { - wl_client_post_no_memory(client); - m_managers.pop_back(); - return; - } - - LOGM(TRACE, "New xx_color_manager at {:x}", (uintptr_t)RESOURCE.get()); -} - -void CXXColorManagementProtocol::onImagePreferredChanged() { - for (auto const& feedback : m_feedbackSurfaces) { - feedback->m_resource->sendPreferredChanged(); - } -} - -void CXXColorManagementProtocol::destroyResource(CXXColorManager* resource) { - std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; }); -} - -void CXXColorManagementProtocol::destroyResource(CXXColorManagementOutput* resource) { - std::erase_if(m_outputs, [&](const auto& other) { return other.get() == resource; }); -} - -void CXXColorManagementProtocol::destroyResource(CXXColorManagementSurface* resource) { - std::erase_if(m_surfaces, [&](const auto& other) { return other.get() == resource; }); -} - -void CXXColorManagementProtocol::destroyResource(CXXColorManagementFeedbackSurface* resource) { - std::erase_if(m_feedbackSurfaces, [&](const auto& other) { return other.get() == resource; }); -} - -void CXXColorManagementProtocol::destroyResource(CXXColorManagementParametricCreator* resource) { - std::erase_if(m_parametricCreators, [&](const auto& other) { return other.get() == resource; }); -} - -void CXXColorManagementProtocol::destroyResource(CXXColorManagementImageDescription* resource) { - std::erase_if(m_imageDescriptions, [&](const auto& other) { return other.get() == resource; }); -} diff --git a/src/protocols/XXColorManagement.hpp b/src/protocols/XXColorManagement.hpp deleted file mode 100644 index 0407730af..000000000 --- a/src/protocols/XXColorManagement.hpp +++ /dev/null @@ -1,188 +0,0 @@ -#pragma once - -#include -#include -#include -#include "WaylandProtocol.hpp" -#include "core/Compositor.hpp" -#include "xx-color-management-v4.hpp" -#include "types/ColorManagement.hpp" - -class CXXColorManager; -class CXXColorManagementOutput; -class CXXColorManagementImageDescription; -class CXXColorManagementProtocol; - -class CXXColorManager { - public: - CXXColorManager(SP resource_); - - bool good(); - - private: - SP m_resource; -}; - -class CXXColorManagementOutput { - public: - CXXColorManagementOutput(SP resource_); - - bool good(); - wl_client* client(); - - WP m_self; - WP m_imageDescription; - - private: - SP m_resource; - wl_client* m_client = nullptr; - - friend class CXXColorManagementProtocol; - friend class CXXColorManagementImageDescription; -}; - -class CXXColorManagementSurface { - public: - CXXColorManagementSurface(SP surface_); // temporary interface for frog CM - CXXColorManagementSurface(SP resource_, SP surface_); - - bool good(); - wl_client* client(); - - WP m_self; - WP m_surface; - - const NColorManagement::SImageDescription& imageDescription(); - bool hasImageDescription(); - void setHasImageDescription(bool has); - const hdr_output_metadata& hdrMetadata(); - void setHDRMetadata(const hdr_output_metadata& metadata); - bool needsHdrMetadataUpdate(); - - private: - SP m_resource; - wl_client* m_client = nullptr; - NColorManagement::SImageDescription m_imageDescription; - bool m_hasImageDescription = false; - bool m_needsNewMetadata = false; - hdr_output_metadata m_hdrMetadata; - - friend class CFrogColorManagementSurface; -}; - -class CXXColorManagementFeedbackSurface { - public: - CXXColorManagementFeedbackSurface(SP resource_, SP surface_); - - bool good(); - wl_client* client(); - - WP m_self; - WP m_surface; - - private: - SP m_resource; - wl_client* m_client = nullptr; - - WP m_currentPreferred; - - friend class CXXColorManagementProtocol; -}; - -class CXXColorManagementParametricCreator { - public: - CXXColorManagementParametricCreator(SP resource_); - - bool good(); - wl_client* client(); - - WP m_self; - - NColorManagement::SImageDescription m_settings; - - private: - enum eValuesSet : uint32_t { // NOLINT - PC_TF = (1 << 0), - PC_TF_POWER = (1 << 1), - PC_PRIMARIES = (1 << 2), - PC_LUMINANCES = (1 << 3), - PC_MASTERING_PRIMARIES = (1 << 4), - PC_MASTERING_LUMINANCES = (1 << 5), - PC_CLL = (1 << 6), - PC_FALL = (1 << 7), - }; - - SP m_resource; - wl_client* m_client = nullptr; - uint32_t m_valuesSet = 0; // enum eValuesSet -}; - -class CXXColorManagementImageDescription { - public: - CXXColorManagementImageDescription(SP resource_, bool allowGetInformation); - - bool good(); - wl_client* client(); - SP resource(); - - WP m_self; - - NColorManagement::SImageDescription m_settings; - - private: - SP m_resource; - wl_client* m_client = nullptr; - bool m_allowGetInformation = false; - - friend class CXXColorManagementOutput; -}; - -class CXXColorManagementImageDescriptionInfo { - public: - CXXColorManagementImageDescriptionInfo(SP resource_, const NColorManagement::SImageDescription& settings_); - - bool good(); - wl_client* client(); - - private: - SP m_resource; - wl_client* m_client = nullptr; - NColorManagement::SImageDescription m_settings; -}; - -class CXXColorManagementProtocol : public IWaylandProtocol { - public: - CXXColorManagementProtocol(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 onImagePreferredChanged(); - - private: - void destroyResource(CXXColorManager* resource); - void destroyResource(CXXColorManagementOutput* resource); - void destroyResource(CXXColorManagementSurface* resource); - void destroyResource(CXXColorManagementFeedbackSurface* resource); - void destroyResource(CXXColorManagementParametricCreator* resource); - void destroyResource(CXXColorManagementImageDescription* resource); - - std::vector> m_managers; - std::vector> m_outputs; - std::vector> m_surfaces; - std::vector> m_feedbackSurfaces; - std::vector> m_parametricCreators; - std::vector> m_imageDescriptions; - - friend class CXXColorManager; - friend class CXXColorManagementOutput; - friend class CXXColorManagementSurface; - friend class CXXColorManagementFeedbackSurface; - friend class CXXColorManagementParametricCreator; - friend class CXXColorManagementImageDescription; - - friend class CFrogColorManagementSurface; -}; - -namespace PROTO { - inline UP xxColorManagement; -}; diff --git a/src/protocols/core/Compositor.cpp b/src/protocols/core/Compositor.cpp index 541eae928..293cd2858 100644 --- a/src/protocols/core/Compositor.cpp +++ b/src/protocols/core/Compositor.cpp @@ -277,25 +277,29 @@ void CWLSurfaceResource::enter(PHLMONITOR monitor) { if UNLIKELY (!PROTO::outputs.contains(monitor->m_name)) { // can happen on unplug/replug - LOGM(ERR, "enter() called on a non-existent output global"); + LOGM(Log::ERR, "enter() called on a non-existent output global"); return; } if UNLIKELY (PROTO::outputs.at(monitor->m_name)->isDefunct()) { - LOGM(ERR, "enter() called on a defunct output global"); + LOGM(Log::ERR, "enter() called on a defunct output global"); return; } - auto output = PROTO::outputs.at(monitor->m_name)->outputResourceFrom(m_client); + auto outputs = PROTO::outputs.at(monitor->m_name)->outputResourcesFrom(m_client); - if UNLIKELY (!output || !output->getResource() || !output->getResource()->resource()) { - LOGM(ERR, "Cannot enter surface {:x} to {}, client hasn't bound the output", (uintptr_t)this, monitor->m_name); + if UNLIKELY (outputs.empty() || std::ranges::all_of(outputs, [](const auto& o) { return !o->getResource() || !o->getResource()->resource(); })) { + LOGM(Log::ERR, "Cannot enter surface {:x} to {}, client hasn't bound the output", (uintptr_t)this, monitor->m_name); return; } m_enteredOutputs.emplace_back(monitor); - m_resource->sendEnter(output->getResource().get()); + for (const auto& o : outputs) { + if (!o->getResource() || !o->getResource()->resource()) + continue; + m_resource->sendEnter(o->getResource().get()); + } m_events.enter.emit(monitor); } @@ -303,16 +307,20 @@ void CWLSurfaceResource::leave(PHLMONITOR monitor) { if UNLIKELY (std::ranges::find(m_enteredOutputs, monitor) == m_enteredOutputs.end()) return; - auto output = PROTO::outputs.at(monitor->m_name)->outputResourceFrom(m_client); + auto outputs = PROTO::outputs.at(monitor->m_name)->outputResourcesFrom(m_client); - if UNLIKELY (!output) { - LOGM(ERR, "Cannot leave surface {:x} from {}, client hasn't bound the output", (uintptr_t)this, monitor->m_name); + if UNLIKELY (outputs.empty() || std::ranges::all_of(outputs, [](const auto& o) { return !o->getResource() || !o->getResource()->resource(); })) { + LOGM(Log::ERR, "Cannot leave surface {:x} from {}, client hasn't bound the output", (uintptr_t)this, monitor->m_name); return; } std::erase(m_enteredOutputs, monitor); - m_resource->sendLeave(output->getResource().get()); + for (const auto& o : outputs) { + if (!o->getResource() || !o->getResource()->resource()) + continue; + m_resource->sendLeave(o->getResource().get()); + } m_events.leave.emit(monitor); } @@ -350,13 +358,21 @@ void CWLSurfaceResource::bfHelper(std::vector> const& nod // first, gather all nodes below for (auto const& n : nodes) { std::erase_if(n->m_subsurfaces, [](const auto& e) { return e.expired(); }); + // subsurfaces is sorted lowest -> highest - for (auto const& c : n->m_subsurfaces) { - if (c->m_zIndex >= 0) - break; - if (c->m_surface.expired()) + for (auto const& subsurfaceRef : n->m_subsurfaces) { + const auto subsurface = subsurfaceRef.lock(); + if (!subsurface) continue; - nodes2.push_back(c->m_surface.lock()); + + if (subsurface->m_zIndex >= 0) + break; + + const auto surface = subsurface->m_surface.lock(); + if (!surface) + continue; + + nodes2.emplace_back(surface); } } @@ -369,19 +385,29 @@ void CWLSurfaceResource::bfHelper(std::vector> const& nod Vector2D offset = {}; if (n->m_role->role() == SURFACE_ROLE_SUBSURFACE) { auto subsurface = sc(n->m_role.get())->m_subsurface.lock(); - offset = subsurface->posRelativeToParent(); + if (subsurface) + offset = subsurface->posRelativeToParent(); } fn(n, offset, data); } for (auto const& n : nodes) { - for (auto const& c : n->m_subsurfaces) { - if (c->m_zIndex < 0) + std::erase_if(n->m_subsurfaces, [](const auto& e) { return e.expired(); }); + + for (auto const& subsurfaceRef : n->m_subsurfaces) { + const auto subsurface = subsurfaceRef.lock(); + if (!subsurface) continue; - if (c->m_surface.expired()) + + if (subsurface->m_zIndex < 0) continue; - nodes2.push_back(c->m_surface.lock()); + + const auto surface = subsurface->m_surface.lock(); + if (!surface) + continue; + + nodes2.emplace_back(surface); } } @@ -391,17 +417,26 @@ void CWLSurfaceResource::bfHelper(std::vector> const& nod void CWLSurfaceResource::breadthfirst(std::function, const Vector2D&, void*)> fn, void* data) { std::vector> surfs; - surfs.push_back(m_self.lock()); + surfs.emplace_back(m_self.lock()); bfHelper(surfs, fn, data); } SP CWLSurfaceResource::findFirstPreorderHelper(SP root, std::function)> fn) { if (fn(root)) return root; - for (auto const& sub : root->m_subsurfaces) { - if (sub.expired() || sub->m_surface.expired()) + + std::erase_if(root->m_subsurfaces, [](const auto& e) { return e.expired(); }); + + for (auto const& subsurfaceRef : root->m_subsurfaces) { + const auto subsurface = subsurfaceRef.lock(); + if (!subsurface) continue; - const auto found = findFirstPreorderHelper(sub->m_surface.lock(), fn); + + const auto surface = subsurface->m_surface.lock(); + if (!surface) + continue; + + const auto found = findFirstPreorderHelper(surface, fn); if (found) return found; } @@ -500,20 +535,32 @@ void CWLSurfaceResource::scheduleState(WP state) { if (state->updated.bits.acquire) { // wait on acquire point for this surface, from explicit sync protocol - state->acquire.addWaiter([state, whenReadable]() { whenReadable(state, LOCK_REASON_FENCE); }); + if (!state->acquire.addWaiter([state, whenReadable]() { whenReadable(state, LOCK_REASON_FENCE); })) { + Log::logger->log(Log::ERR, "Failed to addWaiter in CWLSurfaceResource::scheduleState"); + whenReadable(state, LOCK_REASON_FENCE); + } } else if (state->buffer && state->buffer->isSynchronous()) { // synchronous (shm) buffers can be read immediately - m_stateQueue.unlock(state); + m_stateQueue.unlock(state, LOCK_REASON_FENCE); } else if (state->buffer && state->buffer->m_syncFd.isValid()) { // async buffer and is dmabuf, then we can wait on implicit fences g_pEventLoopManager->doOnReadable(std::move(state->buffer->m_syncFd), [state, whenReadable]() { whenReadable(state, LOCK_REASON_FENCE); }); } else { // state commit without a buffer. - m_stateQueue.unlock(state); + m_stateQueue.tryProcess(); } } void CWLSurfaceResource::commitState(SSurfaceState& state) { + // TODO might be incorrect. needed for VRR with FIFO to avoid same buffer extra frames for second commit when it's used in this way: + // wp_fifo_v1#43.set_barrier() + // wp_fifo_v1#43.wait_barrier() + // wl_surface#3.commit() + // wp_fifo_v1#43.wait_barrier() + // wl_surface#3.commit() + if (!state.updated.all && m_mapped && state.fifoScheduled) + return; + auto lastTexture = m_current.texture; m_current.updateFrom(state); @@ -528,7 +575,7 @@ void CWLSurfaceResource::commitState(SSurfaceState& state) { } if (m_current.texture) - m_current.texture->m_transform = wlTransformToHyprutils(m_current.transform); + m_current.texture->m_transform = Math::wlTransformToHyprutils(m_current.transform); if (m_role->role() == SURFACE_ROLE_SUBSURFACE) { auto subsurface = sc(m_role.get())->m_subsurface.lock(); @@ -556,7 +603,13 @@ void CWLSurfaceResource::commitState(SSurfaceState& state) { dropCurrentBuffer(); } -SImageDescription CWLSurfaceResource::getPreferredImageDescription() { +PImageDescription CWLSurfaceResource::getPreferredImageDescription() { + static const auto PFORCE_HDR = CConfigValue("quirks:prefer_hdr"); + const auto WINDOW = m_hlSurface ? Desktop::View::CWindow::fromView(m_hlSurface->view()) : nullptr; + + if (*PFORCE_HDR == 1 || (*PFORCE_HDR == 2 && m_hlSurface && WINDOW && WINDOW->m_class == "gamescope")) + return g_pCompositor->getHDRImageDescription(); + auto parent = m_self; if (parent->m_role->role() == SURFACE_ROLE_SUBSURFACE) { auto subsurface = sc(parent->m_role.get())->m_subsurface.lock(); @@ -565,13 +618,14 @@ SImageDescription CWLSurfaceResource::getPreferredImageDescription() { WP monitor; if (parent->m_enteredOutputs.size() == 1) monitor = parent->m_enteredOutputs[0]; - else if (m_hlSurface.valid() && m_hlSurface->getWindow()) - monitor = m_hlSurface->getWindow()->m_monitor; + else if (m_hlSurface.valid() && WINDOW) + monitor = WINDOW->m_monitor; return monitor ? monitor->m_imageDescription : g_pCompositor->getPreferredImageDescription(); } void CWLSurfaceResource::sortSubsurfaces() { + std::erase_if(m_subsurfaces, [](const auto& subsurface) { return !subsurface; }); std::ranges::sort(m_subsurfaces, [](const auto& a, const auto& b) { return a->m_zIndex < b->m_zIndex; }); // find the first non-negative index. We will preserve negativity: e.g. -2, -1, 1, 2 @@ -608,6 +662,30 @@ bool CWLSurfaceResource::hasVisibleSubsurface() { return false; } +bool CWLSurfaceResource::isTearing() { + if (m_enteredOutputs.empty() && m_hlSurface) { + for (auto& m : g_pCompositor->m_monitors) { + if (!m || !m->m_enabled) + continue; + + auto box = m_hlSurface->getSurfaceBoxGlobal(); + if (box && !box->intersection({m->m_position, m->m_size}).empty()) { + if (m->m_tearingState.activelyTearing) + return true; + } + } + } else { + for (auto& m : m_enteredOutputs) { + if (!m) + continue; + + if (m->m_tearingState.activelyTearing) + return true; + } + } + return false; +} + void CWLSurfaceResource::updateCursorShm(CRegion damage) { if (damage.empty()) return; @@ -621,7 +699,7 @@ void CWLSurfaceResource::updateCursorShm(CRegion damage) { auto shmAttrs = buf->shm(); if (!shmAttrs.success) { - LOGM(TRACE, "updateCursorShm: ignoring, not a shm buffer"); + LOGM(Log::TRACE, "updateCursorShm: ignoring, not a shm buffer"); return; } @@ -648,12 +726,19 @@ void CWLSurfaceResource::updateCursorShm(CRegion damage) { void CWLSurfaceResource::presentFeedback(const Time::steady_tp& when, PHLMONITOR pMonitor, bool discarded) { frame(when); + auto FEEDBACK = makeUnique(m_self.lock()); 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)); } @@ -675,7 +760,7 @@ CWLCompositorResource::CWLCompositorResource(SP resource_) : m_re RESOURCE->m_self = RESOURCE; RESOURCE->m_stateQueue = CSurfaceStateQueue(RESOURCE); - LOGM(LOG, "New wl_surface with id {} at {:x}", id, (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New wl_surface with id {} at {:x}", id, (uintptr_t)RESOURCE.get()); PROTO::compositor->m_events.newSurface.emit(RESOURCE); }); @@ -691,7 +776,7 @@ CWLCompositorResource::CWLCompositorResource(SP resource_) : m_re RESOURCE->m_self = RESOURCE; - LOGM(LOG, "New wl_region with id {} at {:x}", id, (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New wl_region with id {} at {:x}", id, (uintptr_t)RESOURCE.get()); }); } @@ -726,7 +811,12 @@ void CWLCompositorProtocol::destroyResource(CWLRegionResource* resource) { } void CWLCompositorProtocol::forEachSurface(std::function)> fn) { - for (auto& surf : m_surfaces) { + const auto surfaces = m_surfaces; + + for (auto& surf : surfaces) { + if (!surf) + continue; + fn(surf); } } diff --git a/src/protocols/core/Compositor.hpp b/src/protocols/core/Compositor.hpp index 0dc03cf47..37ca51b73 100644 --- a/src/protocols/core/Compositor.hpp +++ b/src/protocols/core/Compositor.hpp @@ -15,17 +15,17 @@ #include "../../render/Texture.hpp" #include "../types/SurfaceStateQueue.hpp" #include "wayland.hpp" +#include "../../desktop/view/WLSurface.hpp" #include "../../helpers/signal/Signal.hpp" #include "../../helpers/math/Math.hpp" #include "../../helpers/time/Time.hpp" #include "../types/Buffer.hpp" -#include "../types/ColorManagement.hpp" +#include "../../helpers/cm/ColorManagement.hpp" #include "../types/SurfaceRole.hpp" #include "../types/SurfaceState.hpp" class CWLOutputResource; class CMonitor; -class CWLSurface; class CWLSurfaceResource; class CWLSubsurfaceResource; class CViewportResource; @@ -33,7 +33,6 @@ class CDRMSyncobjSurfaceResource; class CFifoResource; class CCommitTimerResource; class CColorManagementSurface; -class CFrogColorManagementSurface; class CContentType; class CWLCallbackResource { @@ -109,7 +108,7 @@ class CWLSurfaceResource { CSurfaceStateQueue m_stateQueue; WP m_self; - WP m_hlSurface; + WP m_hlSurface; std::vector m_enteredOutputs; bool m_mapped = false; std::vector> m_subsurfaces; @@ -126,9 +125,10 @@ class CWLSurfaceResource { void presentFeedback(const Time::steady_tp& when, PHLMONITOR pMonitor, bool discarded = false); void scheduleState(WP state); void commitState(SSurfaceState& state); - NColorManagement::SImageDescription getPreferredImageDescription(); + 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 b3239cf1a..84c318883 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_) { @@ -26,16 +26,16 @@ CWLDataOfferResource::CWLDataOfferResource(SP resource_, SPsetAccept([this](CWlDataOffer* r, uint32_t serial, const char* mime) { if (!m_source) { - LOGM(WARN, "Possible bug: Accept on an offer w/o a source"); + LOGM(Log::WARN, "Possible bug: Accept on an offer w/o a source"); return; } if (m_dead) { - LOGM(WARN, "Possible bug: Accept on an offer that's dead"); + LOGM(Log::WARN, "Possible bug: Accept on an offer that's dead"); return; } - LOGM(LOG, "Offer {:x} accepts data from source {:x} with mime {}", (uintptr_t)this, (uintptr_t)m_source.get(), mime ? mime : "null"); + LOGM(Log::DEBUG, "Offer {:x} accepts data from source {:x} with mime {}", (uintptr_t)this, (uintptr_t)m_source.get(), mime ? mime : "null"); m_source->accepted(mime ? mime : ""); m_accepted = mime; @@ -44,19 +44,19 @@ CWLDataOfferResource::CWLDataOfferResource(SP resource_, SPsetReceive([this](CWlDataOffer* r, const char* mime, int fd) { CFileDescriptor sendFd{fd}; if (!m_source) { - LOGM(WARN, "Possible bug: Receive on an offer w/o a source"); + LOGM(Log::WARN, "Possible bug: Receive on an offer w/o a source"); return; } if (m_dead) { - LOGM(WARN, "Possible bug: Receive on an offer that's dead"); + LOGM(Log::WARN, "Possible bug: Receive on an offer that's dead"); return; } - LOGM(LOG, "Offer {:x} asks to send data from source {:x}", (uintptr_t)this, (uintptr_t)m_source.get()); + LOGM(Log::DEBUG, "Offer {:x} asks to send data from source {:x}", (uintptr_t)this, (uintptr_t)m_source.get()); if (!m_accepted) { - LOGM(WARN, "Offer was never accepted, sending accept first"); + LOGM(Log::WARN, "Offer was never accepted, sending accept first"); m_source->accepted(mime ? mime : ""); } @@ -101,13 +101,13 @@ void CWLDataOfferResource::sendData() { else if (SOURCEACTIONS & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) m_resource->sendAction(WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY); else { - LOGM(ERR, "Client bug? dnd source has no action move or copy. Sending move, f this."); + LOGM(Log::ERR, "Client bug? dnd source has no action move or copy. Sending move, f this."); m_resource->sendAction(WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE); } } for (auto const& m : m_source->mimes()) { - LOGM(LOG, " | offer {:x} supports mime {}", (uintptr_t)this, m); + LOGM(Log::DEBUG, " | offer {:x} supports mime {}", (uintptr_t)this, m); m_resource->sendOffer(m.c_str()); } } @@ -147,7 +147,7 @@ CWLDataSourceResource::CWLDataSourceResource(SP resource_, SPsetOffer([this](CWlDataSource* r, const char* mime) { m_mimeTypes.emplace_back(mime); }); m_resource->setSetActions([this](CWlDataSource* r, uint32_t a) { - LOGM(LOG, "DataSource {:x} actions {}", (uintptr_t)this, a); + LOGM(Log::DEBUG, "DataSource {:x} actions {}", (uintptr_t)this, a); m_supportedActions = a; }); } @@ -173,7 +173,7 @@ void CWLDataSourceResource::accepted(const std::string& mime) { } if (std::ranges::find(m_mimeTypes, mime) == m_mimeTypes.end()) { - LOGM(ERR, "Compositor/App bug: CWLDataSourceResource::sendAccepted with non-existent mime"); + LOGM(Log::ERR, "Compositor/App bug: CWLDataSourceResource::sendAccepted with non-existent mime"); return; } @@ -186,7 +186,7 @@ std::vector CWLDataSourceResource::mimes() { void CWLDataSourceResource::send(const std::string& mime, CFileDescriptor fd) { if (std::ranges::find(m_mimeTypes, mime) == m_mimeTypes.end()) { - LOGM(ERR, "Compositor/App bug: CWLDataSourceResource::sendAskSend with non-existent mime"); + LOGM(Log::ERR, "Compositor/App bug: CWLDataSourceResource::sendAskSend with non-existent mime"); return; } @@ -248,13 +248,13 @@ CWLDataDeviceResource::CWLDataDeviceResource(SP resource_) : m_re m_resource->setSetSelection([](CWlDataDevice* r, wl_resource* sourceR, uint32_t serial) { auto source = sourceR ? CWLDataSourceResource::fromResource(sourceR) : CSharedPointer{}; if (!source) { - LOGM(LOG, "Reset selection received"); + LOGM(Log::DEBUG, "Reset selection received"); g_pSeatManager->setCurrentSelection(nullptr); return; } if (source && source->m_used) - LOGM(WARN, "setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this."); + LOGM(Log::WARN, "setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this."); source->markUsed(); @@ -264,12 +264,12 @@ CWLDataDeviceResource::CWLDataDeviceResource(SP resource_) : m_re m_resource->setStartDrag([](CWlDataDevice* r, wl_resource* sourceR, wl_resource* origin, wl_resource* icon, uint32_t serial) { auto source = CWLDataSourceResource::fromResource(sourceR); if (!source) { - LOGM(ERR, "No source in drag"); + LOGM(Log::ERR, "No source in drag"); return; } if (source && source->m_used) - LOGM(WARN, "setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this."); + LOGM(Log::WARN, "setSelection on a used resource. By protocol, this is a violation, but firefox et al insist on doing this."); source->markUsed(); @@ -298,10 +298,17 @@ void CWLDataDeviceResource::sendDataOffer(SP offer) { void CWLDataDeviceResource::sendEnter(uint32_t serial, SP surf, const Vector2D& local, SP offer) { if (const auto WL = offer->getWayland(); WL) m_resource->sendEnterRaw(serial, surf->getResource()->resource(), wl_fixed_from_double(local.x), wl_fixed_from_double(local.y), WL->m_resource->resource()); + + m_entered = surf; + // FIXME: X11 } void CWLDataDeviceResource::sendLeave() { + if (!m_entered) + return; + + m_entered.reset(); m_resource->sendLeave(); } @@ -350,13 +357,13 @@ CWLDataDeviceManagerResource::CWLDataDeviceManagerResource(SPm_self = RESOURCE; m_sources.emplace_back(RESOURCE); - LOGM(LOG, "New data source bound at {:x}", (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New data source bound at {:x}", (uintptr_t)RESOURCE.get()); }); m_resource->setGetDataDevice([this](CWlDataDeviceManager* r, uint32_t id, wl_resource* seat) { @@ -376,7 +383,7 @@ CWLDataDeviceManagerResource::CWLDataDeviceManagerResource(SPm_device = RESOURCE; } - LOGM(LOG, "New data device bound at {:x}", (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New data device bound at {:x}", (uintptr_t)RESOURCE.get()); }); } @@ -400,7 +407,7 @@ void CWLDataDeviceProtocol::bindManager(wl_client* client, void* data, uint32_t return; } - LOGM(LOG, "New datamgr resource bound at {:x}", (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New datamgr resource bound at {:x}", (uintptr_t)RESOURCE.get()); } void CWLDataDeviceProtocol::destroyResource(CWLDataDeviceManagerResource* seat) { @@ -456,11 +463,11 @@ void CWLDataDeviceProtocol::sendSelectionToDevice(SP dev, SPtype() == DATA_SOURCE_TYPE_WAYLAND ? "wayland" : "X11", (uintptr_t)offer.get(), (uintptr_t)sel.get()); + LOGM(Log::DEBUG, "New {} offer {:x} for data source {:x}", offer->type() == DATA_SOURCE_TYPE_WAYLAND ? "wayland" : "X11", (uintptr_t)offer.get(), (uintptr_t)sel.get()); dev->sendDataOffer(offer); if (const auto WL = offer->getWayland(); WL) @@ -481,7 +488,7 @@ void CWLDataDeviceProtocol::setSelection(SP source) { } if (!source) { - LOGM(LOG, "resetting selection"); + LOGM(Log::DEBUG, "resetting selection"); if (!g_pSeatManager->m_state.keyboardFocusResource) return; @@ -493,7 +500,7 @@ void CWLDataDeviceProtocol::setSelection(SP source) { return; } - LOGM(LOG, "New selection for data source {:x}", (uintptr_t)source.get()); + LOGM(Log::DEBUG, "New selection for data source {:x}", (uintptr_t)source.get()); if (!g_pSeatManager->m_state.keyboardFocusResource) return; @@ -501,12 +508,12 @@ void CWLDataDeviceProtocol::setSelection(SP source) { auto DESTDEVICE = dataDeviceForClient(g_pSeatManager->m_state.keyboardFocusResource->client()); if (!DESTDEVICE) { - LOGM(LOG, "CWLDataDeviceProtocol::setSelection: cannot send selection to a client without a data_device"); + LOGM(Log::DEBUG, "CWLDataDeviceProtocol::setSelection: cannot send selection to a client without a data_device"); return; } if (DESTDEVICE->type() != DATA_SOURCE_TYPE_WAYLAND) { - LOGM(LOG, "CWLDataDeviceProtocol::setSelection: ignoring X11 data device"); + LOGM(Log::DEBUG, "CWLDataDeviceProtocol::setSelection: ignoring X11 data device"); return; } @@ -520,7 +527,7 @@ void CWLDataDeviceProtocol::updateSelection() { auto DESTDEVICE = dataDeviceForClient(g_pSeatManager->m_state.keyboardFocusResource->client()); if (!DESTDEVICE) { - LOGM(LOG, "CWLDataDeviceProtocol::onKeyboardFocus: cannot send selection to a client without a data_device"); + LOGM(Log::DEBUG, "CWLDataDeviceProtocol::onKeyboardFocus: cannot send selection to a client without a data_device"); return; } @@ -550,14 +557,14 @@ void CWLDataDeviceProtocol::onDndPointerFocus() { void CWLDataDeviceProtocol::initiateDrag(WP currentSource, SP dragSurface, SP origin) { if (m_dnd.currentSource) { - LOGM(WARN, "New drag started while old drag still active??"); + LOGM(Log::WARN, "New drag started while old drag still active??"); abortDrag(); } Cursor::overrideController->setOverride("grabbing", Cursor::CURSOR_OVERRIDE_DND); m_dnd.overriddenCursor = true; - LOGM(LOG, "initiateDrag: source {:x}, surface: {:x}, origin: {:x}", (uintptr_t)currentSource.get(), (uintptr_t)dragSurface, (uintptr_t)origin); + LOGM(Log::DEBUG, "initiateDrag: source {:x}, surface: {:x}, origin: {:x}", (uintptr_t)currentSource.get(), (uintptr_t)dragSurface, (uintptr_t)origin); currentSource->m_used = true; @@ -579,31 +586,28 @@ void CWLDataDeviceProtocol::initiateDrag(WP currentSource }); } - m_dnd.mouseButton = g_pHookSystem->hookDynamic("mouseButton", [this](void* self, SCallbackInfo& info, std::any e) { - auto E = std::any_cast(e); - if (E.state == WL_POINTER_BUTTON_STATE_RELEASED) { - LOGM(LOG, "Dropping drag on mouseUp"); + m_dnd.mouseButton = Event::bus()->m_events.input.mouse.button.listen([this](IPointer::SButtonEvent e, Event::SCallbackInfo&) { + if (e.state == WL_POINTER_BUTTON_STATE_RELEASED) { + LOGM(Log::DEBUG, "Dropping drag on mouseUp"); dropDrag(); } }); - m_dnd.touchUp = g_pHookSystem->hookDynamic("touchUp", [this](void* self, SCallbackInfo& info, std::any e) { - LOGM(LOG, "Dropping drag on touchUp"); + m_dnd.touchUp = Event::bus()->m_events.input.touch.up.listen([this](ITouch::SUpEvent e, Event::SCallbackInfo&) { + LOGM(Log::DEBUG, "Dropping drag on touchUp"); dropDrag(); }); - m_dnd.tabletTip = g_pHookSystem->hookDynamic("tabletTip", [this](void* self, SCallbackInfo& info, std::any e) { - auto E = std::any_cast(e); - if (!E.in) { - LOGM(LOG, "Dropping drag on tablet tipUp"); + m_dnd.tabletTip = Event::bus()->m_events.input.tablet.tip.listen([this](CTablet::STipEvent e, Event::SCallbackInfo&) { + if (!e.in) { + LOGM(Log::DEBUG, "Dropping drag on tablet tipUp"); dropDrag(); } }); - m_dnd.mouseMove = g_pHookSystem->hookDynamic("mouseMove", [this](void* self, SCallbackInfo& info, std::any e) { - auto V = std::any_cast(e); + m_dnd.mouseMove = Event::bus()->m_events.input.mouse.move.listen([this](Vector2D pos, Event::SCallbackInfo&) { if (m_dnd.focusedDevice && g_pSeatManager->m_state.dndPointerFocus) { - auto surf = CWLSurface::fromResource(g_pSeatManager->m_state.dndPointerFocus.lock()); + auto surf = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.dndPointerFocus.lock()); if (!surf) return; @@ -613,15 +617,14 @@ void CWLDataDeviceProtocol::initiateDrag(WP currentSource if (!box.has_value()) return; - m_dnd.focusedDevice->sendMotion(Time::millis(Time::steadyNow()), V - box->pos()); - LOGM(LOG, "Drag motion {}", V - box->pos()); + m_dnd.focusedDevice->sendMotion(Time::millis(Time::steadyNow()), pos - box->pos()); + LOGM(Log::DEBUG, "Drag motion {}", pos - box->pos()); } }); - m_dnd.touchMove = g_pHookSystem->hookDynamic("touchMove", [this](void* self, SCallbackInfo& info, std::any e) { - auto E = std::any_cast(e); + m_dnd.touchMove = Event::bus()->m_events.input.touch.motion.listen([this](ITouch::SMotionEvent e, Event::SCallbackInfo&) { if (m_dnd.focusedDevice && g_pSeatManager->m_state.dndPointerFocus) { - auto surf = CWLSurface::fromResource(g_pSeatManager->m_state.dndPointerFocus.lock()); + auto surf = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.dndPointerFocus.lock()); if (!surf) return; @@ -631,8 +634,8 @@ void CWLDataDeviceProtocol::initiateDrag(WP currentSource if (!box.has_value()) return; - m_dnd.focusedDevice->sendMotion(E.timeMs, E.pos); - LOGM(LOG, "Drag motion {}", E.pos); + m_dnd.focusedDevice->sendMotion(e.timeMs, e.pos); + LOGM(Log::DEBUG, "Drag motion {}", e.pos); } }); @@ -654,10 +657,11 @@ void CWLDataDeviceProtocol::updateDrag() { if (m_dnd.focusedDevice) m_dnd.focusedDevice->sendLeave(); - if (!g_pSeatManager->m_state.dndPointerFocus) + auto surface = g_pSeatManager->m_state.dndPointerFocus.lock(); + if (!surface) return; - m_dnd.focusedDevice = dataDeviceForClient(g_pSeatManager->m_state.dndPointerFocus->client()); + m_dnd.focusedDevice = dataDeviceForClient(surface->client()); if (!m_dnd.focusedDevice) return; @@ -682,18 +686,17 @@ void CWLDataDeviceProtocol::updateDrag() { #endif if (!offer) { - LOGM(ERR, "No offer could be created in updateDrag"); + LOGM(Log::ERR, "No offer could be created in updateDrag"); return; } - LOGM(LOG, "New {} dnd offer {:x} for data source {:x}", offer->type() == DATA_SOURCE_TYPE_WAYLAND ? "wayland" : "X11", (uintptr_t)offer.get(), + LOGM(Log::DEBUG, "New {} dnd offer {:x} for data source {:x}", offer->type() == DATA_SOURCE_TYPE_WAYLAND ? "wayland" : "X11", (uintptr_t)offer.get(), (uintptr_t)m_dnd.currentSource.get()); m_dnd.focusedDevice->sendDataOffer(offer); if (const auto WL = offer->getWayland(); WL) WL->sendData(); - m_dnd.focusedDevice->sendEnter(wl_display_next_serial(g_pCompositor->m_wlDisplay), g_pSeatManager->m_state.dndPointerFocus.lock(), - g_pSeatManager->m_state.dndPointerFocus->m_current.size / 2.F, offer); + m_dnd.focusedDevice->sendEnter(wl_display_next_serial(g_pCompositor->m_wlDisplay), surface, surface->m_current.size / 2.F, offer); } void CWLDataDeviceProtocol::cleanupDndState(bool resetDevice, bool resetSource, bool simulateInput) { diff --git a/src/protocols/core/DataDevice.hpp b/src/protocols/core/DataDevice.hpp index 8b52933e3..f3717f78e 100644 --- a/src/protocols/core/DataDevice.hpp +++ b/src/protocols/core/DataDevice.hpp @@ -111,8 +111,10 @@ class CWLDataDeviceResource : public IDataDevice { WP m_self; private: - SP m_resource; - wl_client* m_client = nullptr; + SP m_resource; + wl_client* m_client = nullptr; + + WP m_entered; friend class CWLDataDeviceProtocol; }; @@ -176,11 +178,11 @@ class CWLDataDeviceProtocol : public IWaylandProtocol { CHyprSignalListener dndSurfaceCommit; // for ending a dnd - SP mouseMove; - SP mouseButton; - SP touchUp; - SP touchMove; - SP tabletTip; + CHyprSignalListener mouseMove; + CHyprSignalListener mouseButton; + CHyprSignalListener touchUp; + CHyprSignalListener touchMove; + CHyprSignalListener tabletTip; } m_dnd; void abortDrag(); diff --git a/src/protocols/core/Output.cpp b/src/protocols/core/Output.cpp index d0f057e3d..9278eb955 100644 --- a/src/protocols/core/Output.cpp +++ b/src/protocols/core/Output.cpp @@ -30,8 +30,12 @@ CWLOutputResource::CWLOutputResource(SP resource_, PHLMONITOR pMonito updateState(); - PROTO::compositor->forEachSurface([](SP surf) { - auto HLSurf = CWLSurface::fromResource(surf); + const auto PMONITOR = m_monitor.lock(); + if (!PMONITOR) + return; + + PROTO::compositor->forEachSurface([PMONITOR](SP surf) { + auto HLSurf = Desktop::View::CWLSurface::fromResource(surf); if (!HLSurf) return; @@ -41,12 +45,10 @@ CWLOutputResource::CWLOutputResource(SP resource_, PHLMONITOR pMonito if (!GEOMETRY.has_value()) return; - for (auto& m : g_pCompositor->m_monitors) { - if (!m->logicalBox().expand(-4).overlaps(*GEOMETRY)) - continue; + if (!PMONITOR->logicalBox().expand(-4).overlaps(*GEOMETRY)) + return; - surf->enter(m); - } + surf->enter(PMONITOR); }); } @@ -95,7 +97,7 @@ CWLOutputProtocol::CWLOutputProtocol(const wl_interface* iface, const int& ver, void CWLOutputProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { if UNLIKELY (m_defunct) - Debug::log(WARN, "[wl_output] Binding a wl_output that's inert?? Possible client bug."); + Log::logger->log(Log::WARN, "[wl_output] Binding a wl_output that's inert?? Possible client bug."); const auto RESOURCE = m_outputs.emplace_back(makeShared(makeShared(client, ver, id), m_monitor.lock())); @@ -117,15 +119,17 @@ void CWLOutputProtocol::destroyResource(CWLOutputResource* resource) { PROTO::outputs.erase(m_name); } -SP CWLOutputProtocol::outputResourceFrom(wl_client* client) { +std::vector> CWLOutputProtocol::outputResourcesFrom(wl_client* client) { + std::vector> ret; + for (auto const& r : m_outputs) { if (r->client() != client) continue; - return r; + ret.emplace_back(r); } - return nullptr; + return ret; } void CWLOutputProtocol::remove() { diff --git a/src/protocols/core/Output.hpp b/src/protocols/core/Output.hpp index d0c6fb13f..cf2666856 100644 --- a/src/protocols/core/Output.hpp +++ b/src/protocols/core/Output.hpp @@ -34,13 +34,13 @@ class CWLOutputProtocol : public IWaylandProtocol { public: CWLOutputProtocol(const wl_interface* iface, const int& ver, const std::string& name, PHLMONITOR pMonitor); - virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); - SP outputResourceFrom(wl_client* client); - void sendDone(); + std::vector> outputResourcesFrom(wl_client* client); + void sendDone(); - PHLMONITORREF m_monitor; - WP m_self; + PHLMONITORREF m_monitor; + WP m_self; // will mark the protocol for removal, will be removed when no. of bound outputs is 0 (or when overwritten by a new global) void remove(); diff --git a/src/protocols/core/Seat.cpp b/src/protocols/core/Seat.cpp index 74e5615e1..202010aa8 100644 --- a/src/protocols/core/Seat.cpp +++ b/src/protocols/core/Seat.cpp @@ -118,7 +118,7 @@ CWLPointerResource::CWLPointerResource(SP resource_, SPsetSetCursor([this](CWlPointer* r, uint32_t serial, wl_resource* surf, int32_t hotX, int32_t hotY) { if (!m_owner) { - LOGM(ERR, "Client bug: setCursor when seatClient is already dead"); + LOGM(Log::ERR, "Client bug: setCursor when seatClient is already dead"); return; } @@ -137,8 +137,14 @@ CWLPointerResource::CWLPointerResource(SP resource_, SPonSetCursor(m_owner.lock(), serial, surfResource, {hotX, hotY}); }); - if (g_pSeatManager->m_state.pointerFocus && g_pSeatManager->m_state.pointerFocus->client() == m_resource->client()) - sendEnter(g_pSeatManager->m_state.pointerFocus.lock(), {-1, -1} /* Coords don't really matter that much, they will be updated next move */); + auto surface = g_pSeatManager->m_state.pointerFocus.lock(); + + if (surface && surface->client() == m_resource->client()) + sendEnter(surface, {-1, -1}); +} + +CWLPointerResource::~CWLPointerResource() { + m_events.destroyed.emit(); } int CWLPointerResource::version() { @@ -162,7 +168,7 @@ void CWLPointerResource::sendEnter(SP surface, const Vector2 return; if (m_currentSurface) { - LOGM(WARN, "requested CWLPointerResource::sendEnter without sendLeave first."); + LOGM(Log::WARN, "requested CWLPointerResource::sendEnter without sendLeave first."); sendLeave(); } @@ -218,10 +224,10 @@ void CWLPointerResource::sendButton(uint32_t timeMs, uint32_t button, wl_pointer return; if (state == WL_POINTER_BUTTON_STATE_RELEASED && std::ranges::find(m_pressedButtons, button) == m_pressedButtons.end()) { - LOGM(ERR, "sendButton release on a non-pressed button"); + LOGM(Log::ERR, "sendButton release on a non-pressed button"); return; } else if (state == WL_POINTER_BUTTON_STATE_PRESSED && std::ranges::find(m_pressedButtons, button) != m_pressedButtons.end()) { - LOGM(ERR, "sendButton press on a non-pressed button"); + LOGM(Log::ERR, "sendButton press on a non-pressed button"); return; } @@ -328,7 +334,7 @@ CWLKeyboardResource::CWLKeyboardResource(SP resource_, SPsetOnDestroy([this](CWlKeyboard* r) { PROTO::seat->destroyResource(this); }); if (!g_pSeatManager->m_keyboard) { - LOGM(ERR, "No keyboard on bound wl_keyboard??"); + LOGM(Log::ERR, "No keyboard on bound wl_keyboard??"); return; } @@ -380,7 +386,7 @@ void CWLKeyboardResource::sendEnter(SP surface, wl_array* ke return; if (m_currentSurface) { - LOGM(WARN, "requested CWLKeyboardResource::sendEnter without sendLeave first."); + LOGM(Log::WARN, "requested CWLKeyboardResource::sendEnter without sendLeave first."); sendLeave(); } @@ -531,7 +537,7 @@ void CWLSeatProtocol::bindManager(wl_client* client, void* data, uint32_t ver, u RESOURCE->m_self = RESOURCE; - LOGM(LOG, "New seat resource bound at {:x}", (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New seat resource bound at {:x}", (uintptr_t)RESOURCE.get()); m_events.newSeatResource.emit(RESOURCE); } diff --git a/src/protocols/core/Seat.hpp b/src/protocols/core/Seat.hpp index c30bbd718..85dc5c39f 100644 --- a/src/protocols/core/Seat.hpp +++ b/src/protocols/core/Seat.hpp @@ -71,6 +71,7 @@ class CWLTouchResource { class CWLPointerResource { public: CWLPointerResource(SP resource_, SP owner_); + ~CWLPointerResource(); bool good(); int version(); @@ -88,6 +89,10 @@ class CWLPointerResource { WP m_owner; + struct { + CSignalT<> destroyed; + } m_events; + // static SP fromResource(wl_resource* res); diff --git a/src/protocols/core/Shm.cpp b/src/protocols/core/Shm.cpp index 43c087bc2..660458ab7 100644 --- a/src/protocols/core/Shm.cpp +++ b/src/protocols/core/Shm.cpp @@ -7,6 +7,9 @@ #include "../types/WLBuffer.hpp" #include "../../helpers/Format.hpp" #include "../../render/Renderer.hpp" +#include + +using namespace Hyprgraphics::Egl; using namespace Hyprutils::OS; CWLSHMBuffer::CWLSHMBuffer(WP pool_, uint32_t id, int32_t offset_, const Vector2D& size_, int32_t stride_, uint32_t fmt_) { @@ -16,14 +19,12 @@ CWLSHMBuffer::CWLSHMBuffer(WP pool_, uint32_t id, int32_t of if UNLIKELY (!pool_->m_pool->m_data) return; - g_pHyprRenderer->makeEGLCurrent(); - size = size_; m_pool = pool_->m_pool; m_stride = stride_; m_fmt = fmt_; m_offset = offset_; - m_opaque = NFormatUtils::isFormatOpaque(NFormatUtils::shmToDRM(fmt_)); + m_opaque = isDrmFormatOpaque(NFormatUtils::shmToDRM(fmt_)); m_resource = CWLBufferResource::create(makeShared(pool_->m_resource->client(), 1, id)); @@ -87,7 +88,7 @@ CSHMPool::~CSHMPool() { } void CSHMPool::resize(size_t size_) { - LOGM(LOG, "Resizing a SHM pool from {} to {}", m_size, size_); + LOGM(Log::DEBUG, "Resizing a SHM pool from {} to {}", m_size, size_); if (m_data != MAP_FAILED) munmap(m_data, m_size); @@ -96,13 +97,13 @@ void CSHMPool::resize(size_t size_) { m_data = mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd.get(), 0); if UNLIKELY (m_data == MAP_FAILED) - LOGM(ERR, "Couldn't mmap {} bytes from fd {} of shm client", m_size, m_fd.get()); + LOGM(Log::ERR, "Couldn't mmap {} bytes from fd {} of shm client", m_size, m_fd.get()); } static int shmIsSizeValid(CFileDescriptor& fd, size_t size) { struct stat st; if UNLIKELY (fstat(fd.get(), &st) == -1) { - LOGM(ERR, "Couldn't get stat for fd {} of shm client", fd.get()); + LOGM(Log::ERR, "Couldn't get stat for fd {} of shm client", fd.get()); return 0; } @@ -176,6 +177,7 @@ CWLSHMResource::CWLSHMResource(UP&& resource_) : m_resource(std::move(re if UNLIKELY (!good()) return; + m_resource->setRelease([this](CWlShm* r) { PROTO::shm->destroyResource(this); }); m_resource->setOnDestroy([this](CWlShm* r) { PROTO::shm->destroyResource(this); }); m_resource->setCreatePool([](CWlShm* r, uint32_t id, int32_t fd, int32_t size) { diff --git a/src/protocols/core/Subcompositor.cpp b/src/protocols/core/Subcompositor.cpp index c198052c4..d8a3e8608 100644 --- a/src/protocols/core/Subcompositor.cpp +++ b/src/protocols/core/Subcompositor.cpp @@ -91,12 +91,15 @@ CWLSubsurfaceResource::CWLSubsurfaceResource(SP resource_, SPresetRole(); } void CWLSubsurfaceResource::destroy() { + unlinkFromParent(); + if (m_surface && m_surface->m_mapped) { m_surface->m_events.unmap.emit(); m_surface->unmap(); @@ -105,6 +108,14 @@ void CWLSubsurfaceResource::destroy() { PROTO::subcompositor->destroyResource(this); } +void CWLSubsurfaceResource::unlinkFromParent() { + const auto PARENT = m_parent.lock(); + if (!PARENT) + return; + + std::erase_if(PARENT->m_subsurfaces, [this](const auto& subsurface) { return !subsurface || subsurface.get() == this; }); +} + Vector2D CWLSubsurfaceResource::posRelativeToParent() { Vector2D pos = m_position; SP surf = m_parent.lock(); @@ -186,7 +197,7 @@ CWLSubcompositorResource::CWLSubcompositorResource(SP resource SURF->m_role = makeShared(RESOURCE); PARENT->m_subsurfaces.emplace_back(RESOURCE); - LOGM(LOG, "New wl_subsurface with id {} at {:x}", id, (uintptr_t)RESOURCE.get()); + LOGM(Log::DEBUG, "New wl_subsurface with id {} at {:x}", id, (uintptr_t)RESOURCE.get()); PARENT->m_events.newSubsurface.emit(RESOURCE); }); diff --git a/src/protocols/core/Subcompositor.hpp b/src/protocols/core/Subcompositor.hpp index 45b84eba4..6c46c2e65 100644 --- a/src/protocols/core/Subcompositor.hpp +++ b/src/protocols/core/Subcompositor.hpp @@ -55,6 +55,7 @@ class CWLSubsurfaceResource { SP m_resource; void destroy(); + void unlinkFromParent(); struct { CHyprSignalListener commitSurface; diff --git a/src/protocols/types/Buffer.cpp b/src/protocols/types/Buffer.cpp index 86b37be05..93bd5d209 100644 --- a/src/protocols/types/Buffer.cpp +++ b/src/protocols/types/Buffer.cpp @@ -31,7 +31,7 @@ void IHLBuffer::onBackendRelease(const std::function& fn) { if (m_hlEvents.backendRelease) { if (m_backendReleaseQueuedFn) m_backendReleaseQueuedFn(); - Debug::log(LOG, "backendRelease emitted early"); + Log::logger->log(Log::DEBUG, "backendRelease emitted early"); } m_backendReleaseQueuedFn = fn; @@ -59,6 +59,10 @@ CHLBufferReference::CHLBufferReference(const CHLBufferReference& other) : m_buff m_buffer->lock(); } +CHLBufferReference::CHLBufferReference(CHLBufferReference&& other) noexcept : m_buffer(std::move(other.m_buffer)) { + ; +} + CHLBufferReference::CHLBufferReference(SP buffer_) : m_buffer(buffer_) { if (m_buffer) m_buffer->lock(); @@ -70,6 +74,9 @@ CHLBufferReference::~CHLBufferReference() { } CHLBufferReference& CHLBufferReference::operator=(const CHLBufferReference& other) { + if (m_buffer == other.m_buffer) + return *this; // same buffer, do nothing + if (other.m_buffer) other.m_buffer->lock(); if (m_buffer) @@ -78,6 +85,16 @@ CHLBufferReference& CHLBufferReference::operator=(const CHLBufferReference& othe return *this; } +CHLBufferReference& CHLBufferReference::operator=(CHLBufferReference&& other) { + if (this != &other) { + if (m_buffer) + m_buffer->unlock(); + m_buffer = other.m_buffer; + other.m_buffer = nullptr; + } + return *this; +} + bool CHLBufferReference::operator==(const CHLBufferReference& other) const { return m_buffer == other.m_buffer; } diff --git a/src/protocols/types/Buffer.hpp b/src/protocols/types/Buffer.hpp index f85670ef8..927cd2591 100644 --- a/src/protocols/types/Buffer.hpp +++ b/src/protocols/types/Buffer.hpp @@ -26,7 +26,7 @@ class IHLBuffer : public Aquamarine::IBuffer { void onBackendRelease(const std::function& fn); void addReleasePoint(CDRMSyncPointState& point); - SP m_texture; + SP m_texture; bool m_opaque = false; SP m_resource; std::vector> m_syncReleasers; @@ -49,10 +49,13 @@ class CHLBufferReference { public: CHLBufferReference(); CHLBufferReference(const CHLBufferReference& other); + CHLBufferReference(CHLBufferReference&& other) noexcept; CHLBufferReference(SP buffer); ~CHLBufferReference(); CHLBufferReference& operator=(const CHLBufferReference& other); + CHLBufferReference& operator=(CHLBufferReference&& other); + bool operator==(const CHLBufferReference& other) const; bool operator==(const SP& other) const; bool operator==(const SP& other) const; diff --git a/src/protocols/types/ColorManagement.cpp b/src/protocols/types/ColorManagement.cpp deleted file mode 100644 index e6b470d0a..000000000 --- a/src/protocols/types/ColorManagement.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "ColorManagement.hpp" -#include - -namespace NColorManagement { - static uint32_t lastImageID = 0; - static std::map knownDescriptionIds; // expected to be small - - 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::DEFAULT_PRIMARIES; // FIXME - 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; - } - } - - // TODO make image descriptions immutable and always set an id - - uint32_t SImageDescription::findId() const { - for (auto it = knownDescriptionIds.begin(); it != knownDescriptionIds.end(); ++it) { - if (it->second == *this) - return it->first; - } - - const auto newId = ++lastImageID; - knownDescriptionIds.insert(std::make_pair(newId, *this)); - return newId; - } - - uint32_t SImageDescription::getId() const { - return id > 0 ? id : findId(); - } - - uint32_t SImageDescription::updateId() { - id = 0; - id = findId(); - return id; - } -} \ No newline at end of file diff --git a/src/protocols/types/ColorManagement.hpp b/src/protocols/types/ColorManagement.hpp deleted file mode 100644 index 80cea49f3..000000000 --- a/src/protocols/types/ColorManagement.hpp +++ /dev/null @@ -1,235 +0,0 @@ -#pragma once - -#include "color-management-v1.hpp" -#include -#include "../../helpers/memory/Memory.hpp" - -#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 - -namespace NColorManagement { - enum eNoShader : uint8_t { - CM_NS_DISABLE = 0, - CM_NS_ALWAYS = 1, - CM_NS_ONDEMAND = 2, - CM_NS_IGNORE = 3, - }; - - enum ePrimaries : uint8_t { - CM_PRIMARIES_SRGB = 1, - CM_PRIMARIES_PAL_M = 2, - CM_PRIMARIES_PAL = 3, - CM_PRIMARIES_NTSC = 4, - CM_PRIMARIES_GENERIC_FILM = 5, - CM_PRIMARIES_BT2020 = 6, - CM_PRIMARIES_CIE1931_XYZ = 7, - CM_PRIMARIES_DCI_P3 = 8, - CM_PRIMARIES_DISPLAY_P3 = 9, - CM_PRIMARIES_ADOBE_RGB = 10, - }; - - enum eTransferFunction : uint8_t { - CM_TRANSFER_FUNCTION_BT1886 = 1, - CM_TRANSFER_FUNCTION_GAMMA22 = 2, - CM_TRANSFER_FUNCTION_GAMMA28 = 3, - CM_TRANSFER_FUNCTION_ST240 = 4, - CM_TRANSFER_FUNCTION_EXT_LINEAR = 5, - CM_TRANSFER_FUNCTION_LOG_100 = 6, - CM_TRANSFER_FUNCTION_LOG_316 = 7, - CM_TRANSFER_FUNCTION_XVYCC = 8, - CM_TRANSFER_FUNCTION_SRGB = 9, - CM_TRANSFER_FUNCTION_EXT_SRGB = 10, - CM_TRANSFER_FUNCTION_ST2084_PQ = 11, - CM_TRANSFER_FUNCTION_ST428 = 12, - CM_TRANSFER_FUNCTION_HLG = 13, - }; - - // NOTE should be ok this way. unsupported primaries/tfs must be rejected earlier. supported enum values should be in sync with proto. - // might need a proper switch-case and additional INVALID enum value. - inline wpColorManagerV1Primaries convertPrimaries(ePrimaries primaries) { - return sc(primaries); - } - inline ePrimaries convertPrimaries(wpColorManagerV1Primaries primaries) { - return sc(primaries); - } - inline wpColorManagerV1TransferFunction convertTransferFunction(eTransferFunction tf) { - return sc(tf); - } - inline eTransferFunction convertTransferFunction(wpColorManagerV1TransferFunction tf) { - return sc(tf); - } - - using SPCPRimaries = Hyprgraphics::SPCPRimaries; - - namespace NColorPrimaries { - static const auto DEFAULT_PRIMARIES = SPCPRimaries{}; - - static const auto BT709 = SPCPRimaries{ - .red = {.x = 0.64, .y = 0.33}, - .green = {.x = 0.30, .y = 0.60}, - .blue = {.x = 0.15, .y = 0.06}, - .white = {.x = 0.3127, .y = 0.3290}, - }; - static const auto PAL_M = SPCPRimaries{ - .red = {.x = 0.67, .y = 0.33}, - .green = {.x = 0.21, .y = 0.71}, - .blue = {.x = 0.14, .y = 0.08}, - .white = {.x = 0.310, .y = 0.316}, - }; - static const auto PAL = SPCPRimaries{ - .red = {.x = 0.640, .y = 0.330}, - .green = {.x = 0.290, .y = 0.600}, - .blue = {.x = 0.150, .y = 0.060}, - .white = {.x = 0.3127, .y = 0.3290}, - }; - static const auto NTSC = SPCPRimaries{ - .red = {.x = 0.630, .y = 0.340}, - .green = {.x = 0.310, .y = 0.595}, - .blue = {.x = 0.155, .y = 0.070}, - .white = {.x = 0.3127, .y = 0.3290}, - }; - static const auto GENERIC_FILM = SPCPRimaries{ - .red = {.x = 0.243, .y = 0.692}, - .green = {.x = 0.145, .y = 0.049}, - .blue = {.x = 0.681, .y = 0.319}, // NOLINT(modernize-use-std-numbers) - .white = {.x = 0.310, .y = 0.316}, - }; - static const auto BT2020 = SPCPRimaries{ - .red = {.x = 0.708, .y = 0.292}, - .green = {.x = 0.170, .y = 0.797}, - .blue = {.x = 0.131, .y = 0.046}, - .white = {.x = 0.3127, .y = 0.3290}, - }; - - // FIXME CIE1931_XYZ - - static const auto DCI_P3 = SPCPRimaries{ - .red = {.x = 0.680, .y = 0.320}, - .green = {.x = 0.265, .y = 0.690}, - .blue = {.x = 0.150, .y = 0.060}, - .white = {.x = 0.314, .y = 0.351}, - }; - - static const auto DISPLAY_P3 = SPCPRimaries{ - .red = {.x = 0.680, .y = 0.320}, - .green = {.x = 0.265, .y = 0.690}, - .blue = {.x = 0.150, .y = 0.060}, - .white = {.x = 0.3127, .y = 0.3290}, - }; - static const auto ADOBE_RGB = SPCPRimaries{ - .red = {.x = 0.6400, .y = 0.3300}, - .green = {.x = 0.2100, .y = 0.7100}, - .blue = {.x = 0.1500, .y = 0.0600}, - .white = {.x = 0.3127, .y = 0.3290}, - }; - } - - const SPCPRimaries& getPrimaries(ePrimaries name); - - struct SImageDescription { - uint32_t id = 0; // FIXME needs id setting - - struct SIccFile { - int fd = -1; - uint32_t length = 0; - uint32_t offset = 0; - bool operator==(const SIccFile& i2) const { - return fd == i2.fd; - } - } icc; - - bool windowsScRGB = false; - - eTransferFunction transferFunction = CM_TRANSFER_FUNCTION_SRGB; - float transferFunctionPower = 1.0f; - - 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 - // xx protocol expects int32_t values multiplied by 10000 - // drm expects uint16_t values multiplied by 50000 - // frog protocol expects drm values - SPCPRimaries primaries, masteringPrimaries; - - // luminances in cd/m² - // protos and drm expect min * 10000 - struct SPCLuminances { - float min = 0.2; // 0.2 cd/m² - uint32_t max = 80; // 80 cd/m² - uint32_t reference = 80; // 80 cd/m² - bool operator==(const SPCLuminances& l2) const { - return min == l2.min && max == l2.max && reference == l2.reference; - } - } luminances; - struct SPCMasteringLuminances { - float min = 0; - uint32_t max = 0; - bool operator==(const SPCMasteringLuminances& l2) const { - return min == l2.min && max == l2.max; - } - } masteringLuminances; - - uint32_t maxCLL = 0; - uint32_t maxFALL = 0; - - bool operator==(const SImageDescription& d2) const { - return (id != 0 && id == d2.id) || - (icc == d2.icc && windowsScRGB == d2.windowsScRGB && transferFunction == d2.transferFunction && transferFunctionPower == d2.transferFunctionPower && - ((primariesNameSet && primariesNamed == d2.primariesNameSet) || (primaries == d2.primaries)) && masteringPrimaries == d2.masteringPrimaries && - luminances == d2.luminances && masteringLuminances == d2.masteringLuminances && maxCLL == d2.maxCLL && maxFALL == d2.maxFALL); - } - - const SPCPRimaries& getPrimaries() const { - if (primariesNameSet || primaries == SPCPRimaries{}) - return NColorManagement::getPrimaries(primariesNamed); - return primaries; - } - - float getTFMinLuminance(float sdrMinLuminance = -1.0f) const { - switch (transferFunction) { - 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_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: - case CM_TRANSFER_FUNCTION_XVYCC: - case CM_TRANSFER_FUNCTION_EXT_SRGB: - case CM_TRANSFER_FUNCTION_ST428: - case CM_TRANSFER_FUNCTION_SRGB: - default: return sdrMinLuminance >= 0 ? sdrMinLuminance : SDR_MIN_LUMINANCE; - } - }; - - float getTFMaxLuminance(int sdrMaxLuminance = -1) const { - switch (transferFunction) { - case CM_TRANSFER_FUNCTION_EXT_LINEAR: - 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_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: - case CM_TRANSFER_FUNCTION_XVYCC: - case CM_TRANSFER_FUNCTION_EXT_SRGB: - case CM_TRANSFER_FUNCTION_ST428: - case CM_TRANSFER_FUNCTION_SRGB: - default: return sdrMaxLuminance >= 0 ? sdrMaxLuminance : SDR_MAX_LUMINANCE; - } - }; - - uint32_t findId() const; - uint32_t getId() const; - uint32_t updateId(); - }; -} \ No newline at end of file diff --git a/src/protocols/types/ContentType.cpp b/src/protocols/types/ContentType.cpp index b5b0041c5..e2e58f3e5 100644 --- a/src/protocols/types/ContentType.cpp +++ b/src/protocols/types/ContentType.cpp @@ -1,6 +1,7 @@ #include "ContentType.hpp" +#include "debug/log/Logger.hpp" +#include #include -#include #include namespace NContentType { @@ -8,6 +9,13 @@ namespace NContentType { {"none", CONTENT_TYPE_NONE}, {"photo", CONTENT_TYPE_PHOTO}, {"video", CONTENT_TYPE_VIDEO}, {"game", CONTENT_TYPE_GAME}}; eContentType fromString(const std::string name) { + if (Hyprutils::String::isNumber(name)) { + try { + auto n = std::stoi(name); + if (n >= 0 && n <= 3) + return sc(n); + } catch (std::exception& e) { Log::logger->log(Log::ERR, "NContentType::fromString: invalid number {}, need to be between 0 and 3", name); } + } auto it = table.find(name); if (it != table.end()) return it->second; @@ -42,4 +50,4 @@ namespace NContentType { default: return DRM_MODE_CONTENT_TYPE_NO_DATA; } } -} \ No newline at end of file +} diff --git a/src/protocols/types/DMABuffer.cpp b/src/protocols/types/DMABuffer.cpp index f04665abe..37c463387 100644 --- a/src/protocols/types/DMABuffer.cpp +++ b/src/protocols/types/DMABuffer.cpp @@ -1,8 +1,9 @@ #include "DMABuffer.hpp" #include "WLBuffer.hpp" -#include "../../desktop/LayerSurface.hpp" +#include "../../desktop/view/LayerSurface.hpp" #include "../../render/Renderer.hpp" #include "../../helpers/Format.hpp" +#include #if defined(__linux__) #include @@ -11,36 +12,34 @@ #include using namespace Hyprutils::OS; +using namespace Hyprgraphics::Egl; CDMABuffer::CDMABuffer(uint32_t id, wl_client* client, Aquamarine::SDMABUFAttrs const& attrs_) : m_attrs(attrs_) { - g_pHyprRenderer->makeEGLCurrent(); - m_listeners.resourceDestroy = events.destroy.listen([this] { closeFDs(); m_listeners.resourceDestroy.reset(); }); - size = m_attrs.size; - m_resource = CWLBufferResource::create(makeShared(client, 1, id)); - auto eglImage = g_pHyprOpenGL->createEGLImage(m_attrs); + size = m_attrs.size; + m_resource = CWLBufferResource::create(makeShared(client, 1, id)); + m_opaque = isDrmFormatOpaque(m_attrs.format); + m_texture = g_pHyprRenderer->createTexture(m_attrs, m_opaque); // texture takes ownership of the eglImage - if UNLIKELY (!eglImage) { - Debug::log(ERR, "CDMABuffer: failed to import EGLImage, retrying as implicit"); + if UNLIKELY (!m_texture) { + Log::logger->log(Log::ERR, "CDMABuffer: failed to import EGLImage, retrying as implicit"); m_attrs.modifier = DRM_FORMAT_MOD_INVALID; - eglImage = g_pHyprOpenGL->createEGLImage(m_attrs); + m_texture = g_pHyprRenderer->createTexture(m_attrs, m_opaque); - if UNLIKELY (!eglImage) { - Debug::log(ERR, "CDMABuffer: failed to import EGLImage"); + if UNLIKELY (!m_texture) { + Log::logger->log(Log::ERR, "CDMABuffer: failed to import EGLImage"); return; } } - m_texture = makeShared(m_attrs, eglImage); // texture takes ownership of the eglImage - m_opaque = NFormatUtils::isFormatOpaque(m_attrs.format); - m_success = m_texture->m_texID; + m_success = m_texture->ok(); if UNLIKELY (!m_success) - Debug::log(ERR, "Failed to create a dmabuf: texture is null"); + Log::logger->log(Log::ERR, "Failed to create a dmabuf: texture is null"); } CDMABuffer::~CDMABuffer() { diff --git a/src/protocols/types/SurfaceState.cpp b/src/protocols/types/SurfaceState.cpp index 96e8e769a..7e1baaad8 100644 --- a/src/protocols/types/SurfaceState.cpp +++ b/src/protocols/types/SurfaceState.cpp @@ -1,6 +1,7 @@ #include "SurfaceState.hpp" #include "helpers/Format.hpp" #include "protocols/types/Buffer.hpp" +#include "render/Renderer.hpp" #include "render/Texture.hpp" Vector2D SSurfaceState::sourceSize() { @@ -29,12 +30,12 @@ CRegion SSurfaceState::accumulateBufferDamage() { Vector2D trc = transform % 2 == 1 ? Vector2D{bufferSize.y, bufferSize.x} : bufferSize; - bufferDamage = surfaceDamage.scale(scale).transform(wlTransformToHyprutils(invertTransform(transform)), trc.x, trc.y).add(bufferDamage); + bufferDamage = surfaceDamage.scale(scale).transform(Math::wlTransformToHyprutils(Math::invertTransform(transform)), trc.x, trc.y).add(bufferDamage); damage.clear(); return bufferDamage; } -void SSurfaceState::updateSynchronousTexture(SP lastTexture) { +void SSurfaceState::updateSynchronousTexture(SP lastTexture) { auto [dataPtr, fmt, size] = buffer->beginDataPtr(0); if (dataPtr) { auto drmFmt = NFormatUtils::shmToDRM(fmt); @@ -43,7 +44,7 @@ void SSurfaceState::updateSynchronousTexture(SP lastTexture) { texture = lastTexture; texture->update(drmFmt, dataPtr, stride, accumulateBufferDamage()); } else - texture = makeShared(drmFmt, dataPtr, stride, bufferSize); + texture = g_pHyprRenderer->createTexture(drmFmt, dataPtr, stride, bufferSize); } buffer->endDataPtr(); } @@ -63,6 +64,13 @@ void SSurfaceState::reset() { callbacks.clear(); lockMask = LOCK_REASON_NONE; + + barrierSet = false; + surfaceLocked = false; + fifoScheduled = false; + + pendingTimeout.reset(); + timer.reset(); // CEventLoopManager::nudgeTimers should handle it eventually } void SSurfaceState::updateFrom(SSurfaceState& ref) { @@ -112,4 +120,7 @@ void SSurfaceState::updateFrom(SSurfaceState& ref) { callbacks.insert(callbacks.end(), std::make_move_iterator(ref.callbacks.begin()), std::make_move_iterator(ref.callbacks.end())); ref.callbacks.clear(); } + + if (ref.barrierSet) + barrierSet = ref.barrierSet; } diff --git a/src/protocols/types/SurfaceState.hpp b/src/protocols/types/SurfaceState.hpp index dd7679622..9a12a0807 100644 --- a/src/protocols/types/SurfaceState.hpp +++ b/src/protocols/types/SurfaceState.hpp @@ -1,10 +1,14 @@ #pragma once #include "../../helpers/math/Math.hpp" +#include "../../helpers/time/Time.hpp" +#include "../../managers/eventLoop/EventLoopTimer.hpp" #include "../WaylandProtocol.hpp" #include "./Buffer.hpp" -class CTexture; +namespace Render { + class ITexture; +} class CDRMSyncPointState; class CWLCallbackResource; @@ -48,6 +52,7 @@ struct SSurfaceState { bool acquire : 1; bool acked : 1; bool frame : 1; + bool fifo : 1; } bits; } updated; @@ -85,8 +90,17 @@ struct SSurfaceState { eLockReason lockMask = LOCK_REASON_NONE; // texture of surface content, used for rendering - SP texture; - void updateSynchronousTexture(SP lastTexture); + SP texture; + void updateSynchronousTexture(SP lastTexture); + + // fifo + bool barrierSet = false; + bool surfaceLocked = false; + bool fifoScheduled = false; + + // commit timing + std::optional pendingTimeout; + SP timer; // helpers CRegion accumulateBufferDamage(); // transforms state.damage and merges it into state.bufferDamage diff --git a/src/protocols/types/SurfaceStateQueue.cpp b/src/protocols/types/SurfaceStateQueue.cpp index c10b556fb..82a04878a 100644 --- a/src/protocols/types/SurfaceStateQueue.cpp +++ b/src/protocols/types/SurfaceStateQueue.cpp @@ -21,6 +21,7 @@ void CSurfaceStateQueue::dropState(const WP& state) { } void CSurfaceStateQueue::lock(const WP& weakState, eLockReason reason) { + ASSERT(reason != LOCK_REASON_NONE); auto it = find(weakState); if (it == m_queue.end()) return; @@ -29,6 +30,7 @@ void CSurfaceStateQueue::lock(const WP& weakState, eLockReason re } void CSurfaceStateQueue::unlock(const WP& state, eLockReason reason) { + ASSERT(reason != LOCK_REASON_NONE); auto it = find(state); if (it == m_queue.end()) return; @@ -38,6 +40,7 @@ void CSurfaceStateQueue::unlock(const WP& state, eLockReason reas } void CSurfaceStateQueue::unlockFirst(eLockReason reason) { + ASSERT(reason != LOCK_REASON_NONE); for (auto& it : m_queue) { if ((it->lockMask & reason) != LOCK_REASON_NONE) { it->lockMask &= ~reason; @@ -65,6 +68,9 @@ auto CSurfaceStateQueue::find(const WP& state) -> std::dequelockMask & LOCK_REASON_FIFO && !m_surface->m_current.barrierSet) + front->lockMask &= ~LOCK_REASON_FIFO; + if (front->lockMask != LOCK_REASON_NONE) return; diff --git a/src/protocols/types/SurfaceStateQueue.hpp b/src/protocols/types/SurfaceStateQueue.hpp index 1841ed20f..8d9db7a59 100644 --- a/src/protocols/types/SurfaceStateQueue.hpp +++ b/src/protocols/types/SurfaceStateQueue.hpp @@ -15,7 +15,7 @@ class CSurfaceStateQueue { WP enqueue(UP&& state); void dropState(const WP& state); void lock(const WP& state, eLockReason reason); - void unlock(const WP& state, eLockReason reason = LOCK_REASON_NONE); + void unlock(const WP& state, eLockReason reason); void unlockFirst(eLockReason reason); void tryProcess(); diff --git a/src/render/ElementRenderer.cpp b/src/render/ElementRenderer.cpp new file mode 100644 index 000000000..8d2dedf71 --- /dev/null +++ b/src/render/ElementRenderer.cpp @@ -0,0 +1,485 @@ +#include "ElementRenderer.hpp" +#include "Renderer.hpp" +#include "../layout/LayoutManager.hpp" +#include "../desktop/view/Window.hpp" +#include +#include +#include + +using namespace Render; + +void IElementRenderer::drawElement(WP element, const CRegion& damage) { + if (!element) + return; + + switch (element->type()) { + case EK_BORDER: draw(dynamicPointerCast(element), damage); break; + case EK_CLEAR: draw(dynamicPointerCast(element), damage); break; + case EK_FRAMEBUFFER: draw(dynamicPointerCast(element), damage); break; + case EK_PRE_BLUR: drawPreBlur(dynamicPointerCast(element), damage); break; + case EK_RECT: drawRect(dynamicPointerCast(element), damage); break; + case EK_HINTS: drawHints(dynamicPointerCast(element), damage); break; + case EK_SHADOW: draw(dynamicPointerCast(element), damage); break; + case EK_INNER_GLOW: draw(dynamicPointerCast(element), damage); break; + case EK_SURFACE: preDrawSurface(dynamicPointerCast(element), damage); break; + case EK_TEXTURE: drawTex(dynamicPointerCast(element), damage); break; + case EK_TEXTURE_MATTE: drawTexMatte(dynamicPointerCast(element), damage); break; + case EK_CUSTOM: drawCustom(element, damage); break; + default: Log::logger->log(Log::WARN, "Unimplimented draw for {}", element->passName()); + } +} + +static std::optional getSurfaceExpectedSize(PHLWINDOW pWindow, SP pSurface, PHLMONITOR pMonitor, bool main) { + const auto CAN_USE_WINDOW = pWindow && main; + const auto WINDOW_SIZE_MISALIGN = CAN_USE_WINDOW && pWindow->getReportedSize() != pWindow->wlSurface()->resource()->m_current.size; + + if (pSurface->m_current.viewport.hasDestination) + return (pSurface->m_current.viewport.destination * pMonitor->m_scale).round(); + + if (pSurface->m_current.viewport.hasSource) + return (pSurface->m_current.viewport.source.size() * pMonitor->m_scale).round(); + + if (WINDOW_SIZE_MISALIGN) + return (pSurface->m_current.size * pMonitor->m_scale).round(); + + if (CAN_USE_WINDOW) + return (pWindow->getReportedSize() * pMonitor->m_scale).round(); + + return std::nullopt; +} + +void IElementRenderer::calculateUVForSurface(PHLWINDOW pWindow, SP pSurface, PHLMONITOR pMonitor, bool main, const Vector2D& projSize, + const Vector2D& projSizeUnscaled, bool fixMisalignedFSV1) { + auto& m_renderData = g_pHyprRenderer->m_renderData; + + if (!pWindow || !pWindow->m_isX11) { + static auto PEXPANDEDGES = CConfigValue("render:expand_undersized_textures"); + + Vector2D uvTL; + Vector2D uvBR = Vector2D(1, 1); + + if (pSurface->m_current.viewport.hasSource) { + // we stretch it to dest. if no dest, to 1,1 + Vector2D const& bufferSize = pSurface->m_current.bufferSize; + auto const& bufferSource = pSurface->m_current.viewport.source; + + // calculate UV for the basic src_box. Assume dest == size. Scale to dest later + uvTL = Vector2D(bufferSource.x / bufferSize.x, bufferSource.y / bufferSize.y); + uvBR = Vector2D((bufferSource.x + bufferSource.width) / bufferSize.x, (bufferSource.y + bufferSource.height) / bufferSize.y); + + if (uvBR.x < 0.01f || uvBR.y < 0.01f) { + uvTL = Vector2D(); + uvBR = Vector2D(1, 1); + } + } + + if (projSize != Vector2D{} && fixMisalignedFSV1) { + // instead of nearest_neighbor (we will repeat / skip) + // just cut off / expand surface + const Vector2D PIXELASUV = Vector2D{1, 1} / pSurface->m_current.bufferSize; + const auto& BUFFER_SIZE = pSurface->m_current.bufferSize; + + // compute MISALIGN from the adjusted UV coordinates. + const Vector2D MISALIGNMENT = (uvBR - uvTL) * BUFFER_SIZE - projSize; + + if (MISALIGNMENT != Vector2D{}) + uvBR -= MISALIGNMENT * PIXELASUV; + } else { + // if the surface is smaller than our viewport, extend its edges. + // this will break if later on xdg geometry is hit, but we really try + // to let the apps know to NOT add CSD. Also if source is there. + // there is no way to fix this if that's the case + const auto MONITOR_WL_SCALE = std::ceil(pMonitor->m_scale); + const bool SCALE_UNAWARE = pMonitor->m_scale != 1.f && (MONITOR_WL_SCALE == pSurface->m_current.scale || !pSurface->m_current.viewport.hasDestination); + const auto EXPECTED_SIZE = getSurfaceExpectedSize(pWindow, pSurface, pMonitor, main).value_or((projSize * pMonitor->m_scale).round()); + + const auto RATIO = projSize / EXPECTED_SIZE; + if (!SCALE_UNAWARE || MONITOR_WL_SCALE == 1) { + if (*PEXPANDEDGES && !SCALE_UNAWARE && (RATIO.x > 1 || RATIO.y > 1)) { + const auto FIX = RATIO.clamp(Vector2D{1, 1}, Vector2D{1000000, 1000000}); + uvBR = uvBR * FIX; + } + + // FIXME: probably do this for in anims on all views... + const auto SHOULD_SKIP = !pWindow || pWindow->m_animatingIn; + if (!SHOULD_SKIP && (RATIO.x < 1 || RATIO.y < 1)) { + const auto FIX = RATIO.clamp(Vector2D{0.0001, 0.0001}, Vector2D{1, 1}); + uvBR = uvBR * FIX; + } + } + } + + m_renderData.primarySurfaceUVTopLeft = uvTL; + m_renderData.primarySurfaceUVBottomRight = uvBR; + + if (m_renderData.primarySurfaceUVTopLeft == Vector2D() && m_renderData.primarySurfaceUVBottomRight == Vector2D(1, 1)) { + // No special UV mods needed + m_renderData.primarySurfaceUVTopLeft = Vector2D(-1, -1); + m_renderData.primarySurfaceUVBottomRight = Vector2D(-1, -1); + } + + if (!main || !pWindow) + return; + + // FIXME: this doesn't work. We always set MAXIMIZED anyways, so this doesn't need to work, but it's problematic. + + // CBox geom = pWindow->m_xdgSurface->m_current.geometry; + + // // Adjust UV based on the xdg_surface geometry + // if (geom.x != 0 || geom.y != 0 || geom.w != 0 || geom.h != 0) { + // const auto XPERC = geom.x / pSurface->m_current.size.x; + // const auto YPERC = geom.y / pSurface->m_current.size.y; + // const auto WPERC = (geom.x + geom.w ? geom.w : pSurface->m_current.size.x) / pSurface->m_current.size.x; + // const auto HPERC = (geom.y + geom.h ? geom.h : pSurface->m_current.size.y) / pSurface->m_current.size.y; + + // const auto TOADDTL = Vector2D(XPERC * (uvBR.x - uvTL.x), YPERC * (uvBR.y - uvTL.y)); + // uvBR = uvBR - Vector2D((1.0 - WPERC) * (uvBR.x - uvTL.x), (1.0 - HPERC) * (uvBR.y - uvTL.y)); + // uvTL = uvTL + TOADDTL; + // } + + m_renderData.primarySurfaceUVTopLeft = uvTL; + m_renderData.primarySurfaceUVBottomRight = uvBR; + + if (m_renderData.primarySurfaceUVTopLeft == Vector2D() && m_renderData.primarySurfaceUVBottomRight == Vector2D(1, 1)) { + // No special UV mods needed + m_renderData.primarySurfaceUVTopLeft = Vector2D(-1, -1); + m_renderData.primarySurfaceUVBottomRight = Vector2D(-1, -1); + } + } else { + m_renderData.primarySurfaceUVTopLeft = Vector2D(-1, -1); + m_renderData.primarySurfaceUVBottomRight = Vector2D(-1, -1); + } +} + +void IElementRenderer::drawRect(WP element, const CRegion& damage) { + auto& data = element->m_data; + auto& m_renderData = g_pHyprRenderer->m_renderData; + + if (data.box.w <= 0 || data.box.h <= 0) + return; + + if (!data.clipBox.empty()) + m_renderData.clipBox = data.clipBox; + + data.modifiedBox = data.box; + m_renderData.renderModif.applyToBox(data.modifiedBox); + + CBox transformedBox = data.box; + transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, + m_renderData.pMonitor->m_transformedSize.y); + + data.TOPLEFT[0] = sc(transformedBox.x); + data.TOPLEFT[1] = sc(transformedBox.y); + data.FULLSIZE[0] = sc(transformedBox.width); + data.FULLSIZE[1] = sc(transformedBox.height); + + data.drawRegion = data.color.a == 1.F || !data.blur ? damage : m_renderData.damage; + + 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}; + data.drawRegion = damageClip.intersect(data.drawRegion); + } + + draw(element, damage); + + m_renderData.clipBox = {}; +} + +void IElementRenderer::drawHints(WP element, const CRegion& damage) { + const auto m_data = element->m_data; + if (m_data.renderModif.has_value()) + g_pHyprRenderer->m_renderData.renderModif = *m_data.renderModif; +} + +void IElementRenderer::drawPreBlur(WP element, const CRegion& damage) { + TRACY_GPU_ZONE("RenderPreBlurForCurrentMonitor"); + auto& m_renderData = g_pHyprRenderer->m_renderData; + + const auto SAVEDRENDERMODIF = m_renderData.renderModif; + m_renderData.renderModif = {}; // fix shit + + // make the fake dmg + CRegion fakeDamage{0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}; + + draw(element, fakeDamage); + + m_renderData.pMonitor->m_blurFBDirty = false; + m_renderData.pMonitor->m_blurFBShouldRender = false; + + m_renderData.renderModif = SAVEDRENDERMODIF; +} + +void IElementRenderer::drawSurface(WP element, const CRegion& damage) { + const auto m_data = element->m_data; + auto& m_renderData = g_pHyprRenderer->m_renderData; + + Hyprutils::Utils::CScopeGuard x = {[]() { + g_pHyprRenderer->m_renderData.primarySurfaceUVTopLeft = Vector2D(-1, -1); + g_pHyprRenderer->m_renderData.primarySurfaceUVBottomRight = Vector2D(-1, -1); + }}; + + if (!m_data.texture) + return; + + const auto& TEXTURE = m_data.texture; + + // this is bad, probably has been logged elsewhere. Means the texture failed + // uploading to the GPU. + if (!TEXTURE->ok()) + return; + + const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_layoutManager->dragController()->target() && g_layoutManager->dragController()->mode() == MBIND_RESIZE; + TRACY_GPU_ZONE("RenderSurface"); + + auto PSURFACE = Desktop::View::CWLSurface::fromResource(m_data.surface); + + const float ALPHA = m_data.alpha * m_data.fadeAlpha * (PSURFACE ? PSURFACE->m_alphaModifier : 1.F); + const float OVERALL_ALPHA = PSURFACE ? PSURFACE->m_overallOpacity : 1.F; + const bool BLUR = m_data.blur && (!TEXTURE->m_opaque || ALPHA < 1.F || OVERALL_ALPHA < 1.F); + + auto windowBox = element->getTexBox(); + + const auto PROJSIZEUNSCALED = windowBox.size(); + + windowBox.scale(m_data.pMonitor->m_scale); + windowBox.round(); + + if (windowBox.width <= 1 || windowBox.height <= 1) { + element->discard(); + return; + } + + const bool MISALIGNEDFSV1 = std::floor(m_data.pMonitor->m_scale) != m_data.pMonitor->m_scale /* Fractional */ && m_data.surface->m_current.scale == 1 /* fs protocol */ && + windowBox.size() != m_data.surface->m_current.bufferSize /* misaligned */ && DELTALESSTHAN(windowBox.width, m_data.surface->m_current.bufferSize.x, 3) && + DELTALESSTHAN(windowBox.height, m_data.surface->m_current.bufferSize.y, 3) /* off by one-or-two */ && + (!m_data.pWindow || (!m_data.pWindow->m_realSize->isBeingAnimated() && !INTERACTIVERESIZEINPROGRESS)) /* not window or not animated/resizing */ && + (!m_data.pLS || (!m_data.pLS->m_realSize->isBeingAnimated())); /* not LS or not animated */ + + calculateUVForSurface(m_data.pWindow, m_data.surface, m_data.pMonitor->m_self.lock(), m_data.mainSurface, windowBox.size(), PROJSIZEUNSCALED, MISALIGNEDFSV1); + + auto cancelRender = false; + auto clipRegion = element->visibleRegion(cancelRender); + if (cancelRender) + return; + + // check for fractional scale surfaces misaligning the buffer size + // in those cases it's better to just force nearest neighbor + // as long as the window is not animated. During those it'd look weird. + // UV will fixup it as well + if (MISALIGNEDFSV1) + m_renderData.useNearestNeighbor = true; + + float rounding = m_data.rounding; + float roundingPower = m_data.roundingPower; + + rounding -= 1; // to fix a border issue + + if (m_data.dontRound) { + rounding = 0; + roundingPower = 2.0f; + } + + 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; + + if (CANDISABLEBLEND) + g_pHyprRenderer->blend(false); + else + g_pHyprRenderer->blend(true); + + // FIXME: This is wrong and will bug the blur out as shit if the first surface + // is a subsurface that does NOT cover the entire frame. In such cases, we probably should fall back + // to what we do for misaligned surfaces (blur the entire thing and then render shit without blur) + if (m_data.surfaceCounter == 0 && !m_data.popup) { + if (BLUR) + drawElement(makeShared(CTexPassElement::SRenderData{ + .tex = TEXTURE, + .box = windowBox, + .a = ALPHA, + .blurA = m_data.fadeAlpha, + .overallA = OVERALL_ALPHA, + .round = rounding, + .roundingPower = roundingPower, + .blur = true, + .blockBlurOptimization = m_data.blockBlurOptimization, + .allowCustomUV = true, + .surface = m_data.surface, + .discardMode = m_data.discardMode, + .discardOpacity = m_data.discardOpacity, + .clipRegion = clipRegion, + .currentLS = m_data.pLS, + }), + m_renderData.damage.copy().intersect(windowBox)); + else + drawElement(makeShared(CTexPassElement::SRenderData{ + .tex = TEXTURE, + .box = windowBox, + .a = ALPHA * OVERALL_ALPHA, + .round = rounding, + .roundingPower = roundingPower, + .discardActive = false, + .allowCustomUV = true, + .surface = m_data.surface, + .discardMode = m_data.discardMode, + .discardOpacity = m_data.discardOpacity, + .clipRegion = clipRegion, + .currentLS = m_data.pLS, + }), + m_renderData.damage.copy().intersect(windowBox)); + } else { + if (BLUR && m_data.popup) + drawElement(makeShared(CTexPassElement::SRenderData{ + .tex = TEXTURE, + .box = windowBox, + .a = ALPHA, + .blurA = m_data.fadeAlpha, + .overallA = OVERALL_ALPHA, + .round = rounding, + .roundingPower = roundingPower, + .blur = true, + .blockBlurOptimization = true, + .allowCustomUV = true, + .surface = m_data.surface, + .discardMode = m_data.discardMode, + .discardOpacity = m_data.discardOpacity, + .clipRegion = clipRegion, + .currentLS = m_data.pLS, + }), + m_renderData.damage.copy().intersect(windowBox)); + else + drawElement(makeShared(CTexPassElement::SRenderData{ + .tex = TEXTURE, + .box = windowBox, + .a = ALPHA * OVERALL_ALPHA, + .round = rounding, + .roundingPower = roundingPower, + .discardActive = false, + .allowCustomUV = true, + .surface = m_data.surface, + .discardMode = m_data.discardMode, + .discardOpacity = m_data.discardOpacity, + .clipRegion = clipRegion, + .currentLS = m_data.pLS, + }), + m_renderData.damage.copy().intersect(windowBox)); + } + + g_pHyprRenderer->blend(true); +}; + +void IElementRenderer::preDrawSurface(WP element, const CRegion& damage) { + auto& m_renderData = g_pHyprRenderer->m_renderData; + m_renderData.clipBox = element->m_data.clipBox; + m_renderData.useNearestNeighbor = element->m_data.useNearestNeighbor; + g_pHyprRenderer->pushMonitorTransformEnabled(element->m_data.flipEndFrame); + m_renderData.currentWindow = element->m_data.pWindow; + + drawSurface(element, damage); + + if (!g_pHyprRenderer->m_bBlockSurfaceFeedback) + element->m_data.surface->presentFeedback(element->m_data.when, element->m_data.pMonitor->m_self.lock()); + + // add async (dmabuf) buffers to usedBuffers so we can handle release later + // sync (shm) buffers will be released in commitState, so no need to track them here + if (element->m_data.surface->m_current.buffer && !element->m_data.surface->m_current.buffer->isSynchronous()) + g_pHyprRenderer->m_usedAsyncBuffers.emplace_back(element->m_data.surface->m_current.buffer); + + m_renderData.clipBox = {}; + m_renderData.useNearestNeighbor = false; + g_pHyprRenderer->popMonitorTransformEnabled(); + m_renderData.currentWindow.reset(); +} + +void IElementRenderer::drawTex(WP element, const CRegion& damage) { + auto& m_renderData = g_pHyprRenderer->m_renderData; + if (!element->m_data.clipBox.empty()) + m_renderData.clipBox = element->m_data.clipBox; + + g_pHyprRenderer->pushMonitorTransformEnabled(element->m_data.flipEndFrame); + if (element->m_data.useMirrorProjection) + g_pHyprRenderer->setProjectionType(RPT_MIRROR); + + m_renderData.surface = element->m_data.surface; + + Hyprutils::Utils::CScopeGuard x = {[useMirrorProjection = element->m_data.useMirrorProjection]() { + g_pHyprRenderer->popMonitorTransformEnabled(); + if (useMirrorProjection) + g_pHyprRenderer->setProjectionType(RPT_MONITOR); + g_pHyprRenderer->m_renderData.surface.reset(); + g_pHyprRenderer->m_renderData.clipBox = {}; + }}; + + if (element->m_data.blur) { + // make a damage region for this window + CRegion texDamage{m_renderData.damage}; + texDamage.intersect(element->m_data.box.x, element->m_data.box.y, element->m_data.box.width, element->m_data.box.height); + + // While renderTextureInternalWithDamage will clip the blur as well, + // clipping texDamage here allows blur generation to be optimized. + if (!element->m_data.clipRegion.empty()) + texDamage.intersect(element->m_data.clipRegion); + + if (texDamage.empty()) + return; + + m_renderData.renderModif.applyToRegion(texDamage); + + element->m_data.damage = texDamage; + + // amazing hack: the surface has an opaque region! + const auto& surface = element->m_data.surface; + const auto& box = element->m_data.box; + CRegion inverseOpaque; + if (element->m_data.a >= 1.f && surface && std::round(surface->m_current.size.x * m_renderData.pMonitor->m_scale) == box.w && + std::round(surface->m_current.size.y * m_renderData.pMonitor->m_scale) == box.h) { + pixman_box32_t surfbox = {0, 0, surface->m_current.size.x * surface->m_current.scale, surface->m_current.size.y * surface->m_current.scale}; + inverseOpaque = surface->m_current.opaque; + inverseOpaque.invert(&surfbox).intersect(0, 0, surface->m_current.size.x * surface->m_current.scale, surface->m_current.size.y * surface->m_current.scale); + + if (inverseOpaque.empty()) { + element->m_data.blur = false; + draw(element, damage); + return; + } + } else + inverseOpaque = {0, 0, element->m_data.box.width, element->m_data.box.height}; + + inverseOpaque.scale(m_renderData.pMonitor->m_scale); + element->m_data.blockBlurOptimization = element->m_data.blockBlurOptimization.value_or(false) || + !g_pHyprRenderer->shouldUseNewBlurOptimizations(element->m_data.currentLS.lock(), m_renderData.currentWindow.lock()); + + // vvv TODO: layered blur fbs? + if (element->m_data.blockBlurOptimization.value_or(false)) { + inverseOpaque.translate(box.pos()); + m_renderData.renderModif.applyToRegion(inverseOpaque); + inverseOpaque.intersect(element->m_data.damage); + element->m_data.blurredBG = g_pHyprRenderer->blurMainFramebuffer(element->m_data.a, &inverseOpaque); + } else + element->m_data.blurredBG = m_renderData.pMonitor->resources()->m_blurFB->getTexture(); + + draw(element, damage); + } else + draw(element, damage); +} + +void IElementRenderer::drawTexMatte(WP element, const CRegion& damage) { + if (g_pHyprRenderer->m_renderData.damage.empty()) + return; + + const auto m_data = element->m_data; + if (m_data.disableTransformAndModify) { + g_pHyprRenderer->pushMonitorTransformEnabled(true); + g_pHyprRenderer->m_renderData.renderModif.enabled = false; + draw(element, damage); + g_pHyprRenderer->m_renderData.renderModif.enabled = true; + g_pHyprRenderer->popMonitorTransformEnabled(); + } else + draw(element, damage); +} + +void IElementRenderer::drawCustom(WP element, const CRegion& damage) { + const auto& elements = element->draw(); + for (const auto& el : elements) { + drawElement(el, damage); + } +} diff --git a/src/render/ElementRenderer.hpp b/src/render/ElementRenderer.hpp new file mode 100644 index 000000000..ed92b06f3 --- /dev/null +++ b/src/render/ElementRenderer.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include "./pass/BorderPassElement.hpp" +#include "./pass/ClearPassElement.hpp" +#include "./pass/FramebufferElement.hpp" +#include "./pass/PreBlurElement.hpp" +#include "./pass/RectPassElement.hpp" +#include "./pass/RendererHintsPassElement.hpp" +#include "./pass/ShadowPassElement.hpp" +#include "./pass/SurfacePassElement.hpp" +#include "./pass/TexPassElement.hpp" +#include "./pass/TextureMatteElement.hpp" +#include "./pass/InnerGlowPassElement.hpp" +#include + +namespace Render { + class IElementRenderer { + public: + IElementRenderer() = default; + virtual ~IElementRenderer() = default; + + void drawElement(WP element, const CRegion& damage); + + protected: + virtual void draw(WP element, const CRegion& damage) = 0; + virtual void draw(WP element, const CRegion& damage) = 0; + virtual void draw(WP element, const CRegion& damage) = 0; + virtual void draw(WP element, const CRegion& damage) = 0; + virtual void draw(WP element, const CRegion& damage) = 0; + virtual void draw(WP element, const CRegion& damage) = 0; + virtual void draw(WP element, const CRegion& damage) = 0; + virtual void draw(WP element, const CRegion& damage) = 0; + virtual void draw(WP element, const CRegion& damage) = 0; + + private: + void calculateUVForSurface(PHLWINDOW, SP, PHLMONITOR pMonitor, bool main = false, const Vector2D& projSize = {}, const Vector2D& projSizeUnscaled = {}, + bool fixMisalignedFSV1 = false); + + void drawRect(WP element, const CRegion& damage); + void drawHints(WP element, const CRegion& damage); + void drawPreBlur(WP element, const CRegion& damage); + void drawSurface(WP element, const CRegion& damage); + void preDrawSurface(WP element, const CRegion& damage); + void drawTex(WP element, const CRegion& damage); + void drawTexMatte(WP element, const CRegion& damage); + void drawCustom(WP element, const CRegion& damage); + }; +} diff --git a/src/render/Framebuffer.cpp b/src/render/Framebuffer.cpp index 070bcc1b2..4b8d392ee 100644 --- a/src/render/Framebuffer.cpp +++ b/src/render/Framebuffer.cpp @@ -1,127 +1,67 @@ #include "Framebuffer.hpp" -#include "OpenGL.hpp" +#include "helpers/Format.hpp" +#include "helpers/cm/ColorManagement.hpp" -CFramebuffer::CFramebuffer() { - ; -} +using namespace Render; -bool CFramebuffer::alloc(int w, int h, uint32_t drmFormat) { - bool firstAlloc = false; +IFramebuffer::IFramebuffer(const std::string& name) : m_name(name) {} + +bool IFramebuffer::alloc(int w, int h, DRMFormat format) { RASSERT((w > 0 && h > 0), "cannot alloc a FB with negative / zero size! (attempted {}x{})", w, h); - uint32_t glFormat = NFormatUtils::drmFormatToGL(drmFormat); - uint32_t glType = NFormatUtils::glFormatToType(glFormat); + const bool sizeChanged = m_size != Vector2D(w, h); + const bool formatChanged = format != m_drmFormat; - if (drmFormat != m_drmFormat || m_size != Vector2D{w, h}) - release(); + if (m_fbAllocated && !sizeChanged && !formatChanged) + return true; - m_drmFormat = drmFormat; - - if (!m_tex) { - m_tex = makeShared(); - m_tex->allocate(); - m_tex->bind(); - m_tex->setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - m_tex->setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - m_tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); - m_tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); - firstAlloc = true; - } - - if (!m_fbAllocated) { - glGenFramebuffers(1, &m_fb); - m_fbAllocated = true; - firstAlloc = true; - } - - if (firstAlloc || m_size != Vector2D(w, h)) { - m_tex->bind(); - glTexImage2D(GL_TEXTURE_2D, 0, glFormat, w, h, 0, GL_RGBA, glType, nullptr); - glBindFramebuffer(GL_FRAMEBUFFER, m_fb); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_tex->m_texID, 0); - - if (m_stencilTex) { - m_stencilTex->bind(); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, w, h, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_stencilTex->m_texID, 0); - } - - auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - RASSERT((status == GL_FRAMEBUFFER_COMPLETE), "Framebuffer incomplete, couldn't create! (FB status: {}, GL Error: 0x{:x})", status, sc(glGetError())); - - Debug::log(LOG, "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) + if (sizeChanged || formatChanged) m_tex.reset(); - m_size = Vector2D(); + m_size = {w, h}; + m_drmFormat = format; + m_fbAllocated = internalAlloc(w, h, format); + return m_fbAllocated; } -CFramebuffer::~CFramebuffer() { - release(); -} - -bool CFramebuffer::isAllocated() { +bool IFramebuffer::isAllocated() { return m_fbAllocated && m_tex; } -SP CFramebuffer::getTexture() { +SP IFramebuffer::getTexture() { return m_tex; } -GLuint CFramebuffer::getFBID() { - return m_fbAllocated ? m_fb : 0; +SP IFramebuffer::getMirrorTexture() { + return m_mirrorTex; } -SP CFramebuffer::getStencilTex() { +SP IFramebuffer::getStencilTex() { return m_stencilTex; } + +void IFramebuffer::enableMirror(SP tex) { + if (!tex || tex == m_mirrorTex) + return; + m_mirrorTex = tex; + m_fbAllocated = internalAlloc(m_size.x, m_size.y, m_drmFormat); +} + +void IFramebuffer::disableMirror() { + if (m_mirrorTex) { + m_mirrorTex.reset(); + m_fbAllocated = internalAlloc(m_size.x, m_size.y, m_drmFormat); + } +} + +NColorManagement::PImageDescription IFramebuffer::imageDescription() { + return m_tex ? m_tex->m_imageDescription : m_imageDescription; +} + +void IFramebuffer::setImageDescription(NColorManagement::PImageDescription desc) { + m_imageDescription = desc; + if (m_tex) + m_tex->m_imageDescription = desc; + else + Log::logger->log(Log::TRACE, "CM: FIXME no framebuffer texture"); +} diff --git a/src/render/Framebuffer.hpp b/src/render/Framebuffer.hpp index 0e18df5fc..215e5ed9a 100644 --- a/src/render/Framebuffer.hpp +++ b/src/render/Framebuffer.hpp @@ -3,32 +3,49 @@ #include "../defines.hpp" #include "../helpers/Format.hpp" #include "Texture.hpp" +#include "../helpers/cm/ColorManagement.hpp" +#include +#include -class CFramebuffer { - public: - CFramebuffer(); - ~CFramebuffer(); +class CHLBufferReference; - bool alloc(int w, int h, uint32_t format = GL_RGBA); - void addStencil(SP tex); - void bind(); - void unbind(); - void release(); - void reset(); - bool isAllocated(); - SP getTexture(); - SP getStencilTex(); - GLuint getFBID(); +namespace Render { + class IFramebuffer { + public: + IFramebuffer() = default; + IFramebuffer(const std::string& name); + virtual ~IFramebuffer() = default; - Vector2D m_size; - DRMFormat m_drmFormat = 0 /* DRM_FORMAT_INVALID */; + virtual bool alloc(int w, int h, DRMFormat 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; - private: - SP m_tex; - GLuint m_fb = -1; - bool m_fbAllocated = false; + virtual void bind() = 0; - SP m_stencilTex; + bool isAllocated(); + SP getTexture(); + SP getMirrorTexture(); + SP getStencilTex(); + void enableMirror(SP tex); + void disableMirror(); + NColorManagement::PImageDescription imageDescription(); + void setImageDescription(NColorManagement::PImageDescription desc); - friend class CRenderbuffer; -}; + virtual void addStencil(SP tex) = 0; + + Vector2D m_size; + DRMFormat m_drmFormat = DRM_FORMAT_INVALID; + + protected: + virtual bool internalAlloc(int w, int h, DRMFormat format = DRM_FORMAT_ARGB8888) = 0; + + SP m_tex; + SP m_mirrorTex; + bool m_fbAllocated = false; + + SP m_stencilTex; + std::string m_name; // name for logging + + NColorManagement::PImageDescription m_imageDescription; + }; +} diff --git a/src/render/GLRenderer.cpp b/src/render/GLRenderer.cpp new file mode 100644 index 000000000..129295acd --- /dev/null +++ b/src/render/GLRenderer.cpp @@ -0,0 +1,308 @@ +#include "GLRenderer.hpp" +#include "decorations/CHyprInnerGlowDecoration.hpp" +#include +#include "../config/ConfigValue.hpp" +#include "../managers/CursorManager.hpp" +#include "../managers/PointerManager.hpp" +#include "../protocols/SessionLock.hpp" +#include "../protocols/LayerShell.hpp" +#include "../protocols/PresentationTime.hpp" +#include "../protocols/core/DataDevice.hpp" +#include "../protocols/core/Compositor.hpp" +#include "../debug/Overlay.hpp" +#include "../helpers/Monitor.hpp" +#include "pass/TexPassElement.hpp" +#include "pass/SurfacePassElement.hpp" +#include "../debug/log/Logger.hpp" +#include "../protocols/types/ContentType.hpp" +#include "OpenGL.hpp" +#include "Renderer.hpp" +#include "./gl/GLElementRenderer.hpp" +#include "./gl/GLFramebuffer.hpp" +#include "./gl/GLTexture.hpp" + +#include +#include +#include +#include +using namespace Hyprutils::Utils; +using namespace Hyprutils::OS; +using enum NContentType::eContentType; +using namespace NColorManagement; +using namespace Render; +using namespace Render::GL; + +extern "C" { +#include +} + +CHyprGLRenderer::CHyprGLRenderer() : IHyprRenderer(), m_elementRenderer(makeUnique()) {} + +IHyprRenderer::eType CHyprGLRenderer::type() { + return RT_GL; +} + +void CHyprGLRenderer::initRender() { + g_pHyprOpenGL->makeEGLCurrent(); + g_pHyprRenderer->m_renderData.pMonitor = renderData().pMonitor; +} + +bool CHyprGLRenderer::initRenderBuffer(SP buffer, uint32_t fmt) { + try { + m_currentRenderbuffer = getOrCreateRenderbuffer(m_currentBuffer, fmt); + } catch (std::exception& e) { + Log::logger->log(Log::ERR, "getOrCreateRenderbuffer failed for {}", NFormatUtils::drmFormatName(fmt)); + return false; + } + + return m_currentRenderbuffer; +} + +bool CHyprGLRenderer::beginFullFakeRenderInternal(PHLMONITOR pMonitor, CRegion& damage, SP fb, bool simple) { + initRender(); + + RASSERT(fb, "Cannot render FULL_FAKE without a provided fb!"); + bindFB(fb); + if (simple) + g_pHyprOpenGL->beginSimple(pMonitor, damage, nullptr, fb); + else + g_pHyprOpenGL->begin(pMonitor, damage, fb); + return true; +} + +bool CHyprGLRenderer::beginRenderInternal(PHLMONITOR pMonitor, CRegion& damage, bool simple) { + + m_currentRenderbuffer->bind(); + if (simple) + g_pHyprOpenGL->beginSimple(pMonitor, damage, m_currentRenderbuffer); + else + g_pHyprOpenGL->begin(pMonitor, damage); + + return true; +} + +void CHyprGLRenderer::endRender(const std::function& renderingDoneCallback) { + const auto PMONITOR = g_pHyprRenderer->m_renderData.pMonitor; + static auto PNVIDIAANTIFLICKER = CConfigValue("opengl:nvidia_anti_flicker"); + + g_pHyprRenderer->m_renderData.damage = m_renderPass.render(g_pHyprRenderer->m_renderData.damage); + + auto cleanup = CScopeGuard([this]() { + if (m_currentRenderbuffer) + m_currentRenderbuffer->unbind(); + m_currentRenderbuffer = nullptr; + m_currentBuffer = nullptr; + }); + + if (m_renderMode != RENDER_MODE_TO_BUFFER_READ_ONLY) + g_pHyprOpenGL->end(); + else { + g_pHyprRenderer->m_renderData.pMonitor.reset(); + g_pHyprRenderer->m_renderData.mouseZoomFactor = 1.f; + g_pHyprRenderer->m_renderData.mouseZoomUseMouse = true; + } + + if (m_renderMode == RENDER_MODE_FULL_FAKE) + return; + + if (m_renderMode == RENDER_MODE_NORMAL) + PMONITOR->m_output->state->setBuffer(m_currentBuffer); + + if (!explicitSyncSupported()) { + Log::logger->log(Log::TRACE, "renderer: Explicit sync unsupported, falling back to implicit in endRender"); + + // nvidia doesn't have implicit sync, so we have to explicitly wait here, llvmpipe and other software renderer seems to bug out aswell. + if ((isNvidia() && *PNVIDIAANTIFLICKER) || isSoftware()) + glFinish(); + else + glFlush(); // mark an implicit sync point + + m_usedAsyncBuffers.clear(); // release all buffer refs and hope implicit sync works + if (renderingDoneCallback) + renderingDoneCallback(); + + return; + } + + auto eglSync = createSyncFDManager(); + if LIKELY (eglSync && eglSync->isValid()) { + for (auto const& buf : m_usedAsyncBuffers) { + for (const auto& releaser : buf->m_syncReleasers) { + releaser->addSyncFileFd(eglSync->fd()); + } + } + + // release buffer refs with release points now, since syncReleaser handles actual buffer release based on EGLSync + std::erase_if(m_usedAsyncBuffers, [](const auto& buf) { return !buf->m_syncReleasers.empty(); }); + + // release buffer refs without release points when EGLSync sync_file/fence is signalled + g_pEventLoopManager->doOnReadable(eglSync->fd().duplicate(), [renderingDoneCallback, prevbfs = std::move(m_usedAsyncBuffers)]() mutable { + prevbfs.clear(); + if (renderingDoneCallback) + renderingDoneCallback(); + }); + m_usedAsyncBuffers.clear(); + + if (m_renderMode == RENDER_MODE_NORMAL) { + PMONITOR->m_inFence = eglSync->takeFd(); + PMONITOR->m_output->state->setExplicitInFence(PMONITOR->m_inFence.get()); + } + } else { + Log::logger->log(Log::ERR, "renderer: Explicit sync failed, releasing resources"); + + m_usedAsyncBuffers.clear(); // release all buffer refs and hope implicit sync works + if (renderingDoneCallback) + renderingDoneCallback(); + } +} + +void CHyprGLRenderer::renderOffToMain(SP off) { + g_pHyprOpenGL->renderOffToMain(off); +} + +SP CHyprGLRenderer::getOrCreateRenderbufferInternal(SP buffer, uint32_t fmt) { + g_pHyprOpenGL->makeEGLCurrent(); + return makeShared(buffer, fmt); +} + +UP CHyprGLRenderer::createSyncFDManager() { + return CEGLSync::create(); +} + +SP CHyprGLRenderer::createStencilTexture(const int width, const int height) { + g_pHyprOpenGL->makeEGLCurrent(); + auto tex = makeShared(); + tex->allocate({width, height}); + + return tex; +} + +SP CHyprGLRenderer::createTexture(bool opaque) { + g_pHyprOpenGL->makeEGLCurrent(); + return makeShared(opaque); +} + +SP CHyprGLRenderer::createTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy, bool opaque) { + g_pHyprOpenGL->makeEGLCurrent(); + return makeShared(drmFormat, pixels, stride, size, keepDataCopy, opaque); +} + +SP CHyprGLRenderer::createTexture(const Aquamarine::SDMABUFAttrs& attrs, bool opaque) { + g_pHyprOpenGL->makeEGLCurrent(); + const auto image = g_pHyprOpenGL->createEGLImage(attrs); + if (!image) + return nullptr; + return makeShared(attrs, image, opaque); +} + +SP CHyprGLRenderer::createTexture(const int width, const int height, unsigned char* const data) { + g_pHyprOpenGL->makeEGLCurrent(); + SP tex = makeShared(); + + tex->allocate({width, height}, DRM_FORMAT_ARGB8888); // FIXME assume DRM_FORMAT_ARGB8888 + + 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 CHyprGLRenderer::createTexture(cairo_surface_t* cairo) { + g_pHyprOpenGL->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); + tex->m_drmFormat = DRM_FORMAT_ARGB8888; + } + + glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, tex->m_size.x, tex->m_size.y, 0, glFormat, glType, DATA); + + return tex; +} + +SP CHyprGLRenderer::createTexture(std::span lut3D, size_t N) { + g_pHyprOpenGL->makeEGLCurrent(); + return makeShared(lut3D, N); +} + +bool CHyprGLRenderer::explicitSyncSupported() { + return g_pHyprOpenGL->explicitSyncSupported(); +} + +std::vector CHyprGLRenderer::getDRMFormats() { + return g_pHyprOpenGL->getDRMFormats(); +} + +std::vector CHyprGLRenderer::getDRMFormatModifiers(DRMFormat format) { + return g_pHyprOpenGL->getDRMFormatModifiers(format); +} + +SP CHyprGLRenderer::createFB(const std::string& name) { + g_pHyprOpenGL->makeEGLCurrent(); + return makeShared(name); +} + +void CHyprGLRenderer::disableScissor() { + g_pHyprOpenGL->scissor(nullptr); +} + +void CHyprGLRenderer::blend(bool enabled) { + g_pHyprOpenGL->blend(enabled); +} + +void CHyprGLRenderer::drawShadow(const CBox& box, int round, float roundingPower, int range, CHyprColor color, float a) { + g_pHyprOpenGL->renderRoundedShadow(box, round, roundingPower, range, color, a); +} + +SP CHyprGLRenderer::blurFramebuffer(SP source, float a, CRegion* originalDamage) { + auto src = GLFB(source); + return g_pHyprOpenGL->blurFramebufferWithDamage(a, originalDamage, *src)->getTexture(); +} + +void CHyprGLRenderer::setViewport(int x, int y, int width, int height) { + g_pHyprOpenGL->setViewport(x, y, width, height); +} + +bool CHyprGLRenderer::reloadShaders(const std::string& path) { + return g_pHyprOpenGL->initShaders(path); +} + +SP CHyprGLRenderer::getBlurTexture(PHLMONITORREF pMonitor) { + return pMonitor->resources()->m_blurFB->getTexture(); +} + +void CHyprGLRenderer::unsetEGL() { + if (!g_pHyprOpenGL) + return; + + eglMakeCurrent(g_pHyprOpenGL->m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); +} + +WP CHyprGLRenderer::elementRenderer() { + return m_elementRenderer; +} diff --git a/src/render/GLRenderer.hpp b/src/render/GLRenderer.hpp new file mode 100644 index 000000000..ee831fec7 --- /dev/null +++ b/src/render/GLRenderer.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "Renderer.hpp" +#include "render/ElementRenderer.hpp" + +namespace Render::GL { + class CHyprGLRenderer : public Render::IHyprRenderer { + public: + CHyprGLRenderer(); + ~CHyprGLRenderer() = default; + + eType type() override; + void endRender(const std::function& renderingDoneCallback = {}) override; + UP createSyncFDManager() override; + SP createStencilTexture(const int width, const int height) override; + SP createTexture(bool opaque = false) override; + SP createTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy = false, bool opaque = false) override; + SP createTexture(const Aquamarine::SDMABUFAttrs&, bool opaque = false) override; + SP createTexture(const int width, const int height, unsigned char* const data) override; + SP createTexture(cairo_surface_t* cairo) override; + SP createTexture(std::span lut3D, size_t N) override; + bool explicitSyncSupported() override; + std::vector getDRMFormats() override; + std::vector getDRMFormatModifiers(DRMFormat format) override; + SP createFB(const std::string& name = "") override; + void disableScissor() override; + void blend(bool enabled) override; + void drawShadow(const CBox& box, int round, float roundingPower, int range, CHyprColor color, float a) override; + SP blurFramebuffer(SP source, float a, CRegion* originalDamage) override; + void setViewport(int x, int y, int width, int height) override; + bool reloadShaders(const std::string& path = "") override; + + void unsetEGL(); + WP elementRenderer() override; + + private: + void renderOffToMain(SP off) override; + SP getOrCreateRenderbufferInternal(SP buffer, uint32_t fmt) override; + bool beginRenderInternal(PHLMONITOR pMonitor, CRegion& damage, bool simple = false) override; + bool beginFullFakeRenderInternal(PHLMONITOR pMonitor, CRegion& damage, SP fb, bool simple = false) override; + void initRender() override; + bool initRenderBuffer(SP buffer, uint32_t fmt) override; + + SP getBlurTexture(PHLMONITORREF pMonitor) override; + + SP m_currentRenderbuffer; + UP m_elementRenderer; + + friend class CHyprOpenGLImpl; + }; +} \ No newline at end of file diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index d93e71963..bf914e31f 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -1,5 +1,8 @@ #include +#include #include +#include +#include #include #include #include @@ -10,66 +13,71 @@ #include "../Compositor.hpp" #include "../helpers/MiscFunctions.hpp" #include "../helpers/CursorShapes.hpp" +#include "../helpers/TransferFunction.hpp" #include "../config/ConfigValue.hpp" -#include "../config/ConfigManager.hpp" +#include "../config/legacy/ConfigManager.hpp" #include "../managers/PointerManager.hpp" -#include "../desktop/LayerSurface.hpp" +#include "../desktop/view/LayerSurface.hpp" #include "../desktop/state/FocusState.hpp" #include "../protocols/LayerShell.hpp" #include "../protocols/core/Compositor.hpp" #include "../protocols/ColorManagement.hpp" -#include "../protocols/types/ColorManagement.hpp" -#include "../managers/HookSystemManager.hpp" +#include "../helpers/cm/ColorManagement.hpp" #include "../managers/input/InputManager.hpp" #include "../managers/eventLoop/EventLoopManager.hpp" #include "../managers/CursorManager.hpp" #include "../helpers/fs/FsUtils.hpp" +#include "../helpers/env/Env.hpp" #include "../helpers/MainLoopExecutor.hpp" #include "../i18n/Engine.hpp" -#include "debug/HyprNotificationOverlay.hpp" -#include "hyprerror/HyprError.hpp" +#include "../event/EventBus.hpp" +#include "../managers/screenshare/ScreenshareManager.hpp" +#include "../notification/NotificationOverlay.hpp" +#include "errorOverlay/Overlay.hpp" +#include "macros.hpp" #include "pass/TexPassElement.hpp" #include "pass/RectPassElement.hpp" #include "pass/PreBlurElement.hpp" #include "pass/ClearPassElement.hpp" -#include "render/Shader.hpp" +#include "GLRenderer.hpp" +#include "Shader.hpp" #include "AsyncResourceGatherer.hpp" #include #include +#include #include #include #include #include #include +#include #include "./shaders/Shaders.hpp" +#include "ShaderLoader.hpp" +#include "Texture.hpp" +#include "gl/GLFramebuffer.hpp" +#include "gl/GLTexture.hpp" using namespace Hyprutils::OS; using namespace NColorManagement; - -const std::vector ASSET_PATHS = { -#ifdef DATAROOTDIR - DATAROOTDIR, -#endif - "/usr/share", - "/usr/local/share", -}; +using namespace Render; +using namespace Render::GL; static inline void loadGLProc(void* pProc, const char* name) { void* proc = rc(eglGetProcAddress(name)); if (proc == nullptr) { - Debug::log(CRIT, "[Tracy GPU Profiling] eglGetProcAddress({}) failed", name); + Log::logger->log(Log::CRIT, "[Tracy GPU Profiling] eglGetProcAddress({}) failed", name); abort(); } *sc(pProc) = proc; } -static enum eLogLevel eglLogToLevel(EGLint type) { +static enum Hyprutils::CLI::eLogLevel eglLogToLevel(EGLint type) { switch (type) { - case EGL_DEBUG_MSG_CRITICAL_KHR: return CRIT; - case EGL_DEBUG_MSG_ERROR_KHR: return ERR; - case EGL_DEBUG_MSG_WARN_KHR: return WARN; - case EGL_DEBUG_MSG_INFO_KHR: return LOG; - default: return LOG; + case EGL_DEBUG_MSG_CRITICAL_KHR: return Log::CRIT; + case EGL_DEBUG_MSG_ERROR_KHR: return Log::ERR; + case EGL_DEBUG_MSG_WARN_KHR: return Log::WARN; + case EGL_DEBUG_MSG_INFO_KHR: return Log::DEBUG; + default: return Log::DEBUG; } } @@ -96,7 +104,7 @@ static const char* eglErrorToString(EGLint error) { } static void eglLog(EGLenum error, const char* command, EGLint type, EGLLabelKHR thread, EGLLabelKHR obj, const char* msg) { - Debug::log(eglLogToLevel(type), "[EGL] Command {} errored out with {} (0x{}): {}", command, eglErrorToString(error), error, msg); + Log::logger->log(eglLogToLevel(type), "[EGL] Command {} errored out with {} (0x{}): {}", command, eglErrorToString(error), error, msg); } static int openRenderNode(int drmFd) { @@ -106,14 +114,14 @@ static int openRenderNode(int drmFd) { // primary node renderName = drmGetPrimaryDeviceNameFromFd(drmFd); if (!renderName) { - Debug::log(ERR, "drmGetPrimaryDeviceNameFromFd failed"); + Log::logger->log(Log::ERR, "drmGetPrimaryDeviceNameFromFd failed"); return -1; } - Debug::log(LOG, "DRM dev {} has no render node, falling back to primary", renderName); + Log::logger->log(Log::DEBUG, "DRM dev {} has no render node, falling back to primary", renderName); drmVersion* render_version = drmGetVersion(drmFd); if (render_version && render_version->name) { - Debug::log(LOG, "DRM dev versionName", render_version->name); + Log::logger->log(Log::DEBUG, "DRM dev versionName {}", render_version->name); if (strcmp(render_version->name, "evdi") == 0) { free(renderName); // NOLINT(cppcoreguidelines-no-malloc) renderName = strdup("/dev/dri/card0"); @@ -122,16 +130,22 @@ static int openRenderNode(int drmFd) { } } - Debug::log(LOG, "openRenderNode got drm device {}", renderName); + Log::logger->log(Log::DEBUG, "openRenderNode got drm device {}", renderName); int renderFD = open(renderName, O_RDWR | O_CLOEXEC); if (renderFD < 0) - Debug::log(ERR, "openRenderNode failed to open drm device {}", renderName); + Log::logger->log(Log::ERR, "openRenderNode failed to open drm device {}", renderName); free(renderName); // NOLINT(cppcoreguidelines-no-malloc) return renderFD; } +static ShaderFeatureFlags globalFeatures() { + return g_pHyprRenderer->m_renderData.pMonitor && g_pHyprRenderer->m_renderData.pMonitor->needsUnmodifiedCopy() && g_pHyprRenderer->m_renderData.currentFB->getMirrorTexture() ? + SH_FEAT_MIRROR : + 0; +} + void CHyprOpenGLImpl::initEGL(bool gbm) { std::vector attrs; if (m_exts.KHR_display_reference) { @@ -157,19 +171,26 @@ 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) { - Debug::log(LOG, "EGL: IMG_context_priority supported, requesting high"); + Log::logger->log(Log::DEBUG, "EGL: IMG_context_priority supported, requesting high"); attrs.push_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG); attrs.push_back(EGL_CONTEXT_PRIORITY_HIGH_IMG); } if (m_exts.EXT_create_context_robustness) { - Debug::log(LOG, "EGL: EXT_create_context_robustness supported, requesting lose on reset"); + Log::logger->log(Log::DEBUG, "EGL: EXT_create_context_robustness supported, requesting lose on reset"); attrs.push_back(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT); 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); @@ -180,7 +201,7 @@ void CHyprOpenGLImpl::initEGL(bool gbm) { m_eglContext = eglCreateContext(m_eglDisplay, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attrs.data()); if (m_eglContext == EGL_NO_CONTEXT) { - Debug::log(WARN, "EGL: Failed to create a context with GLES3.2, retrying 3.0"); + Log::logger->log(Log::WARN, "EGL: Failed to create a context with GLES3.2, retrying 3.0"); attrs = attrsNoVer; attrs.push_back(EGL_CONTEXT_MAJOR_VERSION); @@ -200,9 +221,9 @@ void CHyprOpenGLImpl::initEGL(bool gbm) { EGLint priority = EGL_CONTEXT_PRIORITY_MEDIUM_IMG; eglQueryContext(m_eglDisplay, m_eglContext, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &priority); if (priority != EGL_CONTEXT_PRIORITY_HIGH_IMG) - Debug::log(ERR, "EGL: Failed to obtain a high priority context"); + Log::logger->log(Log::ERR, "EGL: Failed to obtain a high priority context"); else - Debug::log(LOG, "EGL: Got a high priority context"); + Log::logger->log(Log::DEBUG, "EGL: Got a high priority context"); } eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, m_eglContext); @@ -222,12 +243,12 @@ static bool drmDeviceHasName(const drmDevice* device, const std::string& name) { EGLDeviceEXT CHyprOpenGLImpl::eglDeviceFromDRMFD(int drmFD) { EGLint nDevices = 0; if (!m_proc.eglQueryDevicesEXT(0, nullptr, &nDevices)) { - Debug::log(ERR, "eglDeviceFromDRMFD: eglQueryDevicesEXT failed"); + Log::logger->log(Log::ERR, "eglDeviceFromDRMFD: eglQueryDevicesEXT failed"); return EGL_NO_DEVICE_EXT; } if (nDevices <= 0) { - Debug::log(ERR, "eglDeviceFromDRMFD: no devices"); + Log::logger->log(Log::ERR, "eglDeviceFromDRMFD: no devices"); return EGL_NO_DEVICE_EXT; } @@ -235,13 +256,13 @@ EGLDeviceEXT CHyprOpenGLImpl::eglDeviceFromDRMFD(int drmFD) { devices.resize(nDevices); if (!m_proc.eglQueryDevicesEXT(nDevices, devices.data(), &nDevices)) { - Debug::log(ERR, "eglDeviceFromDRMFD: eglQueryDevicesEXT failed (2)"); + Log::logger->log(Log::ERR, "eglDeviceFromDRMFD: eglQueryDevicesEXT failed (2)"); return EGL_NO_DEVICE_EXT; } drmDevice* drmDev = nullptr; if (int ret = drmGetDevice(drmFD, &drmDev); ret < 0) { - Debug::log(ERR, "eglDeviceFromDRMFD: drmGetDevice failed"); + Log::logger->log(Log::ERR, "eglDeviceFromDRMFD: drmGetDevice failed"); return EGL_NO_DEVICE_EXT; } @@ -251,21 +272,21 @@ EGLDeviceEXT CHyprOpenGLImpl::eglDeviceFromDRMFD(int drmFD) { continue; if (drmDeviceHasName(drmDev, devName)) { - Debug::log(LOG, "eglDeviceFromDRMFD: Using device {}", devName); + Log::logger->log(Log::DEBUG, "eglDeviceFromDRMFD: Using device {}", devName); drmFreeDevice(&drmDev); return d; } } drmFreeDevice(&drmDev); - Debug::log(LOG, "eglDeviceFromDRMFD: No drm devices found"); + Log::logger->log(Log::DEBUG, "eglDeviceFromDRMFD: No drm devices found"); return EGL_NO_DEVICE_EXT; } CHyprOpenGLImpl::CHyprOpenGLImpl() : m_drmFD(g_pCompositor->m_drmRenderNode.fd >= 0 ? g_pCompositor->m_drmRenderNode.fd : g_pCompositor->m_drm.fd) { const std::string EGLEXTENSIONS = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); - Debug::log(LOG, "Supported EGL global extensions: ({}) {}", std::ranges::count(EGLEXTENSIONS, ' '), EGLEXTENSIONS); + Log::logger->log(Log::DEBUG, "Supported EGL global extensions: ({}) {}", std::ranges::count(EGLEXTENSIONS, ' '), EGLEXTENSIONS); m_exts.KHR_display_reference = EGLEXTENSIONS.contains("KHR_display_reference"); @@ -294,7 +315,8 @@ CHyprOpenGLImpl::CHyprOpenGLImpl() : m_drmFD(g_pCompositor->m_drmRenderNode.fd > loadGLProc(&m_proc.eglQueryDisplayAttribEXT, "eglQueryDisplayAttribEXT"); } - if (EGLEXTENSIONS.contains("EGL_KHR_debug")) { + static const auto GLDEBUG = CConfigValue("debug:gl_debugging"); + if (EGLEXTENSIONS.contains("EGL_KHR_debug") && *GLDEBUG) { loadGLProc(&m_proc.eglDebugMessageControlKHR, "eglDebugMessageControlKHR"); static const EGLAttrib debugAttrs[] = { EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE, EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE, EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE, EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE, EGL_NONE, @@ -315,7 +337,7 @@ CHyprOpenGLImpl::CHyprOpenGLImpl() : m_drmFD(g_pCompositor->m_drmRenderNode.fd > } if (!success) { - Debug::log(WARN, "EGL: EXT_platform_device or EGL_EXT_device_query not supported, using gbm"); + Log::logger->log(Log::WARN, "EGL: EXT_platform_device or EGL_EXT_device_query not supported, using gbm"); if (EGLEXTENSIONS.contains("KHR_platform_gbm")) { success = true; m_gbmFD = CFileDescriptor{openRenderNode(m_drmFD)}; @@ -337,33 +359,33 @@ CHyprOpenGLImpl::CHyprOpenGLImpl() : m_drmFD(g_pCompositor->m_drmRenderNode.fd > m_extensions = EXTENSIONS; - Debug::log(LOG, "Creating the Hypr OpenGL Renderer!"); - Debug::log(LOG, "Using: {}", rc(glGetString(GL_VERSION))); - Debug::log(LOG, "Vendor: {}", rc(glGetString(GL_VENDOR))); - Debug::log(LOG, "Renderer: {}", rc(glGetString(GL_RENDERER))); - Debug::log(LOG, "Supported extensions: ({}) {}", std::ranges::count(m_extensions, ' '), m_extensions); + Log::logger->log(Log::DEBUG, "Creating the Hypr OpenGL Renderer!"); + Log::logger->log(Log::DEBUG, "Using: {}", rc(glGetString(GL_VERSION))); + Log::logger->log(Log::DEBUG, "Vendor: {}", rc(glGetString(GL_VENDOR))); + Log::logger->log(Log::DEBUG, "Renderer: {}", rc(glGetString(GL_RENDERER))); + Log::logger->log(Log::DEBUG, "Supported extensions: ({}) {}", std::ranges::count(m_extensions, ' '), m_extensions); m_exts.EXT_read_format_bgra = m_extensions.contains("GL_EXT_read_format_bgra"); RASSERT(m_extensions.contains("GL_EXT_texture_format_BGRA8888"), "GL_EXT_texture_format_BGRA8888 support by the GPU driver is required"); if (!m_exts.EXT_read_format_bgra) - Debug::log(WARN, "Your GPU does not support GL_EXT_read_format_bgra, this may cause issues with texture importing"); + Log::logger->log(Log::WARN, "Your GPU does not support GL_EXT_read_format_bgra, this may cause issues with texture importing"); if (!m_exts.EXT_image_dma_buf_import || !m_exts.EXT_image_dma_buf_import_modifiers) - Debug::log(WARN, "Your GPU does not support DMABUFs, this will possibly cause issues and will take a hit on the performance."); + Log::logger->log(Log::WARN, "Your GPU does not support DMABUFs, this will possibly cause issues and will take a hit on the performance."); const std::string EGLEXTENSIONS_DISPLAY = eglQueryString(m_eglDisplay, EGL_EXTENSIONS); - Debug::log(LOG, "Supported EGL display extensions: ({}) {}", std::ranges::count(EGLEXTENSIONS_DISPLAY, ' '), EGLEXTENSIONS_DISPLAY); + Log::logger->log(Log::DEBUG, "Supported EGL display extensions: ({}) {}", std::ranges::count(EGLEXTENSIONS_DISPLAY, ' '), EGLEXTENSIONS_DISPLAY); #if defined(__linux__) m_exts.EGL_ANDROID_native_fence_sync_ext = EGLEXTENSIONS_DISPLAY.contains("EGL_ANDROID_native_fence_sync"); if (!m_exts.EGL_ANDROID_native_fence_sync_ext) - Debug::log(WARN, "Your GPU does not support explicit sync via the EGL_ANDROID_native_fence_sync extension."); + Log::logger->log(Log::WARN, "Your GPU does not support explicit sync via the EGL_ANDROID_native_fence_sync extension."); #else m_exts.EGL_ANDROID_native_fence_sync_ext = false; - Debug::log(WARN, "Forcefully disabling explicit sync: BSD is missing support for proper timeline export"); + Log::logger->log(Log::WARN, "Forcefully disabling explicit sync: BSD is missing support for proper timeline export"); #endif #ifdef USE_TRACY_GPU @@ -378,16 +400,10 @@ CHyprOpenGLImpl::CHyprOpenGLImpl() : m_drmFD(g_pCompositor->m_drmRenderNode.fd > initDRMFormats(); - initAssets(); - - static auto P = g_pHookSystem->hookDynamic("preRender", [&](void* self, SCallbackInfo& info, std::any data) { preRender(std::any_cast(data)); }); + static auto P = Event::bus()->m_events.render.pre.listen([&](PHLMONITOR mon) { preRender(mon); }); RASSERT(eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT), "Couldn't unset current EGL!"); - m_globalTimer.reset(); - - pushMonitorTransformEnabled(false); - static auto addLastPressToHistory = [this](const Vector2D& pos, bool killing, bool touch) { // shift the new pos and time in std::ranges::rotate(m_pressedHistoryPositions, m_pressedHistoryPositions.end() - 1); @@ -400,37 +416,35 @@ CHyprOpenGLImpl::CHyprOpenGLImpl() : m_drmFD(g_pCompositor->m_drmRenderNode.fd > m_pressedHistoryKilled <<= 1; m_pressedHistoryKilled |= killing ? 1 : 0; #if POINTER_PRESSED_HISTORY_LENGTH < 32 - m_pressedHistoryKilled &= (1 >> POINTER_PRESSED_HISTORY_LENGTH) - 1; + m_pressedHistoryKilled &= (1U << POINTER_PRESSED_HISTORY_LENGTH) - 1; #endif // shift touch flag in m_pressedHistoryTouched <<= 1; m_pressedHistoryTouched |= touch ? 1 : 0; #if POINTER_PRESSED_HISTORY_LENGTH < 32 - m_pressedHistoryTouched &= (1 >> POINTER_PRESSED_HISTORY_LENGTH) - 1; + m_pressedHistoryTouched &= (1U << POINTER_PRESSED_HISTORY_LENGTH) - 1; #endif }; - static auto P2 = g_pHookSystem->hookDynamic("mouseButton", [](void* self, SCallbackInfo& info, std::any e) { - auto E = std::any_cast(e); - - if (E.state != WL_POINTER_BUTTON_STATE_PRESSED) + static auto P2 = Event::bus()->m_events.input.mouse.button.listen([](IPointer::SButtonEvent e, Event::SCallbackInfo&) { + if (e.state != WL_POINTER_BUTTON_STATE_PRESSED) return; addLastPressToHistory(g_pInputManager->getMouseCoordsInternal(), g_pInputManager->getClickMode() == CLICKMODE_KILL, false); }); - static auto P3 = g_pHookSystem->hookDynamic("touchDown", [](void* self, SCallbackInfo& info, std::any e) { - auto E = std::any_cast(e); - - auto PMONITOR = g_pCompositor->getMonitorFromName(!E.device->m_boundOutput.empty() ? E.device->m_boundOutput : ""); + static auto P3 = Event::bus()->m_events.input.touch.down.listen([](ITouch::SDownEvent e, Event::SCallbackInfo&) { + auto PMONITOR = g_pCompositor->getMonitorFromName(!e.device->m_boundOutput.empty() ? e.device->m_boundOutput : ""); PMONITOR = PMONITOR ? PMONITOR : Desktop::focusState()->monitor(); - const auto TOUCH_COORDS = PMONITOR->m_position + (E.pos * PMONITOR->m_size); + const auto TOUCH_COORDS = PMONITOR->m_position + (e.pos * PMONITOR->m_size); addLastPressToHistory(TOUCH_COORDS, g_pInputManager->getClickMode() == CLICKMODE_KILL, true); }); + + m_finalScreenShader = makeShared(); } CHyprOpenGLImpl::~CHyprOpenGLImpl() { @@ -454,7 +468,7 @@ std::optional> CHyprOpenGLImpl::getModsForFormat(EGLint fo EGLint len = 0; if (!m_proc.eglQueryDmaBufModifiersEXT(m_eglDisplay, format, 0, nullptr, nullptr, &len)) { - Debug::log(ERR, "EGL: Failed to query mods"); + Log::logger->log(Log::ERR, "EGL: Failed to query mods"); return std::nullopt; } @@ -467,7 +481,11 @@ std::optional> CHyprOpenGLImpl::getModsForFormat(EGLint fo mods.resize(len); external.resize(len); - m_proc.eglQueryDmaBufModifiersEXT(m_eglDisplay, format, len, mods.data(), external.data(), &len); + if (!m_proc.eglQueryDmaBufModifiersEXT(m_eglDisplay, format, len, mods.data(), external.data(), &len)) { + const auto err = eglGetError(); + Log::logger->log(Log::ERR, "EGL: Failed to query mods (2) for format 0x{:x}, eglGetError: 0x{:x}", format, err); + return std::nullopt; + } std::vector result; // reserve number of elements to avoid reallocations @@ -492,12 +510,12 @@ std::optional> CHyprOpenGLImpl::getModsForFormat(EGLint fo } void CHyprOpenGLImpl::initDRMFormats() { - const auto DISABLE_MODS = envEnabled("HYPRLAND_EGL_NO_MODIFIERS"); + const auto DISABLE_MODS = Env::envEnabled("HYPRLAND_EGL_NO_MODIFIERS"); if (DISABLE_MODS) - Debug::log(WARN, "HYPRLAND_EGL_NO_MODIFIERS set, disabling modifiers"); + Log::logger->log(Log::WARN, "HYPRLAND_EGL_NO_MODIFIERS set, disabling modifiers"); if (!m_exts.EXT_image_dma_buf_import) { - Debug::log(ERR, "EGL: No dmabuf import, DMABufs will not work."); + Log::logger->log(Log::ERR, "EGL: No dmabuf import, DMABufs will not work."); return; } @@ -506,20 +524,28 @@ void CHyprOpenGLImpl::initDRMFormats() { if (!m_exts.EXT_image_dma_buf_import_modifiers || !m_proc.eglQueryDmaBufFormatsEXT) { formats.push_back(DRM_FORMAT_ARGB8888); formats.push_back(DRM_FORMAT_XRGB8888); - Debug::log(WARN, "EGL: No mod support"); + Log::logger->log(Log::WARN, "EGL: No mod support"); } else { EGLint len = 0; - m_proc.eglQueryDmaBufFormatsEXT(m_eglDisplay, 0, nullptr, &len); + if (!m_proc.eglQueryDmaBufFormatsEXT(m_eglDisplay, 0, nullptr, &len)) { + const auto err = eglGetError(); + Log::logger->log(Log::ERR, "EGL: Failed to query formats, eglGetError: 0x{:x}", err); + return; + } formats.resize(len); - m_proc.eglQueryDmaBufFormatsEXT(m_eglDisplay, len, formats.data(), &len); + if (!m_proc.eglQueryDmaBufFormatsEXT(m_eglDisplay, len, formats.data(), &len)) { + const auto err = eglGetError(); + Log::logger->log(Log::ERR, "EGL: Failed to query formats (2), eglGetError: 0x{:x}", err); + return; + } } if (formats.empty()) { - Debug::log(ERR, "EGL: Failed to get formats, DMABufs will not work."); + Log::logger->log(Log::ERR, "EGL: Failed to get formats, DMABufs will not work."); return; } - Debug::log(LOG, "Supported DMA-BUF formats:"); + Log::logger->log(Log::DEBUG, "Supported DMA-BUF formats:"); std::vector dmaFormats; // reserve number of elements to avoid reallocations @@ -551,7 +577,7 @@ void CHyprOpenGLImpl::initDRMFormats() { modifierData.reserve(mods.size()); auto fmtName = drmGetFormatName(fmt); - Debug::log(LOG, "EGL: GPU Supports Format {} (0x{:x})", fmtName ? fmtName : "?unknown?", fmt); + Log::logger->log(Log::DEBUG, "EGL: GPU Supports Format {} (0x{:x})", fmtName ? fmtName : "?unknown?", fmt); for (auto const& mod : mods) { auto modName = drmGetFormatModifierName(mod); modifierData.emplace_back(std::make_pair<>(mod, modName ? modName : "?unknown?")); @@ -569,16 +595,16 @@ void CHyprOpenGLImpl::initDRMFormats() { }); for (auto const& [m, name] : modifierData) { - Debug::log(LOG, "EGL: | with modifier {} (0x{:x})", name, m); + Log::logger->log(Log::DEBUG, "EGL: | with modifier {} (0x{:x})", name, m); mods.emplace_back(m); } } - Debug::log(LOG, "EGL: {} formats found in total. Some modifiers may be omitted as they are external-only.", dmaFormats.size()); + Log::logger->log(Log::DEBUG, "EGL: {} formats found in total. Some modifiers may be omitted as they are external-only.", dmaFormats.size()); if (dmaFormats.empty()) - Debug::log(WARN, - "EGL: WARNING: No dmabuf formats were found, dmabuf will be disabled. This will degrade performance, but is most likely a driver issue or a very old GPU."); + Log::logger->log( + Log::WARN, "EGL: WARNING: No dmabuf formats were found, dmabuf will be disabled. This will degrade performance, but is most likely a driver issue or a very old GPU."); m_drmFormats = dmaFormats; } @@ -630,103 +656,15 @@ EGLImageKHR CHyprOpenGLImpl::createEGLImage(const Aquamarine::SDMABUFAttrs& attr EGLImageKHR image = m_proc.eglCreateImageKHR(m_eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs.data()); if (image == EGL_NO_IMAGE_KHR) { - Debug::log(ERR, "EGL: EGLCreateImageKHR failed: {}", eglGetError()); + Log::logger->log(Log::ERR, "EGL: EGLCreateImageKHR failed: {}", eglGetError()); return EGL_NO_IMAGE_KHR; } return image; } -void CHyprOpenGLImpl::logShaderError(const GLuint& shader, bool program, bool silent) { - GLint maxLength = 0; - if (program) - glGetProgramiv(shader, GL_INFO_LOG_LENGTH, &maxLength); - else - glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); - - std::vector errorLog(maxLength); - if (program) - glGetProgramInfoLog(shader, maxLength, &maxLength, errorLog.data()); - else - glGetShaderInfoLog(shader, maxLength, &maxLength, errorLog.data()); - std::string errorStr(errorLog.begin(), errorLog.end()); - - const auto FULLERROR = (program ? "Screen shader parser: Error linking program:" : "Screen shader parser: Error compiling shader: ") + errorStr; - - Debug::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; +void CHyprOpenGLImpl::beginSimple(PHLMONITOR pMonitor, const CRegion& damage, SP rb, SP fb) { + g_pHyprRenderer->m_renderData.pMonitor = pMonitor; const GLenum RESETSTATUS = glGetGraphicsResetStatus(); if (RESETSTATUS != GL_NO_ERROR) { @@ -747,38 +685,34 @@ void CHyprOpenGLImpl::beginSimple(PHLMONITOR pMonitor, const CRegion& damage, SP setViewport(0, 0, pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y); - m_renderData.projection = Mat3x3::outputProjection(pMonitor->m_pixelSize, HYPRUTILS_TRANSFORM_NORMAL); - - m_renderData.monitorProjection = Mat3x3::identity(); - if (pMonitor->m_transform != WL_OUTPUT_TRANSFORM_NORMAL) { - const Vector2D tfmd = pMonitor->m_transform % 2 == 1 ? Vector2D{FBO->m_size.y, FBO->m_size.x} : FBO->m_size; - m_renderData.monitorProjection.translate(FBO->m_size / 2.0).transform(wlTransformToHyprutils(pMonitor->m_transform)).translate(-tfmd / 2.0); - } - - m_renderData.pCurrentMonData = &m_monitorRenderResources[pMonitor]; - if (!m_shadersInitialized) initShaders(); - m_renderData.damage.set(damage); - m_renderData.finalDamage.set(damage); + g_pHyprRenderer->m_renderData.transformDamage = true; + g_pHyprRenderer->m_renderData.damage.set(damage); + g_pHyprRenderer->m_renderData.finalDamage.set(damage); m_fakeFrame = true; - m_renderData.currentFB = FBO; - FBO->bind(); + g_pHyprRenderer->bindFB(FBO); m_offloadedFramebuffer = false; - m_renderData.mainFB = m_renderData.currentFB; - m_renderData.outFB = FBO; + g_pHyprRenderer->m_renderData.mainFB = g_pHyprRenderer->m_renderData.currentFB; + g_pHyprRenderer->m_renderData.outFB = FBO; - m_renderData.simplePass = true; - - pushMonitorTransformEnabled(false); + g_pHyprRenderer->pushMonitorTransformEnabled(false); } -void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, CFramebuffer* fb, std::optional finalDamage) { - m_renderData.pMonitor = pMonitor; +void CHyprOpenGLImpl::makeEGLCurrent() { + if (!g_pCompositor || !g_pHyprOpenGL) + return; + + if (eglGetCurrentContext() != g_pHyprOpenGL->m_eglContext) + eglMakeCurrent(g_pHyprOpenGL->m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, g_pHyprOpenGL->m_eglContext); +} + +void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, SP fb, std::optional finalDamage) { + g_pHyprRenderer->m_renderData.pMonitor = pMonitor; const GLenum RESETSTATUS = glGetGraphicsResetStatus(); if (RESETSTATUS != GL_NO_ERROR) { @@ -797,466 +731,174 @@ void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, CFrameb setViewport(0, 0, pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y); - m_renderData.projection = Mat3x3::outputProjection(pMonitor->m_pixelSize, HYPRUTILS_TRANSFORM_NORMAL); - - m_renderData.monitorProjection = pMonitor->m_projMatrix; - - if (m_monitorRenderResources.contains(pMonitor) && m_monitorRenderResources.at(pMonitor).offloadFB.m_size != pMonitor->m_pixelSize) - destroyMonitorResources(pMonitor); - - m_renderData.pCurrentMonData = &m_monitorRenderResources[pMonitor]; - if (!m_shadersInitialized) initShaders(); - const auto DRM_FORMAT = fb ? fb->m_drmFormat : pMonitor->m_output->state->state().drmFormat; + const bool HAS_MIRROR_FB = g_pHyprRenderer->m_renderData.pMonitor->resources()->hasMirrorFB(); + const bool NEEDS_COPY_FB = g_pHyprRenderer->m_renderData.pMonitor->needsACopyFB(); - // ensure a framebuffer for the monitor exists - if (m_renderData.pCurrentMonData->offloadFB.m_size != pMonitor->m_pixelSize || DRM_FORMAT != m_renderData.pCurrentMonData->offloadFB.m_drmFormat) { - m_renderData.pCurrentMonData->stencilTex->allocate(); - - 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); + g_pHyprRenderer->m_renderData.transformDamage = true; + if (HAS_MIRROR_FB != NEEDS_COPY_FB) { + // force full damage because the mirror fb will be empty + g_pHyprRenderer->m_renderData.damage.set({0, 0, INT32_MAX, INT32_MAX}); + g_pHyprRenderer->m_renderData.finalDamage.set(g_pHyprRenderer->m_renderData.damage); + } else { + g_pHyprRenderer->m_renderData.damage.set(damage_); + g_pHyprRenderer->m_renderData.finalDamage.set(finalDamage.value_or(damage_)); } - if (m_renderData.pCurrentMonData->monitorMirrorFB.isAllocated() && m_renderData.pMonitor->m_mirrors.empty()) - m_renderData.pCurrentMonData->monitorMirrorFB.release(); - - m_renderData.damage.set(damage_); - m_renderData.finalDamage.set(finalDamage.value_or(damage_)); - m_fakeFrame = fb; - if (m_reloadScreenShader) { - m_reloadScreenShader = false; - static auto PSHADER = CConfigValue("decoration:screen_shader"); + if (g_pHyprRenderer->m_reloadScreenShader) { + g_pHyprRenderer->m_reloadScreenShader = false; + static auto PSHADER = CConfigValue("decoration:screen_shader"); applyScreenShader(*PSHADER); } - m_renderData.pCurrentMonData->offloadFB.bind(); - m_renderData.currentFB = &m_renderData.pCurrentMonData->offloadFB; + g_pHyprRenderer->bindFB(g_pHyprRenderer->m_renderData.pMonitor->resources()->getUnusedWorkBuffer()); m_offloadedFramebuffer = true; - m_renderData.mainFB = m_renderData.currentFB; - m_renderData.outFB = fb ? fb : g_pHyprRenderer->getCurrentRBO()->getFB(); + g_pHyprRenderer->m_renderData.mainFB = g_pHyprRenderer->m_renderData.currentFB; + g_pHyprRenderer->m_renderData.outFB = fb ? fb : dc(g_pHyprRenderer.get())->m_currentRenderbuffer->getFB(); - pushMonitorTransformEnabled(false); + if UNLIKELY (g_pHyprRenderer->m_renderData.pMonitor->needsUnmodifiedCopy() && !m_fakeFrame) { + if (!g_pHyprRenderer->m_renderData.pMonitor->resources()->m_mirrorTex) { + GLenum buffers[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}; + glDrawBuffers(2, buffers); + g_pHyprRenderer->m_renderData.pMonitor->resources()->enableMirror(); + } + g_pHyprRenderer->m_renderData.mainFB->enableMirror(g_pHyprRenderer->m_renderData.pMonitor->resources()->m_mirrorTex); + } else { + if (g_pHyprRenderer->m_renderData.pMonitor->resources()->m_mirrorTex) { + GLenum buffers[] = {GL_COLOR_ATTACHMENT0}; + glDrawBuffers(1, buffers); + g_pHyprRenderer->m_renderData.pMonitor->resources()->disableMirror(); + } + g_pHyprRenderer->m_renderData.mainFB->disableMirror(); + } + + g_pHyprRenderer->pushMonitorTransformEnabled(false); } void CHyprOpenGLImpl::end() { - static auto PZOOMRIGID = CConfigValue("cursor:zoom_rigid"); static auto PZOOMDISABLEAA = CConfigValue("cursor:zoom_disable_aa"); - + static auto PFPINVALIDATE = CConfigValue("debug:invalidate_fp16"); + auto& m_renderData = g_pHyprRenderer->m_renderData; + const auto PMONITOR = m_renderData.pMonitor; TRACY_GPU_ZONE("RenderEnd"); + g_pHyprRenderer->m_renderData.currentWindow.reset(); + g_pHyprRenderer->m_renderData.surface.reset(); + g_pHyprRenderer->m_renderData.clipBox = {}; + // end the render, copy the data to the main framebuffer - if (m_offloadedFramebuffer) { - m_renderData.damage = m_renderData.finalDamage; - pushMonitorTransformEnabled(true); + if LIKELY (m_offloadedFramebuffer) { + g_pHyprRenderer->m_renderData.damage = g_pHyprRenderer->m_renderData.finalDamage; + g_pHyprRenderer->pushMonitorTransformEnabled(true); CBox monbox = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}; - if (m_renderData.mouseZoomFactor != 1.f) { - const auto ZOOMCENTER = m_renderData.mouseZoomUseMouse ? - (g_pInputManager->getMouseCoordsInternal() - m_renderData.pMonitor->m_position) * m_renderData.pMonitor->m_scale : - m_renderData.pMonitor->m_transformedSize / 2.f; + if LIKELY (g_pHyprRenderer->m_renderMode == RENDER_MODE_NORMAL && g_pHyprRenderer->m_renderData.mouseZoomFactor == 1.0f) + m_renderData.pMonitor->m_zoomController.m_resetCameraState = true; + m_renderData.pMonitor->m_zoomController.applyZoomTransform(monbox, m_renderData); - monbox.translate(-ZOOMCENTER).scale(m_renderData.mouseZoomFactor).translate(*PZOOMRIGID ? m_renderData.pMonitor->m_transformedSize / 2.0 : ZOOMCENTER); - - monbox.x = std::min(monbox.x, 0.0); - monbox.y = std::min(monbox.y, 0.0); - if (monbox.x + monbox.width < m_renderData.pMonitor->m_transformedSize.x) - monbox.x = m_renderData.pMonitor->m_transformedSize.x - monbox.width; - if (monbox.y + monbox.height < m_renderData.pMonitor->m_transformedSize.y) - monbox.y = m_renderData.pMonitor->m_transformedSize.y - monbox.height; - } - - m_applyFinalShader = !m_renderData.blockScreenShader; - if (m_renderData.mouseZoomUseMouse && *PZOOMDISABLEAA) - m_renderData.useNearestNeighbor = true; + m_applyFinalShader = !g_pHyprRenderer->m_renderData.blockScreenShader; + if UNLIKELY (g_pHyprRenderer->m_renderData.mouseZoomFactor != 1.F && g_pHyprRenderer->m_renderData.mouseZoomUseMouse && *PZOOMDISABLEAA) + g_pHyprRenderer->m_renderData.useNearestNeighbor = true; // copy the damaged areas into the mirror buffer - // we can't use the offloadFB for mirroring, as it contains artifacts from blurring - if (!m_renderData.pMonitor->m_mirrors.empty() && !m_fakeFrame) + // we can't use the offloadFB for mirroring / ss, as it contains artifacts from blurring + if UNLIKELY (g_pHyprRenderer->m_renderData.pMonitor->needsACopyFB() && !m_fakeFrame) saveBufferForMirror(monbox); - m_renderData.outFB->bind(); + const auto TEX = g_pHyprRenderer->m_renderData.currentFB->getTexture(); + g_pHyprRenderer->bindFB(g_pHyprRenderer->m_renderData.outFB); blend(false); - if (m_finalScreenShader.program < 1 && !g_pHyprRenderer->m_crashingInProgress) - renderTexturePrimitive(m_renderData.pCurrentMonData->offloadFB.getTexture(), monbox); - else - renderTexture(m_renderData.pCurrentMonData->offloadFB.getTexture(), monbox, {}); + const auto PRIMITIVE_BLOCKED = m_finalScreenShader->program() >= 1 || g_pHyprRenderer->m_crashingInProgress || + g_pHyprRenderer->m_renderData.pMonitor->m_imageDescription->value() != SImageDescription{}; + + if LIKELY (!PRIMITIVE_BLOCKED || g_pHyprRenderer->m_renderMode != RENDER_MODE_NORMAL) + renderTexturePrimitive(TEX, monbox); + else // we need to use renderTexture if we do any CM whatsoever. + renderTexture(TEX, monbox, {.finalMonitorCM = true}); blend(true); - m_renderData.useNearestNeighbor = false; - m_applyFinalShader = false; - popMonitorTransformEnabled(); + g_pHyprRenderer->m_renderData.useNearestNeighbor = false; + m_applyFinalShader = false; + g_pHyprRenderer->popMonitorTransformEnabled(); } // reset our data + g_pHyprRenderer->m_renderData.mouseZoomFactor = 1.f; + g_pHyprRenderer->m_renderData.mouseZoomUseMouse = true; + g_pHyprRenderer->m_renderData.blockScreenShader = false; + g_pHyprRenderer->m_renderData.currentFB.reset(); + g_pHyprRenderer->m_renderData.mainFB.reset(); + g_pHyprRenderer->m_renderData.outFB.reset(); + g_pHyprRenderer->popMonitorTransformEnabled(); + + // invalidate our render FBs to signal to the driver we don't need them anymore + if (!g_pHyprRenderer->m_renderData.pMonitor->useFP16() || *PFPINVALIDATE == 1 || (*PFPINVALIDATE == 2 && !g_pHyprRenderer->isNvidia())) { // FIXME wtf? + g_pHyprRenderer->m_renderData.pMonitor->resources()->forEachUnusedFB( + [](const auto& fb) { + fb->bind(); + GLFB(fb)->invalidate({GL_STENCIL_ATTACHMENT, GL_COLOR_ATTACHMENT0}); + }, + false); + } + m_renderData.pMonitor.reset(); - m_renderData.mouseZoomFactor = 1.f; - m_renderData.mouseZoomUseMouse = true; - m_renderData.blockScreenShader = false; - m_renderData.currentFB = nullptr; - m_renderData.mainFB = nullptr; - m_renderData.outFB = nullptr; - popMonitorTransformEnabled(); - // if we dropped to offMain, release it now. - // if there is a plugin constantly using it, this might be a bit slow, - // but I haven't seen a single plugin yet use these, so it's better to drop a bit of vram. - if (m_renderData.pCurrentMonData->offMainFB.isAllocated()) - m_renderData.pCurrentMonData->offMainFB.release(); + 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 (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) { - m_renderData.damage.set(damage_); - m_renderData.finalDamage.set(finalDamage.value_or(damage_)); -} - -// TODO notify user if bundled shader is newer than ~/.config override -static std::string loadShader(const std::string& filename) { - const auto home = Hyprutils::Path::getHome(); - if (home.has_value()) { - const auto src = NFsUtils::readFileAsString(home.value() + "/hypr/shaders/" + filename); - if (src.has_value()) - return src.value(); - } - for (auto& e : ASSET_PATHS) { - const auto src = NFsUtils::readFileAsString(std::string{e} + "/hypr/shaders/" + filename); - if (src.has_value()) - return src.value(); - } - if (SHADERS.contains(filename)) - return SHADERS.at(filename); - throw std::runtime_error(std::format("Couldn't load shader {}", filename)); -} - -static void loadShaderInclude(const std::string& filename, std::map& includes) { - includes.insert({filename, loadShader(filename)}); -} - -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); + 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."); } } -static std::string processShader(const std::string& filename, const std::map& includes) { - auto source = loadShader(filename); - processShaderIncludes(source, includes); - return source; -} +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", "inner_glow.glsl", "blurprepare.glsl", "blur1.glsl", "blur2.glsl", "blurFinish.glsl", +}; -// 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_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"); -} +// 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", "inner_glow.frag", "surface.frag", "border.frag", "glitch.frag", +}; -// shader has #include "rounding.glsl" -static void getRoundingShaderUniforms(SShader& shader) { - shader.uniformLocations[SHADER_TOP_LEFT] = glGetUniformLocation(shader.program, "topLeft"); - shader.uniformLocations[SHADER_FULL_SIZE] = glGetUniformLocation(shader.program, "fullSize"); - shader.uniformLocations[SHADER_RADIUS] = glGetUniformLocation(shader.program, "radius"); - shader.uniformLocations[SHADER_ROUNDING_POWER] = glGetUniformLocation(shader.program, "roundingPower"); -} - -bool CHyprOpenGLImpl::initShaders() { - auto shaders = makeShared(); - const bool isDynamic = m_shadersInitialized; - static const auto PCM = CConfigValue("render:cm_enabled"); +bool CHyprOpenGLImpl::initShaders(const std::string& path) { + auto shaders = makeShared(); + static const auto PCM = CConfigValue("render:cm_enabled"); try { - std::map includes; - loadShaderInclude("rounding.glsl", includes); - loadShaderInclude("CM.glsl", includes); + auto shaderLoader = makeUnique(SHADER_INCLUDES, FRAG_SHADERS, path); - shaders->TEXVERTSRC = processShader("tex300.vert", includes); - shaders->TEXVERTSRC320 = processShader("tex320.vert", includes); + shaders->TEXVERTSRC = shaderLoader->process("tex300.vert"); + shaders->TEXVERTSRC320 = shaderLoader->process("tex320.vert"); - GLuint prog; + m_cmSupported = *PCM; - if (!*PCM) - m_cmSupported = false; - else { - const auto TEXFRAGSRCCM = processShader("CM.frag", includes); - - prog = createProgram(shaders->TEXVERTSRC, TEXFRAGSRCCM, true, true); - if (m_shadersInitialized && m_cmSupported && prog == 0) - g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_CM_RELOAD_FAILED), CHyprColor{}, 15000, ICON_WARNING); - - m_cmSupported = prog > 0; - if (m_cmSupported) { - shaders->m_shCM.program = prog; - getCMShaderUniforms(shaders->m_shCM); - getRoundingShaderUniforms(shaders->m_shCM); - shaders->m_shCM.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shCM.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shCM.uniformLocations[SHADER_TEX_TYPE] = glGetUniformLocation(prog, "texType"); - shaders->m_shCM.uniformLocations[SHADER_ALPHA_MATTE] = glGetUniformLocation(prog, "texMatte"); - shaders->m_shCM.uniformLocations[SHADER_ALPHA] = glGetUniformLocation(prog, "alpha"); - shaders->m_shCM.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shCM.uniformLocations[SHADER_MATTE_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoordMatte"); - shaders->m_shCM.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shCM.uniformLocations[SHADER_DISCARD_OPAQUE] = glGetUniformLocation(prog, "discardOpaque"); - shaders->m_shCM.uniformLocations[SHADER_DISCARD_ALPHA] = glGetUniformLocation(prog, "discardAlpha"); - shaders->m_shCM.uniformLocations[SHADER_DISCARD_ALPHA_VALUE] = glGetUniformLocation(prog, "discardAlphaValue"); - shaders->m_shCM.uniformLocations[SHADER_APPLY_TINT] = glGetUniformLocation(prog, "applyTint"); - shaders->m_shCM.uniformLocations[SHADER_TINT] = glGetUniformLocation(prog, "tint"); - shaders->m_shCM.uniformLocations[SHADER_USE_ALPHA_MATTE] = glGetUniformLocation(prog, "useAlphaMatte"); - shaders->m_shCM.createVao(); - } else - Debug::log(ERR, - "WARNING: CM Shader failed compiling, color management will not work. It's likely because your GPU is an old piece of garbage, don't file bug reports " - "about this!"); - } - - const auto FRAGSHADOW = processShader("shadow.frag", includes); - const auto FRAGBORDER1 = processShader("border.frag", includes); - const auto FRAGBLURPREPARE = processShader("blurprepare.frag", includes); - const auto FRAGBLURFINISH = processShader("blurfinish.frag", includes); - const auto QUADFRAGSRC = processShader("quad.frag", includes); - const auto TEXFRAGSRCRGBA = processShader("rgba.frag", includes); - const auto TEXFRAGSRCRGBAPASSTHRU = processShader("passthru.frag", includes); - const auto TEXFRAGSRCRGBAMATTE = processShader("rgbamatte.frag", includes); - const auto FRAGGLITCH = processShader("glitch.frag", includes); - const auto TEXFRAGSRCRGBX = processShader("rgbx.frag", includes); - const auto TEXFRAGSRCEXT = processShader("ext.frag", includes); - const auto FRAGBLUR1 = processShader("blur1.frag", includes); - const auto FRAGBLUR2 = processShader("blur2.frag", includes); - - prog = createProgram(shaders->TEXVERTSRC, QUADFRAGSRC, isDynamic); - if (!prog) - return false; - shaders->m_shQUAD.program = prog; - getRoundingShaderUniforms(shaders->m_shQUAD); - shaders->m_shQUAD.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shQUAD.uniformLocations[SHADER_COLOR] = glGetUniformLocation(prog, "color"); - shaders->m_shQUAD.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shQUAD.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, TEXFRAGSRCRGBA, isDynamic); - if (!prog) - return false; - shaders->m_shRGBA.program = prog; - getRoundingShaderUniforms(shaders->m_shRGBA); - shaders->m_shRGBA.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shRGBA.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shRGBA.uniformLocations[SHADER_ALPHA_MATTE] = glGetUniformLocation(prog, "texMatte"); - shaders->m_shRGBA.uniformLocations[SHADER_ALPHA] = glGetUniformLocation(prog, "alpha"); - shaders->m_shRGBA.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shRGBA.uniformLocations[SHADER_MATTE_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoordMatte"); - shaders->m_shRGBA.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shRGBA.uniformLocations[SHADER_DISCARD_OPAQUE] = glGetUniformLocation(prog, "discardOpaque"); - shaders->m_shRGBA.uniformLocations[SHADER_DISCARD_ALPHA] = glGetUniformLocation(prog, "discardAlpha"); - shaders->m_shRGBA.uniformLocations[SHADER_DISCARD_ALPHA_VALUE] = glGetUniformLocation(prog, "discardAlphaValue"); - shaders->m_shRGBA.uniformLocations[SHADER_APPLY_TINT] = glGetUniformLocation(prog, "applyTint"); - shaders->m_shRGBA.uniformLocations[SHADER_TINT] = glGetUniformLocation(prog, "tint"); - shaders->m_shRGBA.uniformLocations[SHADER_USE_ALPHA_MATTE] = glGetUniformLocation(prog, "useAlphaMatte"); - shaders->m_shRGBA.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, TEXFRAGSRCRGBAPASSTHRU, isDynamic); - if (!prog) - return false; - shaders->m_shPASSTHRURGBA.program = prog; - shaders->m_shPASSTHRURGBA.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shPASSTHRURGBA.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shPASSTHRURGBA.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shPASSTHRURGBA.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shPASSTHRURGBA.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, TEXFRAGSRCRGBAMATTE, isDynamic); - if (!prog) - return false; - shaders->m_shMATTE.program = prog; - shaders->m_shMATTE.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shMATTE.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shMATTE.uniformLocations[SHADER_ALPHA_MATTE] = glGetUniformLocation(prog, "texMatte"); - shaders->m_shMATTE.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shMATTE.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shMATTE.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, FRAGGLITCH, isDynamic); - if (!prog) - return false; - shaders->m_shGLITCH.program = prog; - shaders->m_shGLITCH.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shGLITCH.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shGLITCH.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shGLITCH.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shGLITCH.uniformLocations[SHADER_DISTORT] = glGetUniformLocation(prog, "distort"); - shaders->m_shGLITCH.uniformLocations[SHADER_TIME] = glGetUniformLocation(prog, "time"); - shaders->m_shGLITCH.uniformLocations[SHADER_FULL_SIZE] = glGetUniformLocation(prog, "screenSize"); - shaders->m_shGLITCH.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, TEXFRAGSRCRGBX, isDynamic); - if (!prog) - return false; - shaders->m_shRGBX.program = prog; - getRoundingShaderUniforms(shaders->m_shRGBX); - shaders->m_shRGBX.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shRGBX.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shRGBX.uniformLocations[SHADER_ALPHA] = glGetUniformLocation(prog, "alpha"); - shaders->m_shRGBX.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shRGBX.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shRGBX.uniformLocations[SHADER_DISCARD_OPAQUE] = glGetUniformLocation(prog, "discardOpaque"); - shaders->m_shRGBX.uniformLocations[SHADER_DISCARD_ALPHA] = glGetUniformLocation(prog, "discardAlpha"); - shaders->m_shRGBX.uniformLocations[SHADER_DISCARD_ALPHA_VALUE] = glGetUniformLocation(prog, "discardAlphaValue"); - shaders->m_shRGBX.uniformLocations[SHADER_APPLY_TINT] = glGetUniformLocation(prog, "applyTint"); - shaders->m_shRGBX.uniformLocations[SHADER_TINT] = glGetUniformLocation(prog, "tint"); - shaders->m_shRGBX.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, TEXFRAGSRCEXT, isDynamic); - if (!prog) - return false; - shaders->m_shEXT.program = prog; - getRoundingShaderUniforms(shaders->m_shEXT); - shaders->m_shEXT.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shEXT.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shEXT.uniformLocations[SHADER_ALPHA] = glGetUniformLocation(prog, "alpha"); - shaders->m_shEXT.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shEXT.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shEXT.uniformLocations[SHADER_DISCARD_OPAQUE] = glGetUniformLocation(prog, "discardOpaque"); - shaders->m_shEXT.uniformLocations[SHADER_DISCARD_ALPHA] = glGetUniformLocation(prog, "discardAlpha"); - shaders->m_shEXT.uniformLocations[SHADER_DISCARD_ALPHA_VALUE] = glGetUniformLocation(prog, "discardAlphaValue"); - shaders->m_shEXT.uniformLocations[SHADER_APPLY_TINT] = glGetUniformLocation(prog, "applyTint"); - shaders->m_shEXT.uniformLocations[SHADER_TINT] = glGetUniformLocation(prog, "tint"); - shaders->m_shEXT.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, FRAGBLUR1, isDynamic); - if (!prog) - return false; - shaders->m_shBLUR1.program = prog; - shaders->m_shBLUR1.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shBLUR1.uniformLocations[SHADER_ALPHA] = glGetUniformLocation(prog, "alpha"); - shaders->m_shBLUR1.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shBLUR1.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shBLUR1.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shBLUR1.uniformLocations[SHADER_RADIUS] = glGetUniformLocation(prog, "radius"); - shaders->m_shBLUR1.uniformLocations[SHADER_HALFPIXEL] = glGetUniformLocation(prog, "halfpixel"); - shaders->m_shBLUR1.uniformLocations[SHADER_PASSES] = glGetUniformLocation(prog, "passes"); - shaders->m_shBLUR1.uniformLocations[SHADER_VIBRANCY] = glGetUniformLocation(prog, "vibrancy"); - shaders->m_shBLUR1.uniformLocations[SHADER_VIBRANCY_DARKNESS] = glGetUniformLocation(prog, "vibrancy_darkness"); - shaders->m_shBLUR1.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, FRAGBLUR2, isDynamic); - if (!prog) - return false; - shaders->m_shBLUR2.program = prog; - shaders->m_shBLUR2.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shBLUR2.uniformLocations[SHADER_ALPHA] = glGetUniformLocation(prog, "alpha"); - shaders->m_shBLUR2.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shBLUR2.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shBLUR2.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shBLUR2.uniformLocations[SHADER_RADIUS] = glGetUniformLocation(prog, "radius"); - shaders->m_shBLUR2.uniformLocations[SHADER_HALFPIXEL] = glGetUniformLocation(prog, "halfpixel"); - shaders->m_shBLUR2.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, FRAGBLURPREPARE, isDynamic); - if (!prog) - return false; - shaders->m_shBLURPREPARE.program = prog; - getCMShaderUniforms(shaders->m_shBLURPREPARE); - - shaders->m_shBLURPREPARE.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shBLURPREPARE.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shBLURPREPARE.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shBLURPREPARE.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shBLURPREPARE.uniformLocations[SHADER_CONTRAST] = glGetUniformLocation(prog, "contrast"); - shaders->m_shBLURPREPARE.uniformLocations[SHADER_BRIGHTNESS] = glGetUniformLocation(prog, "brightness"); - shaders->m_shBLURPREPARE.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, FRAGBLURFINISH, isDynamic); - if (!prog) - return false; - shaders->m_shBLURFINISH.program = prog; - // getCMShaderUniforms(shaders->m_shBLURFINISH); - - shaders->m_shBLURFINISH.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shBLURFINISH.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shBLURFINISH.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shBLURFINISH.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shBLURFINISH.uniformLocations[SHADER_BRIGHTNESS] = glGetUniformLocation(prog, "brightness"); - shaders->m_shBLURFINISH.uniformLocations[SHADER_NOISE] = glGetUniformLocation(prog, "noise"); - shaders->m_shBLURFINISH.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, FRAGSHADOW, isDynamic); - if (!prog) - return false; - - shaders->m_shSHADOW.program = prog; - getCMShaderUniforms(shaders->m_shSHADOW); - getRoundingShaderUniforms(shaders->m_shSHADOW); - shaders->m_shSHADOW.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shSHADOW.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shSHADOW.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shSHADOW.uniformLocations[SHADER_BOTTOM_RIGHT] = glGetUniformLocation(prog, "bottomRight"); - shaders->m_shSHADOW.uniformLocations[SHADER_RANGE] = glGetUniformLocation(prog, "range"); - shaders->m_shSHADOW.uniformLocations[SHADER_SHADOW_POWER] = glGetUniformLocation(prog, "shadowPower"); - shaders->m_shSHADOW.uniformLocations[SHADER_COLOR] = glGetUniformLocation(prog, "color"); - shaders->m_shSHADOW.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, FRAGBORDER1, isDynamic); - if (!prog) - return false; - - shaders->m_shBORDER1.program = prog; - getCMShaderUniforms(shaders->m_shBORDER1); - getRoundingShaderUniforms(shaders->m_shBORDER1); - shaders->m_shBORDER1.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shBORDER1.uniformLocations[SHADER_THICK] = glGetUniformLocation(prog, "thick"); - shaders->m_shBORDER1.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shBORDER1.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shBORDER1.uniformLocations[SHADER_BOTTOM_RIGHT] = glGetUniformLocation(prog, "bottomRight"); - shaders->m_shBORDER1.uniformLocations[SHADER_FULL_SIZE_UNTRANSFORMED] = glGetUniformLocation(prog, "fullSizeUntransformed"); - shaders->m_shBORDER1.uniformLocations[SHADER_RADIUS_OUTER] = glGetUniformLocation(prog, "radiusOuter"); - shaders->m_shBORDER1.uniformLocations[SHADER_GRADIENT] = glGetUniformLocation(prog, "gradient"); - shaders->m_shBORDER1.uniformLocations[SHADER_GRADIENT2] = glGetUniformLocation(prog, "gradient2"); - shaders->m_shBORDER1.uniformLocations[SHADER_GRADIENT_LENGTH] = glGetUniformLocation(prog, "gradientLength"); - shaders->m_shBORDER1.uniformLocations[SHADER_GRADIENT2_LENGTH] = glGetUniformLocation(prog, "gradient2Length"); - shaders->m_shBORDER1.uniformLocations[SHADER_ANGLE] = glGetUniformLocation(prog, "angle"); - shaders->m_shBORDER1.uniformLocations[SHADER_ANGLE2] = glGetUniformLocation(prog, "angle2"); - shaders->m_shBORDER1.uniformLocations[SHADER_GRADIENT_LERP] = glGetUniformLocation(prog, "gradientLerp"); - shaders->m_shBORDER1.uniformLocations[SHADER_ALPHA] = glGetUniformLocation(prog, "alpha"); - shaders->m_shBORDER1.createVao(); + g_pShaderLoader = std::move(shaderLoader); } catch (const std::exception& e) { if (!m_shadersInitialized) throw e; - Debug::log(ERR, "Shaders update failed: {}", e.what()); + Log::logger->log(Log::ERR, "Shaders update failed: {}", e.what()); return false; } m_shaders = shaders; m_shadersInitialized = true; - Debug::log(LOG, "Shaders initialized successfully."); + Log::logger->log(Log::DEBUG, "Shaders initialized successfully."); return true; } @@ -1264,68 +906,55 @@ 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; - std::ifstream infile(absolutePath(path, g_pConfigManager->getMainConfigPath())); + std::string absPath = absolutePath(path, Config::mgr()->getMainConfigPath()); + + std::error_code ec; + if (!std::filesystem::is_regular_file(absPath, ec)) { + if (ec) + ErrorOverlay::overlay()->queueError("Screen shader parser: Failed to check screen shader path: " + ec.message()); + else + ErrorOverlay::overlay()->queueError("Screen shader parser: Screen shader path is not a regular file"); + return; + } + + std::ifstream infile(absPath); if (!infile.good()) { - g_pConfigManager->addParseError("Screen shader parser: Screen shader path not found"); + ErrorOverlay::overlay()->queueError("Screen shader parser: Failed to open screen shader"); return; } std::string fragmentShader((std::istreambuf_iterator(infile)), (std::istreambuf_iterator())); - m_finalScreenShader.program = createProgram( // - fragmentShader.starts_with("#version 320 es") // do not break existing custom shaders - ? - m_shaders->TEXVERTSRC320 : - m_shaders->TEXVERTSRC, - fragmentShader, true); - - if (!m_finalScreenShader.program) { + if (!m_finalScreenShader->createProgram( // + fragmentShader.starts_with("#version 320 es") // do not break existing custom shaders + ? + m_shaders->TEXVERTSRC320 : + m_shaders->TEXVERTSRC, + fragmentShader, true)) { // Error will have been sent by now by the underlying cause return; } - m_finalScreenShader.uniformLocations[SHADER_POINTER_HIDDEN] = glGetUniformLocation(m_finalScreenShader.program, "pointer_hidden"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_KILLING] = glGetUniformLocation(m_finalScreenShader.program, "pointer_killing"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_SHAPE] = glGetUniformLocation(m_finalScreenShader.program, "pointer_shape"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_SHAPE_PREVIOUS] = glGetUniformLocation(m_finalScreenShader.program, "pointer_shape_previous"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_SWITCH_TIME] = glGetUniformLocation(m_finalScreenShader.program, "pointer_switch_time"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_PRESSED_POSITIONS] = glGetUniformLocation(m_finalScreenShader.program, "pointer_pressed_positions"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_PRESSED_TIMES] = glGetUniformLocation(m_finalScreenShader.program, "pointer_pressed_times"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_PRESSED_KILLED] = glGetUniformLocation(m_finalScreenShader.program, "pointer_pressed_killed"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_PRESSED_TOUCHED] = glGetUniformLocation(m_finalScreenShader.program, "pointer_pressed_touched"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_INACTIVE_TIMEOUT] = glGetUniformLocation(m_finalScreenShader.program, "pointer_inactive_timeout"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_LAST_ACTIVE] = glGetUniformLocation(m_finalScreenShader.program, "pointer_last_active"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_SIZE] = glGetUniformLocation(m_finalScreenShader.program, "pointer_size"); - m_finalScreenShader.uniformLocations[SHADER_POINTER] = glGetUniformLocation(m_finalScreenShader.program, "pointer_position"); - m_finalScreenShader.uniformLocations[SHADER_PROJ] = glGetUniformLocation(m_finalScreenShader.program, "proj"); - m_finalScreenShader.uniformLocations[SHADER_TEX] = glGetUniformLocation(m_finalScreenShader.program, "tex"); - m_finalScreenShader.uniformLocations[SHADER_TIME] = glGetUniformLocation(m_finalScreenShader.program, "time"); - if (m_finalScreenShader.uniformLocations[SHADER_TIME] != -1) - m_finalScreenShader.initialTime = m_globalTimer.getSeconds(); - m_finalScreenShader.uniformLocations[SHADER_WL_OUTPUT] = glGetUniformLocation(m_finalScreenShader.program, "wl_output"); - m_finalScreenShader.uniformLocations[SHADER_FULL_SIZE] = glGetUniformLocation(m_finalScreenShader.program, "screen_size"); - if (m_finalScreenShader.uniformLocations[SHADER_FULL_SIZE] == -1) - m_finalScreenShader.uniformLocations[SHADER_FULL_SIZE] = glGetUniformLocation(m_finalScreenShader.program, "screenSize"); - m_finalScreenShader.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(m_finalScreenShader.program, "texcoord"); - m_finalScreenShader.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(m_finalScreenShader.program, "pos"); + if (m_finalScreenShader->getUniformLocation(SHADER_TIME) != -1) + m_finalScreenShader->setInitialTime(g_pHyprRenderer->m_globalTimer.getSeconds()); static auto uniformRequireNoDamage = [this](eShaderUniform uniform, const std::string& name) { if (*PDT == 0) return; - if (m_finalScreenShader.uniformLocations[uniform] == -1) + if (m_finalScreenShader->getUniformLocation(uniform) == -1) return; // The screen shader uses the uniform // Since the screen shader could change every frame, damage tracking *needs* to be disabled - g_pConfigManager->addParseError(std::format("Screen shader: Screen shader uses uniform '{}', which requires debug:damage_tracking to be switched off.\n" - "WARNING:(Disabling damage tracking will *massively* increase GPU utilization!", - name)); + ErrorOverlay::overlay()->queueError(std::format("Screen shader: Screen shader uses uniform '{}', which requires debug:damage_tracking to be switched off.\n" + "WARNING:(Disabling damage tracking will *massively* increase GPU utilization!", + name)); }; // Allow glitch shader to use time uniform whighout damage tracking @@ -1342,31 +971,12 @@ 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) { - RASSERT(m_renderData.pMonitor, "Tried to render without begin()!"); - - TRACY_GPU_ZONE("RenderClear"); - - glClearColor(color.r, color.g, color.b, color.a); - - if (!m_renderData.damage.empty()) { - m_renderData.damage.forEachRect([this](const auto& RECT) { - scissor(&RECT); - glClear(GL_COLOR_BUFFER_BIT); - }); - } - - scissor(nullptr); } void CHyprOpenGLImpl::blend(bool enabled) { if (enabled) { setCapStatus(GL_BLEND, true); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // everything is premultiplied + GLCALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); // everything is premultiplied } else setCapStatus(GL_BLEND, false); @@ -1374,6 +984,7 @@ void CHyprOpenGLImpl::blend(bool enabled) { } void CHyprOpenGLImpl::scissor(const CBox& originalBox, bool transform) { + auto& m_renderData = g_pHyprRenderer->m_renderData; RASSERT(m_renderData.pMonitor, "Tried to scissor without begin()!"); // only call glScissor if the box has changed @@ -1381,11 +992,11 @@ void CHyprOpenGLImpl::scissor(const CBox& originalBox, bool transform) { if (transform) { CBox box = originalBox; - const auto TR = wlTransformToHyprutils(invertTransform(m_renderData.pMonitor->m_transform)); + const auto TR = Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)); box.transform(TR, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y); if (box != m_lastScissorBox) { - glScissor(box.x, box.y, box.width, box.height); + GLCALL(glScissor(box.x, box.y, box.width, box.height)); m_lastScissorBox = box; } @@ -1394,7 +1005,7 @@ void CHyprOpenGLImpl::scissor(const CBox& originalBox, bool transform) { } if (originalBox != m_lastScissorBox) { - glScissor(originalBox.x, originalBox.y, originalBox.width, originalBox.height); + GLCALL(glScissor(originalBox.x, originalBox.y, originalBox.width, originalBox.height)); m_lastScissorBox = originalBox; } @@ -1402,7 +1013,7 @@ void CHyprOpenGLImpl::scissor(const CBox& originalBox, bool transform) { } void CHyprOpenGLImpl::scissor(const pixman_box32* pBox, bool transform) { - RASSERT(m_renderData.pMonitor, "Tried to scissor without begin()!"); + RASSERT(g_pHyprRenderer->m_renderData.pMonitor, "Tried to scissor without begin()!"); if (!pBox) { setCapStatus(GL_SCISSOR_TEST, false); @@ -1421,7 +1032,7 @@ void CHyprOpenGLImpl::scissor(const int x, const int y, const int w, const int h void CHyprOpenGLImpl::renderRect(const CBox& box, const CHyprColor& col, SRectRenderData data) { if (!data.damage) - data.damage = &m_renderData.damage; + data.damage = &g_pHyprRenderer->m_renderData.damage; if (data.blur) renderRectWithBlurInternal(box, col, data); @@ -1433,97 +1044,70 @@ void CHyprOpenGLImpl::renderRectWithBlurInternal(const CBox& box, const CHyprCol if (data.damage->empty()) return; - CRegion damage{m_renderData.damage}; + CRegion damage{g_pHyprRenderer->m_renderData.damage}; damage.intersect(box); - CFramebuffer* POUTFB = data.xray ? &m_renderData.pCurrentMonData->blurFB : blurMainFramebufferWithDamage(data.blurA, &damage); + auto blurredBG = data.xray ? g_pHyprRenderer->m_renderData.pMonitor->resources()->m_blurFB->getTexture() : g_pHyprRenderer->blurMainFramebuffer(data.blurA, &damage); - m_renderData.currentFB->bind(); - - // make a stencil for rounded corners to work with blur - scissor(nullptr); // allow the entire window and stencil to render - glClearStencil(0); - glClear(GL_STENCIL_BUFFER_BIT); - - setCapStatus(GL_STENCIL_TEST, true); - - glStencilFunc(GL_ALWAYS, 1, 0xFF); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - renderRect(box, CHyprColor(0, 0, 0, 0), SRectRenderData{.round = data.round, .roundingPower = data.roundingPower}); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - - glStencilFunc(GL_EQUAL, 1, 0xFF); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - - scissor(box); - CBox MONITORBOX = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}; - pushMonitorTransformEnabled(true); - const auto SAVEDRENDERMODIF = m_renderData.renderModif; - m_renderData.renderModif = {}; // fix shit - renderTexture(POUTFB->getTexture(), MONITORBOX, - STextureRenderData{.damage = &damage, .a = data.blurA, .round = 0, .roundingPower = 2.0f, .allowCustomUV = false, .allowDim = false, .noAA = false}); - 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); + CBox MONITORBOX = {0, 0, g_pHyprRenderer->m_renderData.pMonitor->m_transformedSize.x, g_pHyprRenderer->m_renderData.pMonitor->m_transformedSize.y}; + g_pHyprRenderer->pushMonitorTransformEnabled(true); + const auto SAVEDRENDERMODIF = g_pHyprRenderer->m_renderData.renderModif; + g_pHyprRenderer->m_renderData.renderModif = {}; // fix shit + renderTexture(blurredBG, MONITORBOX, + STextureRenderData{.damage = &damage, .a = data.blurA, .round = data.round, .roundingPower = 2.F, .allowCustomUV = false, .allowDim = false, .noAA = false}); + g_pHyprRenderer->popMonitorTransformEnabled(); + g_pHyprRenderer->m_renderData.renderModif = SAVEDRENDERMODIF; renderRectWithDamageInternal(box, col, data); } void CHyprOpenGLImpl::renderRectWithDamageInternal(const CBox& box, const CHyprColor& col, const SRectRenderData& data) { + auto& m_renderData = g_pHyprRenderer->m_renderData; RASSERT((box.width > 0 && box.height > 0), "Tried to render rect with width/height < 0!"); RASSERT(m_renderData.pMonitor, "Tried to render rect without begin()!"); TRACY_GPU_ZONE("RenderRectWithDamage"); CBox newBox = box; - m_renderData.renderModif.applyToBox(newBox); + g_pHyprRenderer->m_renderData.renderModif.applyToBox(newBox); - Mat3x3 matrix = m_renderData.monitorProjection.projectBox( - newBox, wlTransformToHyprutils(invertTransform(!m_monitorTransformEnabled ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform)), newBox.rot); - Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); + const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(newBox); - useProgram(m_shaders->m_shQUAD.program); - m_shaders->m_shQUAD.setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + auto shader = useShader(getShaderVariant(SH_FRAG_QUAD, (data.round > 0 ? SH_FEAT_ROUNDING : 0) | globalFeatures())); + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); // premultiply the color as well as we don't work with straight alpha - m_shaders->m_shQUAD.setUniformFloat4(SHADER_COLOR, col.r * col.a, col.g * col.a, col.b * col.a, col.a); + shader->setUniformFloat4(SHADER_COLOR, col.r * col.a, col.g * col.a, col.b * col.a, col.a); CBox transformedBox = box; - transformedBox.transform(wlTransformToHyprutils(invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, + 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 - m_shaders->m_shQUAD.setUniformFloat2(SHADER_TOP_LEFT, sc(TOPLEFT.x), sc(TOPLEFT.y)); - m_shaders->m_shQUAD.setUniformFloat2(SHADER_FULL_SIZE, sc(FULLSIZE.x), sc(FULLSIZE.y)); - m_shaders->m_shQUAD.setUniformFloat(SHADER_RADIUS, data.round); - m_shaders->m_shQUAD.setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower); + shader->setUniformFloat2(SHADER_TOP_LEFT, sc(TOPLEFT.x), sc(TOPLEFT.y)); + shader->setUniformFloat2(SHADER_FULL_SIZE, sc(FULLSIZE.x), sc(FULLSIZE.y)); + shader->setUniformFloat(SHADER_RADIUS, data.round); + shader->setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower); - glBindVertexArray(m_shaders->m_shQUAD.uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); - if (m_renderData.clipBox.width != 0 && m_renderData.clipBox.height != 0) { - CRegion damageClip{m_renderData.clipBox.x, m_renderData.clipBox.y, m_renderData.clipBox.width, m_renderData.clipBox.height}; + if (g_pHyprRenderer->m_renderData.clipBox.width != 0 && g_pHyprRenderer->m_renderData.clipBox.height != 0) { + CRegion damageClip{g_pHyprRenderer->m_renderData.clipBox.x, g_pHyprRenderer->m_renderData.clipBox.y, g_pHyprRenderer->m_renderData.clipBox.width, + g_pHyprRenderer->m_renderData.clipBox.height}; damageClip.intersect(*data.damage); if (!damageClip.empty()) { damageClip.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } } else { data.damage->forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } @@ -1532,14 +1116,14 @@ void CHyprOpenGLImpl::renderRectWithDamageInternal(const CBox& box, const CHyprC scissor(nullptr); } -void CHyprOpenGLImpl::renderTexture(SP tex, const CBox& box, STextureRenderData data) { - RASSERT(m_renderData.pMonitor, "Tried to render texture without begin()!"); +void CHyprOpenGLImpl::renderTexture(SP tex, const CBox& box, STextureRenderData data) { + RASSERT(g_pHyprRenderer->m_renderData.pMonitor, "Tried to render texture without begin()!"); if (!data.damage) { - if (m_renderData.damage.empty()) + if (g_pHyprRenderer->m_renderData.damage.empty()) return; - data.damage = &m_renderData.damage; + data.damage = &g_pHyprRenderer->m_renderData.damage; } if (data.blur) @@ -1552,202 +1136,104 @@ void CHyprOpenGLImpl::renderTexture(SP tex, const CBox& box, STextureR static std::map, std::array> primariesConversionCache; -static bool isSDR2HDR(const NColorManagement::SImageDescription& imageDescription, const NColorManagement::SImageDescription& targetImageDescription) { - // might be too strict - return (imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB || - imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22) && - (targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ || - targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_HLG); -} +void CHyprOpenGLImpl::passCMUniforms(WP shader, const NColorManagement::PImageDescription imageDescription, + const NColorManagement::PImageDescription targetImageDescription, bool modifySDR, float sdrMinLuminance, int sdrMaxLuminance, + const SCMSettings& settings) { + 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); -void CHyprOpenGLImpl::passCMUniforms(SShader& shader, const NColorManagement::SImageDescription& imageDescription, - const NColorManagement::SImageDescription& targetImageDescription, bool modifySDR, float sdrMinLuminance, int sdrMaxLuminance) { - static auto PSDREOTF = CConfigValue("render:cm_sdr_eotf"); + 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]); - if (m_renderData.surface.valid() && - ((!m_renderData.surface->m_colorManagement.valid() && *PSDREOTF >= 1) || - (*PSDREOTF == 2 && m_renderData.surface->m_colorManagement.valid() && - imageDescription.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.transferFunction); - - shader.setUniformInt(SHADER_TARGET_TF, targetImageDescription.transferFunction); - - const auto targetPrimaries = targetImageDescription.primariesNameSet || targetImageDescription.primaries == SPCPRimaries{} ? - getPrimaries(targetImageDescription.primariesNamed) : - targetImageDescription.primaries; - - const std::array glTargetPrimaries = { - targetPrimaries.red.x, targetPrimaries.red.y, targetPrimaries.green.x, targetPrimaries.green.y, - targetPrimaries.blue.x, targetPrimaries.blue.y, targetPrimaries.white.x, targetPrimaries.white.y, - }; - shader.setUniformMatrix4x2fv(SHADER_TARGET_PRIMARIES, 1, false, glTargetPrimaries); - - const bool needsSDRmod = modifySDR && isSDR2HDR(imageDescription, targetImageDescription); - - shader.setUniformFloat2(SHADER_SRC_TF_RANGE, imageDescription.getTFMinLuminance(needsSDRmod ? sdrMinLuminance : -1), - imageDescription.getTFMaxLuminance(needsSDRmod ? sdrMaxLuminance : -1)); - shader.setUniformFloat2(SHADER_DST_TF_RANGE, targetImageDescription.getTFMinLuminance(needsSDRmod ? sdrMinLuminance : -1), - targetImageDescription.getTFMaxLuminance(needsSDRmod ? sdrMaxLuminance : -1)); - - const float maxLuminance = imageDescription.luminances.max > 0 ? imageDescription.luminances.max : imageDescription.luminances.reference; - shader.setUniformFloat(SHADER_MAX_LUMINANCE, maxLuminance * targetImageDescription.luminances.reference / imageDescription.luminances.reference); - shader.setUniformFloat(SHADER_DST_MAX_LUMINANCE, targetImageDescription.luminances.max > 0 ? targetImageDescription.luminances.max : 10000); - shader.setUniformFloat(SHADER_DST_REF_LUMINANCE, targetImageDescription.luminances.reference); - 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.getId(), targetImageDescription.getId()); - if (!primariesConversionCache.contains(cacheKey)) { - const auto mat = imageDescription.getPrimaries().convertMatrix(targetImageDescription.getPrimaries()).mat(); - const std::array glConvertMatrix = { + const auto mat = settings.dstPrimaries2XYZ; + const std::array glTargetPrimariesXYZ = { mat[0][0], mat[1][0], mat[2][0], // mat[0][1], mat[1][1], mat[2][1], // mat[0][2], mat[1][2], mat[2][2], // }; - primariesConversionCache.insert(std::make_pair(cacheKey, glConvertMatrix)); + shader->setUniformMatrix3fv(SHADER_TARGET_PRIMARIES_XYZ, 1, false, glTargetPrimariesXYZ); + } else { + // TODO: this sucks + GLCALL(glActiveTexture(GL_TEXTURE8)); + targetImageDescription->value().icc.lutTexture->bind(); + + shader->setUniformInt(SHADER_LUT_3D, 8); + shader->setUniformFloat(SHADER_LUT_SIZE, targetImageDescription->value().icc.lutSize); + + GLCALL(glActiveTexture(GL_TEXTURE0)); } - shader.setUniformMatrix3fv(SHADER_CONVERT_MATRIX, 1, false, primariesConversionCache[cacheKey]); } -void CHyprOpenGLImpl::passCMUniforms(SShader& shader, const SImageDescription& imageDescription) { - passCMUniforms(shader, imageDescription, m_renderData.pMonitor->m_imageDescription, true, m_renderData.pMonitor->m_sdrMinLuminance, m_renderData.pMonitor->m_sdrMaxLuminance); +void CHyprOpenGLImpl::passCMUniforms(WP shader, const NColorManagement::PImageDescription imageDescription, + const NColorManagement::PImageDescription targetImageDescription, bool modifySDR, float sdrMinLuminance, int sdrMaxLuminance) { + const auto settings = g_pHyprRenderer->getCMSettings(imageDescription, targetImageDescription, + g_pHyprRenderer->m_renderData.surface.valid() ? g_pHyprRenderer->m_renderData.surface.lock() : nullptr, modifySDR, + sdrMinLuminance, sdrMaxLuminance); + passCMUniforms(shader, imageDescription, targetImageDescription, modifySDR, sdrMinLuminance, sdrMaxLuminance, settings); } -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!"); +void CHyprOpenGLImpl::passCMUniforms(WP shader, const PImageDescription imageDescription) { + passCMUniforms(shader, imageDescription, g_pHyprRenderer->workBufferImageDescription(), true, g_pHyprRenderer->m_renderData.pMonitor->m_sdrMinLuminance, + g_pHyprRenderer->m_renderData.pMonitor->m_sdrMaxLuminance); +} - 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); +void CHyprOpenGLImpl::passCMUniforms(WP shader, const PImageDescription imageDescription, const SCMSettings& settings) { + passCMUniforms(shader, imageDescription, g_pHyprRenderer->workBufferImageDescription(), true, g_pHyprRenderer->m_renderData.pMonitor->m_sdrMinLuminance, + g_pHyprRenderer->m_renderData.pMonitor->m_sdrMaxLuminance, settings); +} +WP CHyprOpenGLImpl::renderToOutputInternal() { static const auto PDT = CConfigValue("debug:damage_tracking"); - static const auto PPASS = CConfigValue("render:cm_fs_passthrough"); - static const auto PENABLECM = CConfigValue("render:cm_enabled"); static const auto PCURSORTIMEOUT = CConfigValue("cursor:inactive_timeout"); - // get the needed transform for this texture - const bool TRANSFORMS_MATCH = wlTransformToHyprutils(m_renderData.pMonitor->m_transform) == tex->m_transform; // FIXME: combine them properly!!! - eTransform TRANSFORM = HYPRUTILS_TRANSFORM_NORMAL; - if (m_monitorTransformEnabled || TRANSFORMS_MATCH) - TRANSFORM = wlTransformToHyprutils(invertTransform(m_renderData.pMonitor->m_transform)); + auto& m_renderData = g_pHyprRenderer->m_renderData; - Mat3x3 matrix = m_renderData.monitorProjection.projectBox(newBox, TRANSFORM, newBox.rot); - Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); + WP shader = + g_pHyprRenderer->m_crashingInProgress ? getShaderVariant(SH_FRAG_GLITCH) : (m_finalScreenShader->program() ? m_finalScreenShader : getShaderVariant(SH_FRAG_PASSTHRURGBA)); - SShader* shader = nullptr; + shader = useShader(shader); - 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 - - auto imageDescription = m_renderData.surface.valid() && m_renderData.surface->m_colorManagement.valid() ? - m_renderData.surface->m_colorManagement->imageDescription() : - (data.cmBackToSRGB ? data.cmBackToSRGBSource->m_imageDescription : SImageDescription{}); - - const bool skipCM = !*PENABLECM || !m_cmSupported /* CM unsupported or disabled */ - || m_renderData.pMonitor->doesNoShaderCM() /* no shader needed */ - || (imageDescription == m_renderData.pMonitor->m_imageDescription && !data.cmBackToSRGB) /* Source and target have the same image description */ - || (((*PPASS && canPassHDRSurface) || (*PPASS == 1 && !isHDRSurface)) && 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) { - // revert luma changes to avoid black screenshots. - // this will likely not be 1:1, and might cause screenshots to be too bright, but it's better than pitch black. - imageDescription.luminances = {}; - static auto PSDREOTF = CConfigValue("render:cm_sdr_eotf"); - auto chosenSdrEotf = *PSDREOTF > 0 ? NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22 : NColorManagement::CM_TRANSFER_FUNCTION_SRGB; - passCMUniforms(*shader, imageDescription, NColorManagement::SImageDescription{.transferFunction = chosenSdrEotf}, true, -1, -1); - } else - passCMUniforms(*shader, imageDescription); - } - - shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - shader->setUniformInt(SHADER_TEX, 0); - - if ((usingFinalShader && *PDT == 0) || CRASHING) - shader->setUniformFloat(SHADER_TIME, m_globalTimer.getSeconds() - shader->initialTime); - else if (usingFinalShader) + if (*PDT == 0 || g_pHyprRenderer->m_crashingInProgress) + shader->setUniformFloat(SHADER_TIME, g_pHyprRenderer->m_globalTimer.getSeconds() - shader->getInitialTime()); + else shader->setUniformFloat(SHADER_TIME, 0.f); - if (usingFinalShader) { - shader->setUniformInt(SHADER_WL_OUTPUT, m_renderData.pMonitor->m_id); - shader->setUniformFloat2(SHADER_FULL_SIZE, m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y); - shader->setUniformFloat(SHADER_POINTER_INACTIVE_TIMEOUT, *PCURSORTIMEOUT); - shader->setUniformInt(SHADER_POINTER_HIDDEN, g_pHyprRenderer->m_cursorHiddenByCondition); - shader->setUniformInt(SHADER_POINTER_KILLING, g_pInputManager->getClickMode() == CLICKMODE_KILL); - shader->setUniformInt(SHADER_POINTER_SHAPE, g_pHyprRenderer->m_lastCursorData.shape); - shader->setUniformInt(SHADER_POINTER_SHAPE_PREVIOUS, g_pHyprRenderer->m_lastCursorData.shapePrevious); - shader->setUniformFloat(SHADER_POINTER_SIZE, g_pCursorManager->getScaledSize()); - } + shader->setUniformInt(SHADER_WL_OUTPUT, m_renderData.pMonitor->m_id); + shader->setUniformFloat2(SHADER_FULL_SIZE, m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y); + shader->setUniformFloat(SHADER_POINTER_INACTIVE_TIMEOUT, *PCURSORTIMEOUT); + shader->setUniformInt(SHADER_POINTER_HIDDEN, g_pHyprRenderer->m_cursorHiddenByCondition); + shader->setUniformInt(SHADER_POINTER_KILLING, g_pInputManager->getClickMode() == CLICKMODE_KILL); + shader->setUniformInt(SHADER_POINTER_SHAPE, g_pHyprRenderer->m_lastCursorData.shape); + shader->setUniformInt(SHADER_POINTER_SHAPE_PREVIOUS, g_pHyprRenderer->m_lastCursorData.shapePrevious); + shader->setUniformFloat(SHADER_POINTER_SIZE, g_pCursorManager->getScaledSize()); - if (usingFinalShader && *PDT == 0) { + if (*PDT == 0) { PHLMONITORREF pMonitor = m_renderData.pMonitor; Vector2D p = ((g_pInputManager->getMouseCoordsInternal() - pMonitor->m_position) * pMonitor->m_scale); - p = p.transform(wlTransformToHyprutils(pMonitor->m_transform), pMonitor->m_pixelSize); + p = p.transform(Math::wlTransformToHyprutils(pMonitor->m_transform), pMonitor->m_pixelSize); shader->setUniformFloat2(SHADER_POINTER, p.x / pMonitor->m_pixelSize.x, p.y / pMonitor->m_pixelSize.y); std::vector pressedPos = m_pressedHistoryPositions | std::views::transform([&](const Vector2D& vec) { Vector2D pPressed = ((vec - pMonitor->m_position) * pMonitor->m_scale); - pPressed = pPressed.transform(wlTransformToHyprutils(pMonitor->m_transform), pMonitor->m_pixelSize); + pPressed = pPressed.transform(Math::wlTransformToHyprutils(pMonitor->m_transform), pMonitor->m_pixelSize); return std::array{pPressed.x / pMonitor->m_pixelSize.x, pPressed.y / pMonitor->m_pixelSize.y}; }) | std::views::join | std::ranges::to>(); @@ -1765,7 +1251,7 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c shader->setUniformFloat(SHADER_POINTER_LAST_ACTIVE, g_pInputManager->m_lastCursorMovement.getSeconds()); shader->setUniformFloat(SHADER_POINTER_SWITCH_TIME, g_pHyprRenderer->m_lastCursorData.switchedTimer.getSeconds()); - } else if (usingFinalShader) { + } else { shader->setUniformFloat2(SHADER_POINTER, 0.f, 0.f); static const std::vector pressedPosDefault(POINTER_PRESSED_HISTORY_LENGTH * 2uz, 0.f); @@ -1779,135 +1265,310 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c shader->setUniformFloat(SHADER_POINTER_SWITCH_TIME, 0.f); } - if (CRASHING) { + if (g_pHyprRenderer->m_crashingInProgress) { shader->setUniformFloat(SHADER_DISTORT, g_pHyprRenderer->m_crashingDistort); shader->setUniformFloat2(SHADER_FULL_SIZE, m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y); } - if (!usingFinalShader) { - shader->setUniformFloat(SHADER_ALPHA, alpha); + return shader; +} - if (data.discardActive) { - shader->setUniformInt(SHADER_DISCARD_OPAQUE, !!(m_renderData.discardMode & DISCARD_OPAQUE)); - shader->setUniformInt(SHADER_DISCARD_ALPHA, !!(m_renderData.discardMode & DISCARD_ALPHA)); - shader->setUniformFloat(SHADER_DISCARD_ALPHA_VALUE, m_renderData.discardOpacity); - } else { - shader->setUniformInt(SHADER_DISCARD_OPAQUE, 0); - shader->setUniformInt(SHADER_DISCARD_ALPHA, 0); +WP CHyprOpenGLImpl::renderToFBInternal(SP tex, const STextureRenderData& data, eTextureType texType, const CBox& newBox) { + static const auto PENABLECM = CConfigValue("render:cm_enabled"); + static auto PBLEND = CConfigValue("render:use_shader_blur_blend"); + + auto& m_renderData = g_pHyprRenderer->m_renderData; + + 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 | globalFeatures()); break; // might be unused + default: RASSERT(false, "tex->m_iTarget unsupported!"); + } + + if (data.finalMonitorCM || (g_pHyprRenderer->m_renderData.currentWindow && g_pHyprRenderer->m_renderData.currentWindow->m_ruleApplicator->RGBX().valueOrDefault())) + shaderFeatures &= ~SH_FEAT_RGBA; + + const auto surface = g_pHyprRenderer->m_renderData.surface; + const auto WORK_BUFFER_IMAGE_DESCRIPTION = g_pHyprRenderer->workBufferImageDescription(); + + // chosenSdrEotf contains the valid eotf for this display + + const auto SOURCE_IMAGE_DESCRIPTION = [&] { + if (tex->m_imageDescription) + return tex->m_imageDescription; + + // if valid CM surface, use that as a source + if (surface.valid() && surface->m_colorManagement.valid()) + return CImageDescription::from(surface->m_colorManagement->imageDescription()); + + if (data.cmBackToSRGB) + return g_pHyprRenderer->m_renderData.pMonitor->m_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.finalMonitorCM) // NOLINTNEXTLINE + return WORK_BUFFER_IMAGE_DESCRIPTION; + + // otherwise, default + return getDefaultImageDescription(); + }(); + + const auto TARGET_IMAGE_DESCRIPTION = [&] { + if (g_pHyprRenderer->m_renderData.currentFB->imageDescription()) + return g_pHyprRenderer->m_renderData.currentFB->imageDescription(); + + // if we are CM'ing back, use default sRGB + if (data.cmBackToSRGB) + return getDefaultImageDescription(); + + // for final CM, use the target description + if (data.finalMonitorCM) + return g_pHyprRenderer->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 skipCM = !*PENABLECM || !m_cmSupported /* CM unsupported or disabled */ + || g_pHyprRenderer->m_renderData.pMonitor->doesNoShaderCM() /* no shader needed */ + || !SOURCE_IMAGE_DESCRIPTION->needsCM(TARGET_IMAGE_DESCRIPTION) /* Source and target have matching image descriptions */ + ; + + if (g_pHyprRenderer->m_renderData.pMonitor->needsACopyFB()) + Log::logger->log(Log::TRACE, "CM: render to FB skip={} {} -> {}", skipCM, SOURCE_IMAGE_DESCRIPTION->value(), TARGET_IMAGE_DESCRIPTION->value()); + + if (data.allowDim && g_pHyprRenderer->m_renderData.currentWindow && + (g_pHyprRenderer->m_renderData.currentWindow->m_notRespondingTint->value() > 0 || g_pHyprRenderer->m_renderData.currentWindow->m_dimPercent->value() > 0)) + shaderFeatures |= SH_FEAT_TINT; + + if (data.round > 0) + shaderFeatures |= SH_FEAT_ROUNDING; + + if (!skipCM) { + const auto settings = + g_pHyprRenderer->getCMSettings(SOURCE_IMAGE_DESCRIPTION, TARGET_IMAGE_DESCRIPTION, surface.valid() ? surface.lock() : nullptr, true, + g_pHyprRenderer->m_renderData.pMonitor->m_sdrMinLuminance, g_pHyprRenderer->m_renderData.pMonitor->m_sdrMaxLuminance, true); + + shaderFeatures |= SH_FEAT_CM; + + if (TARGET_IMAGE_DESCRIPTION->value().icc.present) + shaderFeatures |= SH_FEAT_ICC; + else { + if (settings.needsTonemap) + shaderFeatures |= SH_FEAT_TONEMAP; + + if (!data.finalMonitorCM && settings.needsSDRmod) + shaderFeatures |= SH_FEAT_SDR_MOD; } + + if (!shader) + shader = getShaderVariant(SH_FRAG_SURFACE, shaderFeatures | globalFeatures()); + shader = useShader(shader); + + passCMUniforms(shader, SOURCE_IMAGE_DESCRIPTION, TARGET_IMAGE_DESCRIPTION, true, g_pHyprRenderer->m_renderData.pMonitor->m_sdrMinLuminance, + g_pHyprRenderer->m_renderData.pMonitor->m_sdrMaxLuminance, settings); + } else { + if (!shader) + shader = getShaderVariant(SH_FRAG_SURFACE, shaderFeatures | globalFeatures()); + shader = useShader(shader); + } + + shader->setUniformFloat(SHADER_ALPHA, alpha); + + if (shaderFeatures & SH_FEAT_BLUR) { + shader->setUniformInt(SHADER_BLURRED_BG, 1); + shader->setUniformFloat2(SHADER_UV_OFFSET, newBox.x / data.blurredBG->m_size.x, newBox.y / data.blurredBG->m_size.y); + shader->setUniformFloat2(SHADER_UV_SIZE, newBox.width / data.blurredBG->m_size.x, newBox.height / data.blurredBG->m_size.y); + + glActiveTexture(GL_TEXTURE0 + 1); + data.blurredBG->bind(); + } + + if (data.discardActive) { + shader->setUniformInt(SHADER_DISCARD_OPAQUE, !!(data.discardMode & DISCARD_OPAQUE)); + shader->setUniformInt(SHADER_DISCARD_ALPHA, !!(data.discardMode & DISCARD_ALPHA)); + shader->setUniformFloat(SHADER_DISCARD_ALPHA_VALUE, data.discardOpacity); + } else { + shader->setUniformInt(SHADER_DISCARD_OPAQUE, 0); + shader->setUniformInt(SHADER_DISCARD_ALPHA, 0); } CBox transformedBox = newBox; - transformedBox.transform(wlTransformToHyprutils(invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, + transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y); const auto TOPLEFT = Vector2D(transformedBox.x, transformedBox.y); const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height); + // Rounded corners + shader->setUniformFloat2(SHADER_TOP_LEFT, TOPLEFT.x, TOPLEFT.y); + shader->setUniformFloat2(SHADER_FULL_SIZE, FULLSIZE.x, FULLSIZE.y); + shader->setUniformFloat(SHADER_RADIUS, data.round); + shader->setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower); - if (!usingFinalShader) { - // Rounded corners - shader->setUniformFloat2(SHADER_TOP_LEFT, TOPLEFT.x, TOPLEFT.y); - shader->setUniformFloat2(SHADER_FULL_SIZE, FULLSIZE.x, FULLSIZE.y); - shader->setUniformFloat(SHADER_RADIUS, data.round); - shader->setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower); - - if (data.allowDim && m_renderData.currentWindow) { - if (m_renderData.currentWindow->m_notRespondingTint->value() > 0) { - const auto DIM = m_renderData.currentWindow->m_notRespondingTint->value(); - shader->setUniformInt(SHADER_APPLY_TINT, 1); - shader->setUniformFloat3(SHADER_TINT, 1.f - DIM, 1.f - DIM, 1.f - DIM); - } else if (m_renderData.currentWindow->m_dimPercent->value() > 0) { - shader->setUniformInt(SHADER_APPLY_TINT, 1); - const auto DIM = m_renderData.currentWindow->m_dimPercent->value(); - shader->setUniformFloat3(SHADER_TINT, 1.f - DIM, 1.f - DIM, 1.f - DIM); - } else - shader->setUniformInt(SHADER_APPLY_TINT, 0); + if (data.allowDim && g_pHyprRenderer->m_renderData.currentWindow) { + if (g_pHyprRenderer->m_renderData.currentWindow->m_notRespondingTint->value() > 0) { + const auto DIM = g_pHyprRenderer->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 (g_pHyprRenderer->m_renderData.currentWindow->m_dimPercent->value() > 0) { + shader->setUniformInt(SHADER_APPLY_TINT, 1); + const auto DIM = g_pHyprRenderer->m_renderData.currentWindow->m_dimPercent->value(); + shader->setUniformFloat3(SHADER_TINT, 1.f - DIM, 1.f - DIM, 1.f - DIM); } else shader->setUniformInt(SHADER_APPLY_TINT, 0); - } + } else + shader->setUniformInt(SHADER_APPLY_TINT, 0); - glBindVertexArray(shader->uniformLocations[SHADER_SHADER_VAO]); - if (data.allowCustomUV && m_renderData.primarySurfaceUVTopLeft != Vector2D(-1, -1)) { - const float customUVs[] = { - m_renderData.primarySurfaceUVBottomRight.x, m_renderData.primarySurfaceUVTopLeft.y, m_renderData.primarySurfaceUVTopLeft.x, - m_renderData.primarySurfaceUVTopLeft.y, m_renderData.primarySurfaceUVBottomRight.x, m_renderData.primarySurfaceUVBottomRight.y, - m_renderData.primarySurfaceUVTopLeft.x, m_renderData.primarySurfaceUVBottomRight.y, - }; + return shader; +} - glBindBuffer(GL_ARRAY_BUFFER, shader->uniformLocations[SHADER_SHADER_VBO_UV]); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(customUVs), customUVs); +void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, const STextureRenderData& data) { + RASSERT(g_pHyprRenderer->m_renderData.pMonitor, "Tried to render texture without begin()!"); + RASSERT(tex, "Attempted to draw nullptr texture!"); + RASSERT(tex->ok(), "Attempted to draw invalid texture!"); + + TRACY_GPU_ZONE("RenderTextureInternalWithDamage"); + + if (data.damage->empty()) + return; + + CBox newBox = box; + g_pHyprRenderer->m_renderData.renderModif.applyToBox(newBox); + + // get the needed transform for this texture + const auto MONITOR_INVERTED = Math::wlTransformToHyprutils(Math::invertTransform(g_pHyprRenderer->m_renderData.pMonitor->m_transform)); + Hyprutils::Math::eTransform TRANSFORM = tex->m_transform; + + if (g_pHyprRenderer->monitorTransformEnabled()) + TRANSFORM = Math::composeTransform(MONITOR_INVERTED, TRANSFORM); + + const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(newBox, TRANSFORM); + + const bool renderToOutput = m_applyFinalShader && g_pHyprRenderer->workBufferImageDescription()->id() == g_pHyprRenderer->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 (g_pHyprRenderer->m_renderData.useNearestNeighbor) { + tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); + tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); } else { - glBindBuffer(GL_ARRAY_BUFFER, shader->uniformLocations[SHADER_SHADER_VBO_UV]); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(fullVerts), fullVerts); + tex->setTexParameter(GL_TEXTURE_MAG_FILTER, tex->magFilter); + tex->setTexParameter(GL_TEXTURE_MIN_FILTER, tex->minFilter); } - if (!m_renderData.clipBox.empty() || !m_renderData.clipRegion.empty()) { - CRegion damageClip = m_renderData.clipBox; + auto shader = renderToOutput ? renderToOutputInternal() : renderToFBInternal(tex, data, tex->m_type, newBox); - if (!m_renderData.clipRegion.empty()) { - if (m_renderData.clipBox.empty()) - damageClip = m_renderData.clipRegion; + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniformInt(SHADER_TEX, 0); + GLCALL(glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO))); + GLCALL(glBindBuffer(GL_ARRAY_BUFFER, shader->getUniformLocation(SHADER_SHADER_VBO))); + + // this tells GPU can keep reading the old block for previous draws while the CPU writes to a new one. + // to avoid stalls if renderTextureInternal is called multiple times on same renderpass + // at the cost of some temporar vram usage. + glBufferData(GL_ARRAY_BUFFER, sizeof(fullVerts), nullptr, GL_DYNAMIC_DRAW); + + auto verts = fullVerts; + + if (data.allowCustomUV && data.primarySurfaceUVTopLeft != Vector2D(-1, -1)) { + const float u0 = data.primarySurfaceUVTopLeft.x; + const float v0 = data.primarySurfaceUVTopLeft.y; + const float u1 = data.primarySurfaceUVBottomRight.x; + const float v1 = data.primarySurfaceUVBottomRight.y; + + verts[0].u = u0; + verts[0].v = v0; + verts[1].u = u0; + verts[1].v = v1; + verts[2].u = u1; + verts[2].v = v0; + verts[3].u = u1; + verts[3].v = v1; + } + + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts.data()); + + if (!g_pHyprRenderer->m_renderData.clipBox.empty() || !data.clipRegion.empty()) { + CRegion damageClip = g_pHyprRenderer->m_renderData.clipBox; + + if (!data.clipRegion.empty()) { + if (g_pHyprRenderer->m_renderData.clipBox.empty()) + damageClip = data.clipRegion; else - damageClip.intersect(m_renderData.clipRegion); + damageClip.intersect(data.clipRegion); } if (!damageClip.empty()) { damageClip.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } } else { data.damage->forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); + GLCALL(glBindVertexArray(0)); + GLCALL(glBindBuffer(GL_ARRAY_BUFFER, 0)); tex->unbind(); } -void CHyprOpenGLImpl::renderTexturePrimitive(SP tex, const CBox& box) { - RASSERT(m_renderData.pMonitor, "Tried to render texture without begin()!"); - RASSERT((tex->m_texID > 0), "Attempted to draw nullptr texture!"); +void CHyprOpenGLImpl::renderTexturePrimitive(SP tex, const CBox& box) { + RASSERT(g_pHyprRenderer->m_renderData.pMonitor, "Tried to render texture without begin()!"); + RASSERT((tex->ok()), "Attempted to draw nullptr texture!"); TRACY_GPU_ZONE("RenderTexturePrimitive"); - if (m_renderData.damage.empty()) + if (g_pHyprRenderer->m_renderData.damage.empty()) return; CBox newBox = box; - m_renderData.renderModif.applyToBox(newBox); + g_pHyprRenderer->m_renderData.renderModif.applyToBox(newBox); // get transform - const auto TRANSFORM = wlTransformToHyprutils(invertTransform(!m_monitorTransformEnabled ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform)); - Mat3x3 matrix = m_renderData.monitorProjection.projectBox(newBox, TRANSFORM, newBox.rot); - Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); - - SShader* shader = &m_shaders->m_shPASSTHRURGBA; + const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(newBox); glActiveTexture(GL_TEXTURE0); tex->bind(); // ensure the final blit uses the desired sampling filter // when cursor zoom is active we want nearest-neighbor (no anti-aliasing) - if (m_renderData.useNearestNeighbor) { + if (g_pHyprRenderer->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); + tex->setTexParameter(GL_TEXTURE_MAG_FILTER, tex->magFilter); + tex->setTexParameter(GL_TEXTURE_MIN_FILTER, tex->minFilter); } - useProgram(shader->program); + auto shader = useShader(getShaderVariant(SH_FRAG_PASSTHRURGBA)); shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); shader->setUniformInt(SHADER_TEX, 0); - glBindVertexArray(shader->uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); - m_renderData.damage.forEachRect([this](const auto& RECT) { - scissor(&RECT); + g_pHyprRenderer->m_renderData.damage.forEachRect([this](const auto& RECT) { + scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); @@ -1916,26 +1577,19 @@ void CHyprOpenGLImpl::renderTexturePrimitive(SP tex, const CBox& box) tex->unbind(); } -void CHyprOpenGLImpl::renderTextureMatte(SP tex, const CBox& box, CFramebuffer& matte) { - RASSERT(m_renderData.pMonitor, "Tried to render texture without begin()!"); - RASSERT((tex->m_texID > 0), "Attempted to draw nullptr texture!"); +void CHyprOpenGLImpl::renderTextureMatte(SP tex, const CBox& box, SP matte) { + RASSERT(g_pHyprRenderer->m_renderData.pMonitor, "Tried to render texture without begin()!"); + RASSERT((tex->ok()), "Attempted to draw nullptr texture!"); TRACY_GPU_ZONE("RenderTextureMatte"); - if (m_renderData.damage.empty()) - return; - CBox newBox = box; - m_renderData.renderModif.applyToBox(newBox); + g_pHyprRenderer->m_renderData.renderModif.applyToBox(newBox); // get transform - const auto TRANSFORM = wlTransformToHyprutils(invertTransform(!m_monitorTransformEnabled ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform)); - Mat3x3 matrix = m_renderData.monitorProjection.projectBox(newBox, TRANSFORM, newBox.rot); - Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); + const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(newBox); - SShader* shader = &m_shaders->m_shMATTE; - - useProgram(shader->program); + auto shader = useShader(getShaderVariant(SH_FRAG_MATTE, globalFeatures())); shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); shader->setUniformInt(SHADER_TEX, 0); shader->setUniformInt(SHADER_ALPHA_MATTE, 1); @@ -1944,13 +1598,13 @@ void CHyprOpenGLImpl::renderTextureMatte(SP tex, const CBox& box, CFra tex->bind(); glActiveTexture(GL_TEXTURE0 + 1); - auto matteTex = matte.getTexture(); + auto matteTex = matte->getTexture(); matteTex->bind(); - glBindVertexArray(shader->uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); - m_renderData.damage.forEachRect([this](const auto& RECT) { - scissor(&RECT); + g_pHyprRenderer->m_renderData.damage.forEachRect([this](const auto& RECT) { + scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); @@ -1963,27 +1617,19 @@ void CHyprOpenGLImpl::renderTextureMatte(SP tex, const CBox& box, CFra // but it works... well, I guess? // // Dual (or more) kawase blur -CFramebuffer* CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* originalDamage) { - if (!m_renderData.currentFB->getTexture()) { - Debug::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 blurFramebufferWithDamage(a, originalDamage, *m_renderData.currentFB); -} - -CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* originalDamage, CFramebuffer& source) { +SP CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* originalDamage, CGLFramebuffer& source) { TRACY_GPU_ZONE("RenderBlurFramebufferWithDamage"); + auto& m_renderData = g_pHyprRenderer->m_renderData; const auto BLENDBEFORE = m_blend; blend(false); setCapStatus(GL_STENCIL_TEST, false); // get transforms for the full monitor - const auto TRANSFORM = wlTransformToHyprutils(invertTransform(m_renderData.pMonitor->m_transform)); - CBox MONITORBOX = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}; - Mat3x3 matrix = m_renderData.monitorProjection.projectBox(MONITORBOX, TRANSFORM); - Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); + const auto TRANSFORM = Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)); + CBox MONITORBOX = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}; + + const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(MONITORBOX, TRANSFORM); // get the config settings static auto PBLURSIZE = CConfigValue("decoration:blur:size"); @@ -1995,21 +1641,22 @@ CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* origi // prep damage CRegion damage{*originalDamage}; - damage.transform(wlTransformToHyprutils(invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, + damage.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y); 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 = g_pHyprRenderer->m_renderData.pMonitor->resources()->getUnusedWorkBuffer(); + const auto PMIRRORSWAPFB = g_pHyprRenderer->m_renderData.pMonitor->resources()->getUnusedWorkBuffer(); - CFramebuffer* currentRenderToFB = PMIRRORFB; + auto currentRenderToFB = PMIRRORFB; // Begin with base color adjustments - global brightness and contrast // TODO: make this a part of the first pass maybe to save on a drawcall? { static auto PBLURCONTRAST = CConfigValue("decoration:blur:contrast"); static auto PBLURBRIGHTNESS = CConfigValue("decoration:blur:brightness"); + static auto PBLEND = CConfigValue("render:use_shader_blur_blend"); PMIRRORSWAPFB->bind(); @@ -2020,31 +1667,33 @@ CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* origi currentTex->bind(); currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); - useProgram(m_shaders->m_shBLURPREPARE.program); + WP shader; // From FB to sRGB - const bool skipCM = !m_cmSupported || m_renderData.pMonitor->m_imageDescription == SImageDescription{}; - m_shaders->m_shBLURPREPARE.setUniformInt(SHADER_SKIP_CM, skipCM); + const bool skipCM = !m_cmSupported || !g_pHyprRenderer->workBufferImageDescription()->needsCM(getDefaultImageDescription()); if (!skipCM) { - passCMUniforms(m_shaders->m_shBLURPREPARE, m_renderData.pMonitor->m_imageDescription, SImageDescription{}); - m_shaders->m_shBLURPREPARE.setUniformFloat(SHADER_SDR_SATURATION, - m_renderData.pMonitor->m_sdrSaturation > 0 && - m_renderData.pMonitor->m_imageDescription.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.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ? - m_renderData.pMonitor->m_sdrBrightness : - 1.0f); - } + shader = useShader(getShaderVariant(SH_FRAG_BLURPREPARE, SH_FEAT_CM)); + passCMUniforms(shader, g_pHyprRenderer->workBufferImageDescription(), getDefaultImageDescription()); + shader->setUniformFloat(SHADER_SDR_SATURATION, + m_renderData.pMonitor->m_sdrSaturation > 0 && + g_pHyprRenderer->workBufferImageDescription()->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 && + g_pHyprRenderer->workBufferImageDescription()->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ? + m_renderData.pMonitor->m_sdrBrightness : + 1.0f); + } else + shader = useShader(getShaderVariant(SH_FRAG_BLURPREPARE)); - m_shaders->m_shBLURPREPARE.setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - m_shaders->m_shBLURPREPARE.setUniformFloat(SHADER_CONTRAST, *PBLURCONTRAST); - m_shaders->m_shBLURPREPARE.setUniformFloat(SHADER_BRIGHTNESS, *PBLURBRIGHTNESS); - m_shaders->m_shBLURPREPARE.setUniformInt(SHADER_TEX, 0); + const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(MONITORBOX, *PBLEND ? HYPRUTILS_TRANSFORM_NORMAL : TRANSFORM); + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniformFloat(SHADER_CONTRAST, *PBLURCONTRAST); + shader->setUniformFloat(SHADER_BRIGHTNESS, *PBLURBRIGHTNESS); + shader->setUniformInt(SHADER_TEX, 0); - glBindVertexArray(m_shaders->m_shBLURPREPARE.uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); if (!damage.empty()) { damage.forEachRect([this](const auto& RECT) { @@ -2058,7 +1707,7 @@ CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* origi } // declare the draw func - auto drawPass = [&](SShader* pShader, CRegion* pDamage) { + auto drawPass = [&](WP shader, ePreparedFragmentShader frag, CRegion* pDamage) { if (currentRenderToFB == PMIRRORFB) PMIRRORSWAPFB->bind(); else @@ -2072,21 +1721,19 @@ CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* origi currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); - useProgram(pShader->program); - // prep two shaders - pShader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - pShader->setUniformFloat(SHADER_RADIUS, *PBLURSIZE * a); // this makes the blursize change with a - if (pShader == &m_shaders->m_shBLUR1) { - m_shaders->m_shBLUR1.setUniformFloat2(SHADER_HALFPIXEL, 0.5f / (m_renderData.pMonitor->m_pixelSize.x / 2.f), 0.5f / (m_renderData.pMonitor->m_pixelSize.y / 2.f)); - m_shaders->m_shBLUR1.setUniformInt(SHADER_PASSES, BLUR_PASSES); - m_shaders->m_shBLUR1.setUniformFloat(SHADER_VIBRANCY, *PBLURVIBRANCY); - m_shaders->m_shBLUR1.setUniformFloat(SHADER_VIBRANCY_DARKNESS, *PBLURVIBRANCYDARKNESS); + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniformFloat(SHADER_RADIUS, *PBLURSIZE * a); // this makes the blursize change with a + if (frag == SH_FRAG_BLUR1) { + shader->setUniformFloat2(SHADER_HALFPIXEL, 0.5f / (m_renderData.pMonitor->m_pixelSize.x / 2.f), 0.5f / (m_renderData.pMonitor->m_pixelSize.y / 2.f)); + shader->setUniformInt(SHADER_PASSES, BLUR_PASSES); + shader->setUniformFloat(SHADER_VIBRANCY, *PBLURVIBRANCY); + shader->setUniformFloat(SHADER_VIBRANCY_DARKNESS, *PBLURVIBRANCYDARKNESS); } else - m_shaders->m_shBLUR2.setUniformFloat2(SHADER_HALFPIXEL, 0.5f / (m_renderData.pMonitor->m_pixelSize.x * 2.f), 0.5f / (m_renderData.pMonitor->m_pixelSize.y * 2.f)); - pShader->setUniformInt(SHADER_TEX, 0); + shader->setUniformFloat2(SHADER_HALFPIXEL, 0.5f / (m_renderData.pMonitor->m_pixelSize.x * 2.f), 0.5f / (m_renderData.pMonitor->m_pixelSize.y * 2.f)); + shader->setUniformInt(SHADER_TEX, 0); - glBindVertexArray(pShader->uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); if (!pDamage->empty()) { pDamage->forEachRect([this](const auto& RECT) { @@ -2112,14 +1759,16 @@ CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* origi CRegion tempDamage{damage}; // and draw + auto shader = useShader(getShaderVariant(SH_FRAG_BLUR1)); for (auto i = 1; i <= BLUR_PASSES; ++i) { tempDamage = damage.copy().scale(1.f / (1 << i)); - drawPass(&m_shaders->m_shBLUR1, &tempDamage); // down + drawPass(shader, SH_FRAG_BLUR1, &tempDamage); // down } + shader = useShader(getShaderVariant(SH_FRAG_BLUR2)); for (auto i = BLUR_PASSES - 1; i >= 0; --i) { tempDamage = damage.copy().scale(1.f / (1 << i)); // when upsampling we make the region twice as big - drawPass(&m_shaders->m_shBLUR2, &tempDamage); // up + drawPass(shader, SH_FRAG_BLUR2, &tempDamage); // up } // finalize the image @@ -2140,14 +1789,31 @@ CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* origi currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); - useProgram(m_shaders->m_shBLURFINISH.program); - m_shaders->m_shBLURFINISH.setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - m_shaders->m_shBLURFINISH.setUniformFloat(SHADER_NOISE, *PBLURNOISE); - m_shaders->m_shBLURFINISH.setUniformFloat(SHADER_BRIGHTNESS, *PBLURBRIGHTNESS); + // From FB to sRGB + const bool skipCM = !m_cmSupported || !g_pHyprRenderer->workBufferImageDescription()->needsCM(getDefaultImageDescription()); + if (!skipCM) { + shader = useShader(getShaderVariant(SH_FRAG_BLURFINISH, SH_FEAT_CM)); + passCMUniforms(shader, getDefaultImageDescription(), g_pHyprRenderer->workBufferImageDescription()); + shader->setUniformFloat(SHADER_SDR_SATURATION, + m_renderData.pMonitor->m_sdrSaturation > 0 && + g_pHyprRenderer->workBufferImageDescription()->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 && + g_pHyprRenderer->workBufferImageDescription()->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ? + m_renderData.pMonitor->m_sdrBrightness : + 1.0f); + } else + shader = useShader(getShaderVariant(SH_FRAG_BLURFINISH)); - m_shaders->m_shBLURFINISH.setUniformInt(SHADER_TEX, 0); + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniformFloat(SHADER_NOISE, *PBLURNOISE); + shader->setUniformFloat(SHADER_BRIGHTNESS, *PBLURBRIGHTNESS); - glBindVertexArray(m_shaders->m_shBLURFINISH.uniformLocations[SHADER_SHADER_VAO]); + shader->setUniformInt(SHADER_TEX, 0); + + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); if (!damage.empty()) { damage.forEachRect([this](const auto& RECT) { @@ -2172,16 +1838,12 @@ CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* origi return currentRenderToFB; } -void CHyprOpenGLImpl::markBlurDirtyForMonitor(PHLMONITOR pMonitor) { - m_monitorRenderResources[pMonitor].blurFBDirty = true; -} - void CHyprOpenGLImpl::preRender(PHLMONITOR pMonitor) { static auto PBLURNEWOPTIMIZE = CConfigValue("decoration:blur:new_optimizations"); static auto PBLURXRAY = CConfigValue("decoration:blur:xray"); static auto PBLUR = CConfigValue("decoration:blur:enabled"); - if (!*PBLURNEWOPTIMIZE || !m_monitorRenderResources[pMonitor].blurFBDirty || !*PBLUR) + if (!*PBLURNEWOPTIMIZE || !pMonitor->m_blurFBDirty || !*PBLUR) return; // ignore if solitary present, nothing to blur @@ -2199,10 +1861,10 @@ void CHyprOpenGLImpl::preRender(PHLMONITOR pMonitor) { if (pWindow->m_ruleApplicator->noBlur().valueOrDefault()) return false; - if (pWindow->m_wlSurface->small() && !pWindow->m_wlSurface->m_fillIgnoreSmall) + if (pWindow->wlSurface()->small() && !pWindow->wlSurface()->m_fillIgnoreSmall) return true; - const auto PSURFACE = pWindow->m_wlSurface->resource(); + const auto PSURFACE = pWindow->wlSurface()->resource(); const auto PWORKSPACE = pWindow->m_workspace; const float A = pWindow->m_alpha->value() * pWindow->m_activeInactiveAlpha->value() * PWORKSPACE->m_alpha->value(); @@ -2256,236 +1918,147 @@ void CHyprOpenGLImpl::preRender(PHLMONITOR pMonitor) { return; g_pHyprRenderer->damageMonitor(pMonitor); - m_monitorRenderResources[pMonitor].blurFBShouldRender = true; + pMonitor->m_blurFBShouldRender = true; } -void CHyprOpenGLImpl::preBlurForCurrentMonitor() { - - TRACY_GPU_ZONE("RenderPreBlurForCurrentMonitor"); - - const auto SAVEDRENDERMODIF = m_renderData.renderModif; - m_renderData.renderModif = {}; // fix shit - - // make the fake dmg - CRegion fakeDamage{0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}; - const auto POUTFB = blurMainFramebufferWithDamage(1, &fakeDamage); - - // render onto blurFB - m_renderData.pCurrentMonData->blurFB.alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y, - m_renderData.pMonitor->m_output->state->state().drmFormat); - m_renderData.pCurrentMonData->blurFB.bind(); - - clear(CHyprColor(0, 0, 0, 0)); - - pushMonitorTransformEnabled(true); - renderTextureInternal(POUTFB->getTexture(), CBox{0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}, - STextureRenderData{.damage = &fakeDamage, .a = 1, .round = 0, .roundingPower = 2.F, .discardActive = false, .allowCustomUV = false, .noAA = true}); - popMonitorTransformEnabled(); - - m_renderData.currentFB->bind(); - - m_renderData.pCurrentMonData->blurFBDirty = false; - - m_renderData.renderModif = SAVEDRENDERMODIF; - - m_monitorRenderResources[m_renderData.pMonitor].blurFBShouldRender = false; -} - -void CHyprOpenGLImpl::preWindowPass() { - if (!preBlurQueued()) - return; - - g_pHyprRenderer->m_renderPass.add(makeUnique()); -} - -bool CHyprOpenGLImpl::preBlurQueued() { - static auto PBLURNEWOPTIMIZE = CConfigValue("decoration:blur:new_optimizations"); - static auto PBLUR = CConfigValue("decoration:blur:enabled"); - - return m_renderData.pCurrentMonData->blurFBDirty && *PBLURNEWOPTIMIZE && *PBLUR && m_renderData.pCurrentMonData->blurFBShouldRender; -} - -bool CHyprOpenGLImpl::shouldUseNewBlurOptimizations(PHLLS pLayer, PHLWINDOW pWindow) { - static auto PBLURNEWOPTIMIZE = CConfigValue("decoration:blur:new_optimizations"); - static auto PBLURXRAY = CConfigValue("decoration:blur:xray"); - - if (!m_renderData.pCurrentMonData->blurFB.getTexture()) - return false; - - if (pWindow && pWindow->m_ruleApplicator->xray().hasValue() && !pWindow->m_ruleApplicator->xray().valueOrDefault()) - return false; - - if (pLayer && pLayer->m_ruleApplicator->xray().valueOrDefault() == 0) - return false; - - if ((*PBLURNEWOPTIMIZE && pWindow && !pWindow->m_isFloating && !pWindow->onSpecialWorkspace()) || *PBLURXRAY) - return true; - - if ((pLayer && pLayer->m_ruleApplicator->xray().valueOrDefault() == 1) || (pWindow && pWindow->m_ruleApplicator->xray().valueOrDefault())) - return true; - - return false; -} - -void CHyprOpenGLImpl::renderTextureWithBlurInternal(SP tex, const CBox& box, const STextureRenderData& data) { +void CHyprOpenGLImpl::renderTextureWithBlurInternal(SP tex, const CBox& box, const STextureRenderData& data) { + auto& m_renderData = g_pHyprRenderer->m_renderData; RASSERT(m_renderData.pMonitor, "Tried to render texture with blur without begin()!"); TRACY_GPU_ZONE("RenderTextureWithBlur"); - // make a damage region for this window - CRegion texDamage{m_renderData.damage}; - texDamage.intersect(box.x, box.y, box.width, box.height); + static auto PBLEND = CConfigValue("render:use_shader_blur_blend"); + const auto NEEDS_STENCIL = data.discardMode != 0 && (!data.blockBlurOptimization || (data.discardMode & DISCARD_ALPHA)); + if (!*PBLEND) { - // While renderTextureInternalWithDamage will clip the blur as well, - // clipping texDamage here allows blur generation to be optimized. - if (!m_renderData.clipRegion.empty()) - texDamage.intersect(m_renderData.clipRegion); + if (NEEDS_STENCIL) { + scissor(nullptr); // allow the entire window and stencil to render + glStencilMask(0xFF); + glClearStencil(0); + glClear(GL_STENCIL_BUFFER_BIT); - if (texDamage.empty()) - return; + setCapStatus(GL_STENCIL_TEST, true); - m_renderData.renderModif.applyToRegion(texDamage); + glStencilFunc(GL_ALWAYS, 1, 0xFF); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - // amazing hack: the surface has an opaque region! - CRegion inverseOpaque; - if (data.a >= 1.f && data.surface && std::round(data.surface->m_current.size.x * m_renderData.pMonitor->m_scale) == box.w && - std::round(data.surface->m_current.size.y * m_renderData.pMonitor->m_scale) == box.h) { - pixman_box32_t surfbox = {0, 0, data.surface->m_current.size.x * data.surface->m_current.scale, data.surface->m_current.size.y * data.surface->m_current.scale}; - inverseOpaque = data.surface->m_current.opaque; - inverseOpaque.invert(&surfbox).intersect(0, 0, data.surface->m_current.size.x * data.surface->m_current.scale, - data.surface->m_current.size.y * data.surface->m_current.scale); + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - if (inverseOpaque.empty()) { - renderTextureInternal(tex, box, data); - return; - } - } else - inverseOpaque = {0, 0, box.width, box.height}; - - 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; - - 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; - - 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); - 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_EQUAL, 1, 0xFF); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - - // stencil done. Render everything. - const auto LASTTL = m_renderData.primarySurfaceUVTopLeft; - const auto LASTBR = m_renderData.primarySurfaceUVBottomRight; - - CBox transformedBox = box; - transformedBox.transform(wlTransformToHyprutils(invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, - m_renderData.pMonitor->m_transformedSize.y); - - 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 = 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); - if (!USENEWOPTIMIZE) - setRenderModifEnabled(false); - renderTextureInternal(POUTFB->getTexture(), box, + renderTexture(tex, 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(); + .damage = &g_pHyprRenderer->m_renderData.damage, + .a = data.a, + .round = data.round, + .roundingPower = data.roundingPower, + .discardActive = true, + .allowCustomUV = true, + .wrapX = data.wrapX, + .wrapY = data.wrapY, + .discardMode = data.discardMode, + .discardOpacity = data.discardOpacity, + .clipRegion = data.clipRegion, + .currentLS = data.currentLS, + .primarySurfaceUVTopLeft = g_pHyprRenderer->m_renderData.primarySurfaceUVTopLeft, + .primarySurfaceUVBottomRight = g_pHyprRenderer->m_renderData.primarySurfaceUVBottomRight, + }); // discard opaque and alpha < discardOpacity - m_renderData.primarySurfaceUVTopLeft = LASTTL; - m_renderData.primarySurfaceUVBottomRight = LASTBR; + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - // render the window, but clear stencil - glClearStencil(0); - glClear(GL_STENCIL_BUFFER_BIT); + glStencilFunc(GL_EQUAL, 1, 0xFF); + glStencilMask(0x00); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + } + + // stencil done. Render everything. + 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); + + 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}; + + static auto PBLURIGNOREOPACITY = CConfigValue("decoration:blur:ignore_opacity"); + + g_pHyprRenderer->pushMonitorTransformEnabled(true); + bool renderModif = g_pHyprRenderer->m_renderData.renderModif.enabled; + if (!data.blockBlurOptimization) + g_pHyprRenderer->m_renderData.renderModif.enabled = false; + + renderTextureInternal(data.blurredBG, box, + STextureRenderData{ + .damage = data.damage, + .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, + .discardMode = data.discardMode, + .discardOpacity = data.discardOpacity, + .clipRegion = data.clipRegion, + .currentLS = data.currentLS, + + .primarySurfaceUVTopLeft = monitorSpaceBox.pos() / m_renderData.pMonitor->m_transformedSize, + .primarySurfaceUVBottomRight = (monitorSpaceBox.pos() + monitorSpaceBox.size()) / m_renderData.pMonitor->m_transformedSize, + }); + + g_pHyprRenderer->m_renderData.renderModif.enabled = renderModif; + g_pHyprRenderer->popMonitorTransformEnabled(); + + if (NEEDS_STENCIL) + setCapStatus(GL_STENCIL_TEST, false); + } // draw window - setCapStatus(GL_STENCIL_TEST, false); renderTextureInternal(tex, box, STextureRenderData{ - .damage = &texDamage, - .a = data.a * data.overallA, - .round = data.round, - .roundingPower = data.roundingPower, - .discardActive = false, - .allowCustomUV = true, - .allowDim = true, - .noAA = false, - .wrapX = data.wrapX, - .wrapY = data.wrapY, + .blur = *PBLEND, + .blurredBG = data.blurredBG, + .damage = data.damage, + .a = data.a * data.overallA, + .round = data.round, + .roundingPower = data.roundingPower, + .discardActive = *PBLEND && NEEDS_STENCIL, + .allowCustomUV = true, + .allowDim = true, + .noAA = false, + .wrapX = data.wrapX, + .wrapY = data.wrapY, + .discardMode = data.discardMode, + .discardOpacity = data.discardOpacity, + .clipRegion = data.clipRegion, + .currentLS = data.currentLS, + + .primarySurfaceUVTopLeft = g_pHyprRenderer->m_renderData.primarySurfaceUVTopLeft, + .primarySurfaceUVBottomRight = g_pHyprRenderer->m_renderData.primarySurfaceUVBottomRight, }); - glStencilMask(0xFF); - glStencilFunc(GL_ALWAYS, 1, 0xFF); + GLFB(g_pHyprRenderer->m_renderData.currentFB)->invalidate({GL_STENCIL_ATTACHMENT}); scissor(nullptr); } -void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& grad, SBorderRenderData data) { +void CHyprOpenGLImpl::renderBorder(const CBox& box, const Config::CGradientValueData& grad, SBorderRenderData data) { + auto& m_renderData = g_pHyprRenderer->m_renderData; RASSERT((box.width > 0 && box.height > 0), "Tried to render rect with width/height < 0!"); RASSERT(m_renderData.pMonitor, "Tried to render rect without begin()!"); TRACY_GPU_ZONE("RenderBorder"); - if (m_renderData.damage.empty()) + if (g_pHyprRenderer->m_renderData.damage.empty()) return; CBox newBox = box; - m_renderData.renderModif.applyToBox(newBox); + g_pHyprRenderer->m_renderData.renderModif.applyToBox(newBox); if (data.borderSize < 1) return; int scaledBorderSize = std::round(data.borderSize * m_renderData.pMonitor->m_scale); - scaledBorderSize = std::round(scaledBorderSize * m_renderData.renderModif.combinedScale()); + scaledBorderSize = std::round(scaledBorderSize * g_pHyprRenderer->m_renderData.renderModif.combinedScale()); // adjust box newBox.x -= scaledBorderSize; @@ -2493,57 +2066,58 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr newBox.width += 2 * scaledBorderSize; newBox.height += 2 * scaledBorderSize; - float round = data.round + (data.round == 0 ? 0 : scaledBorderSize); + float round = data.round + (data.round == 0 ? 0 : scaledBorderSize); - Mat3x3 matrix = m_renderData.monitorProjection.projectBox( - newBox, wlTransformToHyprutils(invertTransform(!m_monitorTransformEnabled ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform)), newBox.rot); - Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); + const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(newBox); - const auto BLEND = m_blend; + const auto BLEND = m_blend; blend(true); - useProgram(m_shaders->m_shBORDER1.program); + WP shader; - const bool skipCM = !m_cmSupported || m_renderData.pMonitor->m_imageDescription == SImageDescription{}; - m_shaders->m_shBORDER1.setUniformInt(SHADER_SKIP_CM, skipCM); - if (!skipCM) - passCMUniforms(m_shaders->m_shBORDER1, SImageDescription{}); + const bool IS_ICC = g_pHyprRenderer->workBufferImageDescription()->value().icc.present; + const bool skipCM = !m_cmSupported || !g_pHyprRenderer->workBufferImageDescription()->needsCM(getDefaultImageDescription()); + 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) | globalFeatures())); + passCMUniforms(shader, getDefaultImageDescription()); + } else + shader = useShader(getShaderVariant(SH_FRAG_BORDER1, SH_FEAT_ROUNDING | globalFeatures())); - m_shaders->m_shBORDER1.setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - m_shaders->m_shBORDER1.setUniform4fv(SHADER_GRADIENT, grad.m_colorsOkLabA.size() / 4, grad.m_colorsOkLabA); - m_shaders->m_shBORDER1.setUniformInt(SHADER_GRADIENT_LENGTH, grad.m_colorsOkLabA.size() / 4); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_ANGLE, sc(grad.m_angle / (std::numbers::pi / 180.0)) % 360 * (std::numbers::pi / 180.0)); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_ALPHA, data.a); - m_shaders->m_shBORDER1.setUniformInt(SHADER_GRADIENT2_LENGTH, 0); + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniform4fv(SHADER_GRADIENT, grad.m_colorsOkLabA.size() / 4, grad.m_colorsOkLabA); + shader->setUniformInt(SHADER_GRADIENT_LENGTH, grad.m_colorsOkLabA.size() / 4); + shader->setUniformFloat(SHADER_ANGLE, sc(grad.m_angle / (std::numbers::pi / 180.0)) % 360 * (std::numbers::pi / 180.0)); + shader->setUniformFloat(SHADER_ALPHA, data.a); + shader->setUniformInt(SHADER_GRADIENT2_LENGTH, 0); CBox transformedBox = newBox; - transformedBox.transform(wlTransformToHyprutils(invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, + 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); - m_shaders->m_shBORDER1.setUniformFloat2(SHADER_TOP_LEFT, sc(TOPLEFT.x), sc(TOPLEFT.y)); - m_shaders->m_shBORDER1.setUniformFloat2(SHADER_FULL_SIZE, sc(FULLSIZE.x), sc(FULLSIZE.y)); - m_shaders->m_shBORDER1.setUniformFloat2(SHADER_FULL_SIZE_UNTRANSFORMED, sc(newBox.width), sc(newBox.height)); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_RADIUS, round); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_RADIUS_OUTER, data.outerRound == -1 ? round : data.outerRound); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_THICK, scaledBorderSize); + shader->setUniformFloat2(SHADER_TOP_LEFT, sc(TOPLEFT.x), sc(TOPLEFT.y)); + shader->setUniformFloat2(SHADER_FULL_SIZE, sc(FULLSIZE.x), sc(FULLSIZE.y)); + shader->setUniformFloat2(SHADER_FULL_SIZE_UNTRANSFORMED, sc(newBox.width), sc(newBox.height)); + shader->setUniformFloat(SHADER_RADIUS, round); + shader->setUniformFloat(SHADER_RADIUS_OUTER, data.outerRound == -1 ? round : data.outerRound); + shader->setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower); + shader->setUniformFloat(SHADER_THICK, scaledBorderSize); - glBindVertexArray(m_shaders->m_shBORDER1.uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); // calculate the border's region, which we need to render over. No need to run the shader on // things outside there - CRegion borderRegion = m_renderData.damage.copy().intersect(newBox); + CRegion borderRegion = g_pHyprRenderer->m_renderData.damage.copy().intersect(newBox); borderRegion.subtract(box.copy().expand(-scaledBorderSize - round)); - if (m_renderData.clipBox.width != 0 && m_renderData.clipBox.height != 0) - borderRegion.intersect(m_renderData.clipBox); + if (g_pHyprRenderer->m_renderData.clipBox.width != 0 && g_pHyprRenderer->m_renderData.clipBox.height != 0) + borderRegion.intersect(g_pHyprRenderer->m_renderData.clipBox); if (!borderRegion.empty()) { borderRegion.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } @@ -2553,23 +2127,24 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr blend(BLEND); } -void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& grad1, const CGradientValueData& grad2, float lerp, SBorderRenderData data) { +void CHyprOpenGLImpl::renderBorder(const CBox& box, const Config::CGradientValueData& grad1, const Config::CGradientValueData& grad2, float lerp, SBorderRenderData data) { + auto& m_renderData = g_pHyprRenderer->m_renderData; RASSERT((box.width > 0 && box.height > 0), "Tried to render rect with width/height < 0!"); RASSERT(m_renderData.pMonitor, "Tried to render rect without begin()!"); TRACY_GPU_ZONE("RenderBorder2"); - if (m_renderData.damage.empty()) + if (g_pHyprRenderer->m_renderData.damage.empty()) return; CBox newBox = box; - m_renderData.renderModif.applyToBox(newBox); + g_pHyprRenderer->m_renderData.renderModif.applyToBox(newBox); if (data.borderSize < 1) return; int scaledBorderSize = std::round(data.borderSize * m_renderData.pMonitor->m_scale); - scaledBorderSize = std::round(scaledBorderSize * m_renderData.renderModif.combinedScale()); + scaledBorderSize = std::round(scaledBorderSize * g_pHyprRenderer->m_renderData.renderModif.combinedScale()); // adjust box newBox.x -= scaledBorderSize; @@ -2577,61 +2152,61 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr newBox.width += 2 * scaledBorderSize; newBox.height += 2 * scaledBorderSize; - float round = data.round + (data.round == 0 ? 0 : scaledBorderSize); + float round = data.round + (data.round == 0 ? 0 : scaledBorderSize); - Mat3x3 matrix = m_renderData.monitorProjection.projectBox( - newBox, wlTransformToHyprutils(invertTransform(!m_monitorTransformEnabled ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform)), newBox.rot); - Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); + const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(newBox); - const auto BLEND = m_blend; + const auto BLEND = m_blend; blend(true); - useProgram(m_shaders->m_shBORDER1.program); + WP shader; + const bool IS_ICC = g_pHyprRenderer->workBufferImageDescription()->value().icc.present; + const bool skipCM = !m_cmSupported || !g_pHyprRenderer->workBufferImageDescription()->needsCM(getDefaultImageDescription()); + 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) | globalFeatures())); + passCMUniforms(shader, getDefaultImageDescription()); + } else + shader = useShader(getShaderVariant(SH_FRAG_BORDER1, SH_FEAT_ROUNDING | globalFeatures())); - const bool skipCM = !m_cmSupported || m_renderData.pMonitor->m_imageDescription == SImageDescription{}; - m_shaders->m_shBORDER1.setUniformInt(SHADER_SKIP_CM, skipCM); - if (!skipCM) - passCMUniforms(m_shaders->m_shBORDER1, SImageDescription{}); - - m_shaders->m_shBORDER1.setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - m_shaders->m_shBORDER1.setUniform4fv(SHADER_GRADIENT, grad1.m_colorsOkLabA.size() / 4, grad1.m_colorsOkLabA); - m_shaders->m_shBORDER1.setUniformInt(SHADER_GRADIENT_LENGTH, grad1.m_colorsOkLabA.size() / 4); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_ANGLE, sc(grad1.m_angle / (std::numbers::pi / 180.0)) % 360 * (std::numbers::pi / 180.0)); + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniform4fv(SHADER_GRADIENT, grad1.m_colorsOkLabA.size() / 4, grad1.m_colorsOkLabA); + shader->setUniformInt(SHADER_GRADIENT_LENGTH, grad1.m_colorsOkLabA.size() / 4); + shader->setUniformFloat(SHADER_ANGLE, sc(grad1.m_angle / (std::numbers::pi / 180.0)) % 360 * (std::numbers::pi / 180.0)); if (!grad2.m_colorsOkLabA.empty()) - m_shaders->m_shBORDER1.setUniform4fv(SHADER_GRADIENT2, grad2.m_colorsOkLabA.size() / 4, grad2.m_colorsOkLabA); - m_shaders->m_shBORDER1.setUniformInt(SHADER_GRADIENT2_LENGTH, grad2.m_colorsOkLabA.size() / 4); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_ANGLE2, sc(grad2.m_angle / (std::numbers::pi / 180.0)) % 360 * (std::numbers::pi / 180.0)); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_ALPHA, data.a); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_GRADIENT_LERP, lerp); + shader->setUniform4fv(SHADER_GRADIENT2, grad2.m_colorsOkLabA.size() / 4, grad2.m_colorsOkLabA); + shader->setUniformInt(SHADER_GRADIENT2_LENGTH, grad2.m_colorsOkLabA.size() / 4); + shader->setUniformFloat(SHADER_ANGLE2, sc(grad2.m_angle / (std::numbers::pi / 180.0)) % 360 * (std::numbers::pi / 180.0)); + shader->setUniformFloat(SHADER_ALPHA, data.a); + shader->setUniformFloat(SHADER_GRADIENT_LERP, lerp); CBox transformedBox = newBox; - transformedBox.transform(wlTransformToHyprutils(invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, + 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); - m_shaders->m_shBORDER1.setUniformFloat2(SHADER_TOP_LEFT, sc(TOPLEFT.x), sc(TOPLEFT.y)); - m_shaders->m_shBORDER1.setUniformFloat2(SHADER_FULL_SIZE, sc(FULLSIZE.x), sc(FULLSIZE.y)); - m_shaders->m_shBORDER1.setUniformFloat2(SHADER_FULL_SIZE_UNTRANSFORMED, sc(newBox.width), sc(newBox.height)); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_RADIUS, round); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_RADIUS_OUTER, data.outerRound == -1 ? round : data.outerRound); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_THICK, scaledBorderSize); + shader->setUniformFloat2(SHADER_TOP_LEFT, sc(TOPLEFT.x), sc(TOPLEFT.y)); + shader->setUniformFloat2(SHADER_FULL_SIZE, sc(FULLSIZE.x), sc(FULLSIZE.y)); + shader->setUniformFloat2(SHADER_FULL_SIZE_UNTRANSFORMED, sc(newBox.width), sc(newBox.height)); + shader->setUniformFloat(SHADER_RADIUS, round); + shader->setUniformFloat(SHADER_RADIUS_OUTER, data.outerRound == -1 ? round : data.outerRound); + shader->setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower); + shader->setUniformFloat(SHADER_THICK, scaledBorderSize); - glBindVertexArray(m_shaders->m_shBORDER1.uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); // calculate the border's region, which we need to render over. No need to run the shader on // things outside there - CRegion borderRegion = m_renderData.damage.copy().intersect(newBox); + CRegion borderRegion = g_pHyprRenderer->m_renderData.damage.copy().intersect(newBox); borderRegion.subtract(box.copy().expand(-scaledBorderSize - round)); - if (m_renderData.clipBox.width != 0 && m_renderData.clipBox.height != 0) - borderRegion.intersect(m_renderData.clipBox); + if (g_pHyprRenderer->m_renderData.clipBox.width != 0 && g_pHyprRenderer->m_renderData.clipBox.height != 0) + borderRegion.intersect(g_pHyprRenderer->m_renderData.clipBox); if (!borderRegion.empty()) { borderRegion.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } @@ -2641,17 +2216,17 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr } void CHyprOpenGLImpl::renderRoundedShadow(const CBox& box, int round, float roundingPower, int range, const CHyprColor& color, float a) { + auto& m_renderData = g_pHyprRenderer->m_renderData; RASSERT(m_renderData.pMonitor, "Tried to render shadow without begin()!"); RASSERT((box.width > 0 && box.height > 0), "Tried to render shadow with width/height < 0!"); - RASSERT(m_renderData.currentWindow, "Tried to render shadow without a window!"); - if (m_renderData.damage.empty()) + if (g_pHyprRenderer->m_renderData.damage.empty()) return; TRACY_GPU_ZONE("RenderShadow"); CBox newBox = box; - m_renderData.renderModif.applyToBox(newBox); + g_pHyprRenderer->m_renderData.renderModif.applyToBox(newBox); static auto PSHADOWPOWER = CConfigValue("decoration:shadow:render_power"); @@ -2659,49 +2234,141 @@ void CHyprOpenGLImpl::renderRoundedShadow(const CBox& box, int round, float roun const auto col = color; - Mat3x3 matrix = m_renderData.monitorProjection.projectBox( - newBox, wlTransformToHyprutils(invertTransform(!m_monitorTransformEnabled ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform)), newBox.rot); - Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); + const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(newBox); blend(true); - useProgram(m_shaders->m_shSHADOW.program); - const bool skipCM = !m_cmSupported || m_renderData.pMonitor->m_imageDescription == SImageDescription{}; - m_shaders->m_shSHADOW.setUniformInt(SHADER_SKIP_CM, skipCM); + const bool IS_ICC = g_pHyprRenderer->workBufferImageDescription()->value().icc.present; + const bool skipCM = !m_cmSupported || !g_pHyprRenderer->workBufferImageDescription()->needsCM(getDefaultImageDescription()); + auto shader = useShader(getShaderVariant(SH_FRAG_SHADOW, skipCM ? 0 : SH_FEAT_CM | (IS_ICC ? SH_FEAT_ICC : SH_FEAT_TONEMAP | SH_FEAT_SDR_MOD) | globalFeatures())); if (!skipCM) - passCMUniforms(m_shaders->m_shSHADOW, SImageDescription{}); + passCMUniforms(shader, getDefaultImageDescription()); - m_shaders->m_shSHADOW.setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - m_shaders->m_shSHADOW.setUniformFloat4(SHADER_COLOR, col.r, col.g, col.b, col.a * a); + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniformFloat4(SHADER_COLOR, col.r, col.g, col.b, col.a * a); const auto TOPLEFT = Vector2D(range + round, range + round); const auto BOTTOMRIGHT = Vector2D(newBox.width - (range + round), newBox.height - (range + round)); const auto FULLSIZE = Vector2D(newBox.width, newBox.height); // Rounded corners - m_shaders->m_shSHADOW.setUniformFloat2(SHADER_TOP_LEFT, sc(TOPLEFT.x), sc(TOPLEFT.y)); - m_shaders->m_shSHADOW.setUniformFloat2(SHADER_BOTTOM_RIGHT, sc(BOTTOMRIGHT.x), sc(BOTTOMRIGHT.y)); - m_shaders->m_shSHADOW.setUniformFloat2(SHADER_FULL_SIZE, sc(FULLSIZE.x), sc(FULLSIZE.y)); - m_shaders->m_shSHADOW.setUniformFloat(SHADER_RADIUS, range + round); - m_shaders->m_shSHADOW.setUniformFloat(SHADER_ROUNDING_POWER, roundingPower); - m_shaders->m_shSHADOW.setUniformFloat(SHADER_RANGE, range); - m_shaders->m_shSHADOW.setUniformFloat(SHADER_SHADOW_POWER, SHADOWPOWER); + shader->setUniformFloat2(SHADER_TOP_LEFT, sc(TOPLEFT.x), sc(TOPLEFT.y)); + shader->setUniformFloat2(SHADER_BOTTOM_RIGHT, sc(BOTTOMRIGHT.x), sc(BOTTOMRIGHT.y)); + shader->setUniformFloat2(SHADER_FULL_SIZE, sc(FULLSIZE.x), sc(FULLSIZE.y)); + shader->setUniformFloat(SHADER_RADIUS, round); + shader->setUniformFloat(SHADER_ROUNDING_POWER, roundingPower); + shader->setUniformFloat(SHADER_RANGE, range); + shader->setUniformFloat(SHADER_SHADOW_POWER, SHADOWPOWER); + shader->setUniformFloat2(SHADER_WINDOW_TOP_LEFT, -1.F, -1.F); + shader->setUniformFloat2(SHADER_WINDOW_BOTTOM_RIGHT, -1.F, -1.F); + shader->setUniformFloat(SHADER_THICK, 0.F); - glBindVertexArray(m_shaders->m_shSHADOW.uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); - if (m_renderData.clipBox.width != 0 && m_renderData.clipBox.height != 0) { - CRegion damageClip{m_renderData.clipBox.x, m_renderData.clipBox.y, m_renderData.clipBox.width, m_renderData.clipBox.height}; - damageClip.intersect(m_renderData.damage); + CRegion drawRegion; + + if (g_pHyprRenderer->m_renderData.clipBox.width != 0 && g_pHyprRenderer->m_renderData.clipBox.height != 0) { + drawRegion = {g_pHyprRenderer->m_renderData.clipBox.x, g_pHyprRenderer->m_renderData.clipBox.y, g_pHyprRenderer->m_renderData.clipBox.width, + g_pHyprRenderer->m_renderData.clipBox.height}; + drawRegion.intersect(g_pHyprRenderer->m_renderData.damage); + } else + drawRegion = g_pHyprRenderer->m_renderData.damage; + + if (g_pHyprRenderer->m_renderData.currentWindow) { + const auto PWINDOW = g_pHyprRenderer->m_renderData.currentWindow.lock(); + if (PWINDOW) { + if (const auto WINDOWBOX = PWINDOW->surfaceLogicalBox(); WINDOWBOX.has_value()) { + CBox scaledWindowBox = WINDOWBOX.value(); + + const auto PWORKSPACE = PWINDOW->m_workspace; + if (PWORKSPACE && !PWINDOW->m_pinned) + scaledWindowBox.translate(PWORKSPACE->m_renderOffset->value()); + + scaledWindowBox.translate(PWINDOW->m_floatingOffset); + scaledWindowBox.translate(-m_renderData.pMonitor->m_position); + scaledWindowBox.scale(m_renderData.pMonitor->m_scale).round(); + m_renderData.renderModif.applyToBox(scaledWindowBox); + + const auto cutoutTopLeft = scaledWindowBox.pos() - newBox.pos(); + const auto cutoutBottomRight = cutoutTopLeft + scaledWindowBox.size(); + + float cutoutRadius = std::max(0.F, sc(PWINDOW->rounding() * m_renderData.pMonitor->m_scale)); + cutoutRadius = std::round(cutoutRadius * m_renderData.renderModif.combinedScale()); + + shader->setUniformFloat2(SHADER_WINDOW_TOP_LEFT, sc(cutoutTopLeft.x), sc(cutoutTopLeft.y)); + shader->setUniformFloat2(SHADER_WINDOW_BOTTOM_RIGHT, sc(cutoutBottomRight.x), sc(cutoutBottomRight.y)); + shader->setUniformFloat(SHADER_THICK, cutoutRadius); + + drawRegion.subtract(scaledWindowBox.copy().expand(-sc(std::round(cutoutRadius)))); + } + } + } + + if (!drawRegion.empty()) + drawRegion.forEachRect([this](const auto& RECT) { + scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + }); + + glBindVertexArray(0); +} + +void CHyprOpenGLImpl::renderInnerGlow(const CBox& box, int round, float roundingPower, int range, const CHyprColor& color, int glowPower, float a) { + auto& m_renderData = g_pHyprRenderer->m_renderData; + RASSERT(m_renderData.pMonitor, "Tried to render inner glow without begin()!"); + RASSERT((box.width > 0 && box.height > 0), "Tried to render inner glow with width/height < 0!"); + + if (g_pHyprRenderer->m_renderData.damage.empty()) + return; + + TRACY_GPU_ZONE("RenderInnerGlow"); + + CBox newBox = box; + g_pHyprRenderer->m_renderData.renderModif.applyToBox(newBox); + + const auto col = color; + + const auto& glMatrix = g_pHyprRenderer->projectBoxToTarget(newBox); + + blend(true); + + const bool IS_ICC = g_pHyprRenderer->workBufferImageDescription()->value().icc.present; + const bool skipCM = !m_cmSupported || !g_pHyprRenderer->workBufferImageDescription()->needsCM(getDefaultImageDescription()); + auto shader = useShader(getShaderVariant(SH_FRAG_INNER_GLOW, skipCM ? 0 : SH_FEAT_CM | (IS_ICC ? SH_FEAT_ICC : SH_FEAT_TONEMAP | SH_FEAT_SDR_MOD))); + if (!skipCM) + passCMUniforms(shader, getDefaultImageDescription()); + + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniformFloat4(SHADER_COLOR, col.r, col.g, col.b, col.a * a); + + const auto TOPLEFT = Vector2D(round, round); + const auto BOTTOMRIGHT = Vector2D(newBox.width - round, newBox.height - round); + const auto FULLSIZE = Vector2D(newBox.width, newBox.height); + + 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, round); + shader->setUniformFloat(SHADER_ROUNDING_POWER, roundingPower); + shader->setUniformFloat(SHADER_RANGE, range); + shader->setUniformFloat(SHADER_SHADOW_POWER, glowPower); + + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); + + if (g_pHyprRenderer->m_renderData.clipBox.width != 0 && g_pHyprRenderer->m_renderData.clipBox.height != 0) { + CRegion damageClip{g_pHyprRenderer->m_renderData.clipBox.x, g_pHyprRenderer->m_renderData.clipBox.y, g_pHyprRenderer->m_renderData.clipBox.width, + g_pHyprRenderer->m_renderData.clipBox.height}; + damageClip.intersect(g_pHyprRenderer->m_renderData.damage); if (!damageClip.empty()) { damageClip.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } } else { - m_renderData.damage.forEachRect([this](const auto& RECT) { - scissor(&RECT); + g_pHyprRenderer->m_renderData.damage.forEachRect([this](const auto& RECT) { + scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } @@ -2710,584 +2377,62 @@ void CHyprOpenGLImpl::renderRoundedShadow(const CBox& box, int round, float roun } void CHyprOpenGLImpl::saveBufferForMirror(const CBox& box) { + const auto TEX = g_pHyprRenderer->m_renderData.pMonitor->resources()->m_mirrorTex ? g_pHyprRenderer->m_renderData.pMonitor->resources()->m_mirrorTex : + g_pHyprRenderer->m_renderData.currentFB->getTexture(); + if (!TEX) { + Log::logger->log(Log::ERR, "Invalid source texture for mirror"); + return; + } + auto guard = g_pHyprRenderer->bindTempFB(g_pHyprRenderer->m_renderData.pMonitor->resources()->mirrorFB()); - 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(); + Log::logger->log(Log::TRACE, "CM: saveBufferForMirror {} -> {}", TEX->m_imageDescription->value(), g_pHyprRenderer->m_renderData.currentFB->imageDescription()->value()); blend(false); - renderTexture(m_renderData.currentFB->getTexture(), box, + renderTexture(TEX, box, STextureRenderData{ - .a = 1.f, + .damage = &g_pHyprRenderer->m_renderData.finalDamage, + .a = 1.F, .round = 0, .discardActive = false, .allowCustomUV = false, + .cmBackToSRGB = true, }); blend(true); - - m_renderData.currentFB->bind(); } -void CHyprOpenGLImpl::renderMirrored() { +WP CHyprOpenGLImpl::useShader(WP prog) { + if (m_currentProgram == prog->program()) + return prog; - auto monitor = m_renderData.pMonitor; - auto mirrored = monitor->m_mirrorOf; + glUseProgram(prog->program()); + m_currentProgram = prog->program(); - const double scale = std::min(monitor->m_transformedSize.x / mirrored->m_transformedSize.x, monitor->m_transformedSize.y / mirrored->m_transformedSize.y); - CBox monbox = {0, 0, mirrored->m_transformedSize.x * scale, mirrored->m_transformedSize.y * scale}; - - // transform box as it will be drawn on a transformed projection - monbox.transform(wlTransformToHyprutils(mirrored->m_transform), mirrored->m_transformedSize.x * scale, mirrored->m_transformedSize.y * scale); - - monbox.x = (monitor->m_transformedSize.x - monbox.w) / 2; - monbox.y = (monitor->m_transformedSize.y - monbox.h) / 2; - - const auto PFB = &m_monitorRenderResources[mirrored].monitorMirrorFB; - if (!PFB->isAllocated() || !PFB->getTexture()) - return; - - g_pHyprRenderer->m_renderPass.add(makeUnique(CClearPassElement::SClearData{CHyprColor(0, 0, 0, 0)})); - - CTexPassElement::SRenderData data; - data.tex = PFB->getTexture(); - data.box = monbox; - data.replaceProjection = Mat3x3::identity() - .translate(monitor->m_pixelSize / 2.0) - .transform(wlTransformToHyprutils(monitor->m_transform)) - .transform(wlTransformToHyprutils(invertTransform(mirrored->m_transform))) - .translate(-monitor->m_transformedSize / 2.0); - - g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(data))); -} - -void CHyprOpenGLImpl::renderSplash(cairo_t* const CAIRO, cairo_surface_t* const CAIROSURFACE, double offsetY, const Vector2D& size) { - static auto PSPLASHCOLOR = CConfigValue("misc:col.splash"); - static auto PSPLASHFONT = CConfigValue("misc:splash_font_family"); - static auto FALLBACKFONT = CConfigValue("misc:font_family"); - - const auto FONTFAMILY = *PSPLASHFONT != STRVAL_EMPTY ? *PSPLASHFONT : *FALLBACKFONT; - const auto FONTSIZE = sc(size.y / 76); - const auto COLOR = CHyprColor(*PSPLASHCOLOR); - - PangoLayout* layoutText = pango_cairo_create_layout(CAIRO); - PangoFontDescription* pangoFD = pango_font_description_new(); - - pango_font_description_set_family_static(pangoFD, FONTFAMILY.c_str()); - pango_font_description_set_absolute_size(pangoFD, FONTSIZE * PANGO_SCALE); - pango_font_description_set_style(pangoFD, PANGO_STYLE_NORMAL); - pango_font_description_set_weight(pangoFD, PANGO_WEIGHT_NORMAL); - pango_layout_set_font_description(layoutText, pangoFD); - - cairo_set_source_rgba(CAIRO, COLOR.r, COLOR.g, COLOR.b, COLOR.a); - - int textW = 0, textH = 0; - pango_layout_set_text(layoutText, g_pCompositor->m_currentSplash.c_str(), -1); - pango_layout_get_size(layoutText, &textW, &textH); - textW /= PANGO_SCALE; - textH /= PANGO_SCALE; - - cairo_move_to(CAIRO, (size.x - textW) / 2.0, size.y - textH - offsetY); - pango_cairo_show_layout(CAIRO, layoutText); - - pango_font_description_free(pangoFD); - g_object_unref(layoutText); - - cairo_surface_flush(CAIROSURFACE); -} - -std::string CHyprOpenGLImpl::resolveAssetPath(const std::string& filename) { - std::string fullPath; - for (auto& e : ASSET_PATHS) { - std::string p = std::string{e} + "/hypr/" + filename; - std::error_code ec; - if (std::filesystem::exists(p, ec)) { - fullPath = p; - break; - } else - Debug::log(LOG, "resolveAssetPath: looking at {} unsuccessful: ec {}", filename, ec.message()); - } - - if (fullPath.empty()) { - m_failedAssetsNo++; - Debug::log(ERR, "resolveAssetPath: looking for {} failed (no provider found)", filename); - return ""; - } - - return fullPath; -} - -SP CHyprOpenGLImpl::loadAsset(const std::string& filename) { - - const std::string fullPath = resolveAssetPath(filename); - - if (fullPath.empty()) - return m_missingAssetTexture; - - const auto CAIROSURFACE = cairo_image_surface_create_from_png(fullPath.c_str()); - - if (!CAIROSURFACE) { - m_failedAssetsNo++; - Debug::log(ERR, "loadAsset: failed to load {} (corrupt / inaccessible / not png)", fullPath); - return m_missingAssetTexture; - } - - auto tex = texFromCairo(CAIROSURFACE); - - cairo_surface_destroy(CAIROSURFACE); - - return tex; -} - -SP CHyprOpenGLImpl::texFromCairo(cairo_surface_t* cairo) { - const auto CAIROFORMAT = cairo_image_surface_get_format(cairo); - auto tex = makeShared(); - - 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; - 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 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"); - - const auto FONTFAMILY = fontFamily.empty() ? *FONT : fontFamily; - const auto FONTSIZE = pt; - const auto COLOR = col; - - auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1920, 1080 /* arbitrary, just for size */); - auto CAIRO = cairo_create(CAIROSURFACE); - - PangoLayout* layoutText = pango_cairo_create_layout(CAIRO); - PangoFontDescription* pangoFD = pango_font_description_new(); - - pango_font_description_set_family_static(pangoFD, FONTFAMILY.c_str()); - pango_font_description_set_absolute_size(pangoFD, FONTSIZE * PANGO_SCALE); - pango_font_description_set_style(pangoFD, italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL); - pango_font_description_set_weight(pangoFD, sc(weight)); - pango_layout_set_font_description(layoutText, pangoFD); - - cairo_set_source_rgba(CAIRO, COLOR.r, COLOR.g, COLOR.b, COLOR.a); - - int textW = 0, textH = 0; - pango_layout_set_text(layoutText, text.c_str(), -1); - - if (maxWidth > 0) { - pango_layout_set_width(layoutText, maxWidth * PANGO_SCALE); - pango_layout_set_ellipsize(layoutText, PANGO_ELLIPSIZE_END); - } - - pango_layout_get_size(layoutText, &textW, &textH); - textW /= PANGO_SCALE; - textH /= PANGO_SCALE; - - pango_font_description_free(pangoFD); - g_object_unref(layoutText); - cairo_destroy(CAIRO); - cairo_surface_destroy(CAIROSURFACE); - - CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, textW, textH); - CAIRO = cairo_create(CAIROSURFACE); - - layoutText = pango_cairo_create_layout(CAIRO); - pangoFD = pango_font_description_new(); - - pango_font_description_set_family_static(pangoFD, FONTFAMILY.c_str()); - pango_font_description_set_absolute_size(pangoFD, FONTSIZE * PANGO_SCALE); - pango_font_description_set_style(pangoFD, italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL); - pango_font_description_set_weight(pangoFD, sc(weight)); - pango_layout_set_font_description(layoutText, pangoFD); - pango_layout_set_text(layoutText, text.c_str(), -1); - - cairo_set_source_rgba(CAIRO, COLOR.r, COLOR.g, COLOR.b, COLOR.a); - - cairo_move_to(CAIRO, 0, 0); - pango_cairo_show_layout(CAIRO, layoutText); - - pango_font_description_free(pangoFD); - g_object_unref(layoutText); - - cairo_surface_flush(CAIROSURFACE); - - tex->allocate(); - tex->m_size = {cairo_image_surface_get_width(CAIROSURFACE), cairo_image_surface_get_height(CAIROSURFACE)}; - - const auto DATA = cairo_image_surface_get_data(CAIROSURFACE); - 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, GL_RGBA, tex->m_size.x, tex->m_size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA); - - cairo_destroy(CAIRO); - cairo_surface_destroy(CAIROSURFACE); - - return tex; -} - -void CHyprOpenGLImpl::initMissingAssetTexture() { - SP tex = makeShared(); - tex->allocate(); - - const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 512, 512); - const auto CAIRO = cairo_create(CAIROSURFACE); - - cairo_set_antialias(CAIRO, CAIRO_ANTIALIAS_NONE); - cairo_save(CAIRO); - cairo_set_source_rgba(CAIRO, 0, 0, 0, 1); - cairo_set_operator(CAIRO, CAIRO_OPERATOR_SOURCE); - cairo_paint(CAIRO); - cairo_set_source_rgba(CAIRO, 1, 0, 1, 1); - cairo_rectangle(CAIRO, 256, 0, 256, 256); - cairo_fill(CAIRO); - cairo_rectangle(CAIRO, 0, 256, 256, 256); - cairo_fill(CAIRO); - cairo_restore(CAIRO); - - cairo_surface_flush(CAIROSURFACE); - - tex->m_size = {512, 512}; - - // copy the data to an OpenGL texture we have - const GLint glFormat = GL_RGBA; - const GLint glType = GL_UNSIGNED_BYTE; - - const auto DATA = cairo_image_surface_get_data(CAIROSURFACE); - 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); - - cairo_surface_destroy(CAIROSURFACE); - cairo_destroy(CAIRO); - - m_missingAssetTexture = tex; -} - -void CHyprOpenGLImpl::useProgram(GLuint prog) { - if (m_currentProgram == prog) - return; - - glUseProgram(prog); - m_currentProgram = prog; -} - -void CHyprOpenGLImpl::initAssets() { - initMissingAssetTexture(); - - m_screencopyDeniedTexture = renderText("Permission denied to share screen", Colors::WHITE, 20); -} - -void CHyprOpenGLImpl::ensureLockTexturesRendered(bool load) { - static bool loaded = false; - - if (loaded == load) - return; - - loaded = load; - - if (load) { - // this will cause a small hitch. I don't think we can do much, other than wasting VRAM and having this loaded all the time. - m_lockDeadTexture = loadAsset("lockdead.png"); - m_lockDead2Texture = loadAsset("lockdead2.png"); - - const auto VT = g_pCompositor->getVTNr(); - - m_lockTtyTextTexture = renderText(std::format("Running on tty {}", VT.has_value() ? std::to_string(*VT) : "unknown"), CHyprColor{0.9F, 0.9F, 0.9F, 0.7F}, 20, true); - } else { - m_lockDeadTexture.reset(); - m_lockDead2Texture.reset(); - m_lockTtyTextTexture.reset(); - } -} - -void CHyprOpenGLImpl::requestBackgroundResource() { - if (m_backgroundResource) - return; - - static auto PNOWALLPAPER = CConfigValue("misc:disable_hyprland_logo"); - static auto PFORCEWALLPAPER = CConfigValue("misc:force_default_wallpaper"); - - const auto FORCEWALLPAPER = std::clamp(*PFORCEWALLPAPER, sc(-1), sc(2)); - - if (*PNOWALLPAPER) - return; - - static bool once = true; - static std::string texPath = "wall"; - - if (once) { - // get the adequate tex - if (FORCEWALLPAPER == -1) { - std::mt19937_64 engine(time(nullptr)); - std::uniform_int_distribution<> distribution(0, 2); - - texPath += std::to_string(distribution(engine)); - } else - texPath += std::to_string(std::clamp(*PFORCEWALLPAPER, sc(0), sc(2))); - - texPath += ".png"; - - texPath = resolveAssetPath(texPath); - - once = false; - } - - if (texPath.empty()) { - m_backgroundResourceFailed = true; - return; - } - - m_backgroundResource = makeAtomicShared(texPath); - - // doesn't have to be ASP as it's passed - SP executor = makeShared([] { - for (const auto& m : g_pCompositor->m_monitors) { - g_pHyprRenderer->damageMonitor(m); - } - }); - - m_backgroundResource->m_events.finished.listenStatic([executor] { - // this is in the worker thread. - executor->signal(); - }); - - g_pAsyncResourceGatherer->enqueue(m_backgroundResource); -} - -void CHyprOpenGLImpl::createBGTextureForMonitor(PHLMONITOR pMonitor) { - RASSERT(m_renderData.pMonitor, "Tried to createBGTex without begin()!"); - - Debug::log(LOG, "Creating a texture for BGTex"); - - static auto PRENDERTEX = CConfigValue("misc:disable_hyprland_logo"); - static auto PNOSPLASH = CConfigValue("misc:disable_splash_rendering"); - - if (*PRENDERTEX || m_backgroundResourceFailed) - return; - - if (!m_backgroundResource) { - // queue the asset to be created - requestBackgroundResource(); - return; - } - - if (!m_backgroundResource->m_ready) - return; - - // release the last tex if exists - 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(); - - 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); - - cairo_set_antialias(CAIRO, CAIRO_ANTIALIAS_GOOD); - cairo_save(CAIRO); - cairo_set_source_rgba(CAIRO, 0, 0, 0, 0); - cairo_set_operator(CAIRO, CAIRO_OPERATOR_SOURCE); - cairo_paint(CAIRO); - cairo_restore(CAIRO); - - if (!*PNOSPLASH) - renderSplash(CAIRO, CAIROSURFACE, 0.02 * pMonitor->m_pixelSize.y, pMonitor->m_pixelSize); - - cairo_surface_flush(CAIROSURFACE); - - tex->m_size = pMonitor->m_pixelSize; - - // copy the data to an OpenGL texture we have - const GLint glFormat = GL_RGBA; - const GLint glType = GL_UNSIGNED_BYTE; - - const auto DATA = cairo_image_surface_get_data(CAIROSURFACE); - 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); - - cairo_surface_destroy(CAIROSURFACE); - cairo_destroy(CAIRO); - - // render the texture to our fb - PFB->bind(); - CRegion fakeDamage{0, 0, INT16_MAX, INT16_MAX}; - - blend(true); - clear(CHyprColor{0, 0, 0, 1}); - - SP backgroundTexture = texFromCairo(m_backgroundResource->m_asset.cairoSurface->cairo()); - - // first render the background - if (backgroundTexture) { - const double MONRATIO = m_renderData.pMonitor->m_transformedSize.x / m_renderData.pMonitor->m_transformedSize.y; - const double WPRATIO = backgroundTexture->m_size.x / backgroundTexture->m_size.y; - Vector2D origin; - double scale = 1.0; - - if (MONRATIO > WPRATIO) { - scale = m_renderData.pMonitor->m_transformedSize.x / backgroundTexture->m_size.x; - origin.y = (m_renderData.pMonitor->m_transformedSize.y - backgroundTexture->m_size.y * scale) / 2.0; - } else { - scale = m_renderData.pMonitor->m_transformedSize.y / backgroundTexture->m_size.y; - origin.x = (m_renderData.pMonitor->m_transformedSize.x - backgroundTexture->m_size.x * scale) / 2.0; - } - - CBox texbox = CBox{origin, backgroundTexture->m_size * scale}; - renderTextureInternal(backgroundTexture, texbox, {.damage = &fakeDamage, .a = 1.0}); - } - - CBox monbox = {{}, pMonitor->m_pixelSize}; - renderTextureInternal(tex, monbox, {.damage = &fakeDamage, .a = 1.0}); - - // bind back - if (m_renderData.currentFB) - m_renderData.currentFB->bind(); - - Debug::log(LOG, "Background created for monitor {}", pMonitor->m_name); - - // clear the resource after we're done using it - g_pEventLoopManager->doLater([this] { m_backgroundResource.reset(); }); - - // set the animation to start for fading this background in nicely - pMonitor->m_backgroundOpacity->setValueAndWarp(0.F); - *pMonitor->m_backgroundOpacity = 1.F; -} - -void CHyprOpenGLImpl::clearWithTex() { - RASSERT(m_renderData.pMonitor, "Tried to render BGtex without begin()!"); - - static auto PBACKGROUNDCOLOR = CConfigValue("misc:background_color"); - - auto TEXIT = m_monitorBGFBs.find(m_renderData.pMonitor); - - if (TEXIT == m_monitorBGFBs.end()) { - createBGTextureForMonitor(m_renderData.pMonitor.lock()); - g_pHyprRenderer->m_renderPass.add(makeUnique(CClearPassElement::SClearData{CHyprColor(*PBACKGROUNDCOLOR)})); - } - - if (TEXIT != m_monitorBGFBs.end()) { - CTexPassElement::SRenderData data; - 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(); - g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(data))); - } + return prog; } void CHyprOpenGLImpl::destroyMonitorResources(PHLMONITORREF pMonitor) { - g_pHyprRenderer->makeEGLCurrent(); + makeEGLCurrent(); if (!g_pHyprOpenGL) return; - auto RESIT = g_pHyprOpenGL->m_monitorRenderResources.find(pMonitor); - if (RESIT != g_pHyprOpenGL->m_monitorRenderResources.end()) { - RESIT->second.mirrorFB.release(); - RESIT->second.offloadFB.release(); - RESIT->second.mirrorSwapFB.release(); - RESIT->second.monitorMirrorFB.release(); - RESIT->second.blurFB.release(); - RESIT->second.offMainFB.release(); - RESIT->second.stencilTex->destroyTexture(); - g_pHyprOpenGL->m_monitorRenderResources.erase(RESIT); - } - auto TEXIT = g_pHyprOpenGL->m_monitorBGFBs.find(pMonitor); if (TEXIT != g_pHyprOpenGL->m_monitorBGFBs.end()) { - TEXIT->second.release(); + TEXIT->second.reset(); g_pHyprOpenGL->m_monitorBGFBs.erase(TEXIT); } if (pMonitor) - Debug::log(LOG, "Monitor {} -> destroyed all render data", pMonitor->m_name); + Log::logger->log(Log::DEBUG, "Monitor {} -> destroyed all render data", pMonitor->m_name); } -void CHyprOpenGLImpl::saveMatrix() { - m_renderData.savedProjection = m_renderData.projection; -} - -void CHyprOpenGLImpl::setMatrixScaleTranslate(const Vector2D& translate, const float& scale) { - m_renderData.projection.scale(scale).translate(translate); -} - -void CHyprOpenGLImpl::restoreMatrix() { - m_renderData.projection = m_renderData.savedProjection; -} - -void CHyprOpenGLImpl::bindOffMain() { - if (!m_renderData.pCurrentMonData->offMainFB.isAllocated()) { - m_renderData.pCurrentMonData->offMainFB.alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y, - m_renderData.pMonitor->m_output->state->state().drmFormat); - - m_renderData.pCurrentMonData->offMainFB.addStencil(m_renderData.pCurrentMonData->stencilTex); - } - - m_renderData.pCurrentMonData->offMainFB.bind(); - clear(CHyprColor(0, 0, 0, 0)); - m_renderData.currentFB = &m_renderData.pCurrentMonData->offMainFB; -} - -void CHyprOpenGLImpl::renderOffToMain(CFramebuffer* off) { - CBox monbox = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}; +void CHyprOpenGLImpl::renderOffToMain(SP off) { + CBox monbox = {0, 0, g_pHyprRenderer->m_renderData.pMonitor->m_transformedSize.x, g_pHyprRenderer->m_renderData.pMonitor->m_transformedSize.y}; renderTexturePrimitive(off->getTexture(), monbox); } -void CHyprOpenGLImpl::bindBackOnMain() { - m_renderData.mainFB->bind(); - m_renderData.currentFB = m_renderData.mainFB; -} - -void CHyprOpenGLImpl::pushMonitorTransformEnabled(bool enabled) { - m_monitorTransformStack.push(enabled); - m_monitorTransformEnabled = enabled; -} - -void CHyprOpenGLImpl::popMonitorTransformEnabled() { - m_monitorTransformStack.pop(); - m_monitorTransformEnabled = m_monitorTransformStack.top(); -} - -void CHyprOpenGLImpl::setRenderModifEnabled(bool enabled) { - m_renderData.renderModif.enabled = enabled; -} - void CHyprOpenGLImpl::setViewport(GLint x, GLint y, GLsizei width, GLsizei height) { if (m_lastViewport.x == x && m_lastViewport.y == y && m_lastViewport.width == width && m_lastViewport.height == height) return; @@ -3310,9 +2455,9 @@ void CHyprOpenGLImpl::setCapStatus(int cap, bool status) { if (idx == CAP_STATUS_END) { if (status) - glEnable(cap); + GLCALL(glEnable(cap)) else - glDisable(cap); + GLCALL(glDisable(cap)); return; } @@ -3322,31 +2467,52 @@ void CHyprOpenGLImpl::setCapStatus(int cap, bool status) { if (status) { m_capStatus[idx] = status; - glEnable(cap); + GLCALL(glEnable(cap)); } else { m_capStatus[idx] = status; - glDisable(cap); + GLCALL(glDisable(cap)); } } -uint32_t CHyprOpenGLImpl::getPreferredReadFormat(PHLMONITOR pMonitor) { - static const auto PFORCE8BIT = CConfigValue("misc:screencopy_force_8b"); +std::vector CHyprOpenGLImpl::getDRMFormatModifiers(DRMFormat drmFormat) { + SDRMFormat format; - if (!*PFORCE8BIT) - return pMonitor->m_output->state->state().drmFormat; + for (const auto& fmt : m_drmFormats) { + if (fmt.drmFormat == drmFormat) { + format = fmt; + break; + } + } - auto fmt = pMonitor->m_output->state->state().drmFormat; - - if (fmt == DRM_FORMAT_BGRA1010102 || fmt == DRM_FORMAT_ARGB2101010 || fmt == DRM_FORMAT_XRGB2101010 || fmt == DRM_FORMAT_BGRX1010102 || fmt == DRM_FORMAT_XBGR2101010) - return DRM_FORMAT_XRGB8888; - - return fmt; + return format.modifiers; } bool CHyprOpenGLImpl::explicitSyncSupported() { return m_exts.EGL_ANDROID_native_fence_sync_ext; } +WP CHyprOpenGLImpl::getShaderVariant(ePreparedFragmentShader frag, ShaderFeatureFlags features) { + auto& variants = m_shaders->fragVariants[frag]; + auto it = variants.find(features); + + if (it == variants.end()) { + 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]); + + it = variants.emplace(features, std::move(shader)).first; + return it->second; + } + + ASSERT(it->second); + return it->second; +} + std::vector CHyprOpenGLImpl::getDRMFormats() { return m_drmFormats; } @@ -3372,7 +2538,7 @@ void SRenderModifData::applyToBox(CBox& box) { box.y = OLDPOS.y * COS + OLDPOS.x * SIN; } } - } catch (std::bad_any_cast& e) { Debug::log(ERR, "BUG THIS OR PLUGIN ERROR: caught a bad_any_cast in SRenderModifData::applyToBox!"); } + } catch (std::bad_any_cast& e) { Log::logger->log(Log::ERR, "BUG THIS OR PLUGIN ERROR: caught a bad_any_cast in SRenderModifData::applyToBox!"); } } } @@ -3389,7 +2555,7 @@ void SRenderModifData::applyToRegion(CRegion& rg) { case RMOD_TYPE_ROTATE: /* TODO */ case RMOD_TYPE_ROTATECENTER: break; } - } catch (std::bad_any_cast& e) { Debug::log(ERR, "BUG THIS OR PLUGIN ERROR: caught a bad_any_cast in SRenderModifData::applyToRegion!"); } + } catch (std::bad_any_cast& e) { Log::logger->log(Log::ERR, "BUG THIS OR PLUGIN ERROR: caught a bad_any_cast in SRenderModifData::applyToRegion!"); } } } @@ -3407,7 +2573,7 @@ float SRenderModifData::combinedScale() { case RMOD_TYPE_ROTATE: case RMOD_TYPE_ROTATECENTER: break; } - } catch (std::bad_any_cast& e) { Debug::log(ERR, "BUG THIS OR PLUGIN ERROR: caught a bad_any_cast in SRenderModifData::combinedScale!"); } + } catch (std::bad_any_cast& e) { Log::logger->log(Log::ERR, "BUG THIS OR PLUGIN ERROR: caught a bad_any_cast in SRenderModifData::combinedScale!"); } } return scale; } @@ -3417,8 +2583,8 @@ UP CEGLSync::create() { EGLSyncKHR sync = g_pHyprOpenGL->m_proc.eglCreateSyncKHR(g_pHyprOpenGL->m_eglDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr); - if (sync == EGL_NO_SYNC_KHR) { - Debug::log(ERR, "eglCreateSyncKHR failed"); + if UNLIKELY (sync == EGL_NO_SYNC_KHR) { + Log::logger->log(Log::ERR, "eglCreateSyncKHR failed"); return nullptr; } @@ -3426,8 +2592,8 @@ UP CEGLSync::create() { glFlush(); int fd = g_pHyprOpenGL->m_proc.eglDupNativeFenceFDANDROID(g_pHyprOpenGL->m_eglDisplay, sync); - if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { - Debug::log(ERR, "eglDupNativeFenceFDANDROID failed"); + if UNLIKELY (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { + Log::logger->log(Log::ERR, "eglDupNativeFenceFDANDROID failed"); return nullptr; } @@ -3440,19 +2606,11 @@ UP CEGLSync::create() { } CEGLSync::~CEGLSync() { - if (m_sync == EGL_NO_SYNC_KHR) + if UNLIKELY (m_sync == EGL_NO_SYNC_KHR) return; - if (g_pHyprOpenGL && g_pHyprOpenGL->m_proc.eglDestroySyncKHR(g_pHyprOpenGL->m_eglDisplay, m_sync) != EGL_TRUE) - Debug::log(ERR, "eglDestroySyncKHR failed"); -} - -CFileDescriptor& CEGLSync::fd() { - return m_fd; -} - -CFileDescriptor&& CEGLSync::takeFd() { - return std::move(m_fd); + if UNLIKELY (g_pHyprOpenGL && g_pHyprOpenGL->m_proc.eglDestroySyncKHR(g_pHyprOpenGL->m_eglDisplay, m_sync) != EGL_TRUE) + Log::logger->log(Log::ERR, "eglDestroySyncKHR failed"); } bool CEGLSync::isValid() { diff --git a/src/render/OpenGL.hpp b/src/render/OpenGL.hpp index 45ed28dd4..82b8f0548 100644 --- a/src/render/OpenGL.hpp +++ b/src/render/OpenGL.hpp @@ -16,10 +16,13 @@ #include +#include "render/SyncFDManager.hpp" +#include "types.hpp" #include "Shader.hpp" #include "Texture.hpp" #include "Framebuffer.hpp" #include "Renderbuffer.hpp" +#include "../desktop/DesktopTypes.hpp" #include "pass/Pass.hpp" #include @@ -31,395 +34,327 @@ #include "../debug/TracyDefines.hpp" #include "../protocols/core/Compositor.hpp" +#include "ShaderLoader.hpp" +#include "gl/GLFramebuffer.hpp" +#include "gl/GLRenderbuffer.hpp" +#include "pass/TexPassElement.hpp" + +#define GLFB(ifb) dc(ifb.get()) struct gbm_device; -class CHyprRenderer; +namespace Render { + class IHyprRenderer; +} +namespace Config { + class CGradientValueData; +} -inline const float fullVerts[] = { - 1, 0, // top right - 0, 0, // top left - 1, 1, // bottom right - 0, 1, // bottom left -}; -inline const float fanVertsFull[] = {-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f}; +namespace Render::GL { -enum eDiscardMode : uint8_t { - DISCARD_OPAQUE = 1, - DISCARD_ALPHA = 1 << 1 -}; - -struct SRenderModifData { - enum eRenderModifType : uint8_t { - RMOD_TYPE_SCALE, /* scale by a float */ - RMOD_TYPE_SCALECENTER, /* scale by a float from the center */ - RMOD_TYPE_TRANSLATE, /* translate by a Vector2D */ - RMOD_TYPE_ROTATE, /* rotate by a float in rad from top left */ - RMOD_TYPE_ROTATECENTER, /* rotate by a float in rad from center */ + struct SVertex { + float x, y; // position + float u, v; // uv }; - std::vector> modifs; + 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 + }}; - void applyToBox(CBox& box); - void applyToRegion(CRegion& rg); - float combinedScale(); + inline const float fanVertsFull[] = {-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f}; - bool enabled = true; -}; - -enum eMonitorRenderFBs : uint8_t { - FB_MONITOR_RENDER_MAIN = 0, - FB_MONITOR_RENDER_CURRENT = 1, - FB_MONITOR_RENDER_OUT = 2, -}; - -enum eMonitorExtraRenderFBs : uint8_t { - FB_MONITOR_RENDER_EXTRA_OFFLOAD = 0, - FB_MONITOR_RENDER_EXTRA_MIRROR, - FB_MONITOR_RENDER_EXTRA_MIRROR_SWAP, - FB_MONITOR_RENDER_EXTRA_OFF_MAIN, - FB_MONITOR_RENDER_EXTRA_MONITOR_MIRROR, - FB_MONITOR_RENDER_EXTRA_BLUR, -}; - -struct SPreparedShaders { - std::string TEXVERTSRC; - std::string TEXVERTSRC320; - SShader m_shQUAD; - SShader m_shRGBA; - SShader m_shPASSTHRURGBA; - SShader m_shMATTE; - SShader m_shRGBX; - SShader m_shEXT; - SShader m_shBLUR1; - SShader m_shBLUR2; - SShader m_shBLURPREPARE; - SShader m_shBLURFINISH; - SShader m_shSHADOW; - SShader m_shBORDER1; - SShader m_shGLITCH; - SShader m_shCM; -}; - -struct SMonitorRenderData { - CFramebuffer offloadFB; - CFramebuffer mirrorFB; // these are used for some effects, - CFramebuffer mirrorSwapFB; // etc - CFramebuffer offMainFB; - CFramebuffer monitorMirrorFB; // used for mirroring outputs, does not contain artifacts like offloadFB - CFramebuffer blurFB; - - SP stencilTex = makeShared(); - - bool blurFBDirty = true; - bool blurFBShouldRender = false; -}; - -struct SCurrentRenderData { - PHLMONITORREF pMonitor; - Mat3x3 projection; - Mat3x3 savedProjection; - Mat3x3 monitorProjection; - - // FIXME: raw pointer galore! - SMonitorRenderData* pCurrentMonData = nullptr; - CFramebuffer* currentFB = nullptr; // current rendering to - CFramebuffer* mainFB = nullptr; // main to render to - CFramebuffer* outFB = nullptr; // out to render to (if offloaded, etc) - - CRegion damage; - CRegion finalDamage; // damage used for funal off -> main - - SRenderModifData renderModif; - float mouseZoomFactor = 1.f; - bool mouseZoomUseMouse = true; // true by default - bool useNearestNeighbor = false; - bool blockScreenShader = false; - bool simplePass = false; - - Vector2D primarySurfaceUVTopLeft = Vector2D(-1, -1); - Vector2D primarySurfaceUVBottomRight = Vector2D(-1, -1); - - CBox clipBox = {}; // scaled coordinates - CRegion clipRegion; - - uint32_t discardMode = DISCARD_OPAQUE; - float discardOpacity = 0.f; - - PHLLSREF currentLS; - PHLWINDOWREF currentWindow; - WP surface; -}; - -class CEGLSync { - public: - static UP create(); - - ~CEGLSync(); - - Hyprutils::OS::CFileDescriptor& fd(); - Hyprutils::OS::CFileDescriptor&& takeFd(); - bool isValid(); - - private: - CEGLSync() = default; - - Hyprutils::OS::CFileDescriptor m_fd; - EGLSyncKHR m_sync = EGL_NO_SYNC_KHR; - bool m_valid = false; - - friend class CHyprOpenGLImpl; -}; - -class CGradientValueData; - -class CHyprOpenGLImpl { - public: - CHyprOpenGLImpl(); - ~CHyprOpenGLImpl(); - - struct SRectRenderData { - const CRegion* damage = nullptr; - int round = 0; - float roundingPower = 2.F; - bool blur = false; - float blurA = 1.F; - bool xray = false; + enum eMonitorRenderFBs : uint8_t { + FB_MONITOR_RENDER_MAIN = 0, + FB_MONITOR_RENDER_CURRENT = 1, + FB_MONITOR_RENDER_OUT = 2, }; - struct STextureRenderData { - const CRegion* damage = nullptr; - SP surface = nullptr; - float a = 1.F; - bool blur = false; - float blurA = 1.F, overallA = 1.F; - int round = 0; - float roundingPower = 2.F; - bool discardActive = false; - bool allowCustomUV = false; - bool allowDim = true; - bool noAA = false; - bool blockBlurOptimization = false; - GLenum wrapX = GL_CLAMP_TO_EDGE, wrapY = GL_CLAMP_TO_EDGE; - bool cmBackToSRGB = false; - SP cmBackToSRGBSource; + enum eMonitorExtraRenderFBs : uint8_t { + FB_MONITOR_RENDER_EXTRA_OFFLOAD = 0, + FB_MONITOR_RENDER_EXTRA_MIRROR, + FB_MONITOR_RENDER_EXTRA_MIRROR_SWAP, + FB_MONITOR_RENDER_EXTRA_OFF_MAIN, + FB_MONITOR_RENDER_EXTRA_MONITOR_MIRROR, + FB_MONITOR_RENDER_EXTRA_BLUR, }; - struct SBorderRenderData { - int round = 0; - float roundingPower = 2.F; - int borderSize = 1; - float a = 1.0; - int outerRound = -1; /* use round */ + struct SFragShaderDesc { + Render::ePreparedFragmentShader id; + const char* file; }; - 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 renderRoundedShadow(const CBox&, int round, float roundingPower, int range, const CHyprColor& color, float a = 1.0); - void renderBorder(const CBox&, const CGradientValueData&, SBorderRenderData data); - void renderBorder(const CBox&, const CGradientValueData&, const CGradientValueData&, float lerp, SBorderRenderData data); - void renderTextureMatte(SP tex, const CBox& pBox, CFramebuffer& matte); - - void pushMonitorTransformEnabled(bool enabled); - void popMonitorTransformEnabled(); - - void setRenderModifEnabled(bool enabled); - void setViewport(GLint x, GLint y, GLsizei width, GLsizei height); - void setCapStatus(int cap, bool status); - - void saveMatrix(); - void setMatrixScaleTranslate(const Vector2D& translate, const float& scale); - void restoreMatrix(); - - void blend(bool enabled); - - bool shouldUseNewBlurOptimizations(PHLLS pLayer, PHLWINDOW pWindow); - - void clear(const CHyprColor&); - void clearWithTex(); - void scissor(const CBox&, bool transform = true); - void scissor(const pixman_box32*, bool transform = true); - void scissor(const int x, const int y, const int w, const int h, bool transform = true); - - void destroyMonitorResources(PHLMONITORREF); - - void markBlurDirtyForMonitor(PHLMONITOR); - - void preWindowPass(); - bool preBlurQueued(); - void preRender(PHLMONITOR); - - void saveBufferForMirror(const CBox&); - void renderMirrored(); - - void applyScreenShader(const std::string& path); - - void bindOffMain(); - void renderOffToMain(CFramebuffer* off); - void bindBackOnMain(); - - 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); - - void setDamage(const CRegion& damage, std::optional finalDamage = {}); - - uint32_t getPreferredReadFormat(PHLMONITOR pMonitor); - std::vector getDRMFormats(); - EGLImageKHR createEGLImage(const Aquamarine::SDMABUFAttrs& attrs); - - bool initShaders(); - - 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); - - void ensureLockTexturesRendered(bool load); - - bool explicitSyncSupported(); - - bool m_shadersInitialized = false; - SP m_shaders; - - SCurrentRenderData m_renderData; - - Hyprutils::OS::CFileDescriptor m_gbmFD; - gbm_device* m_gbmDevice = nullptr; - EGLContext m_eglContext = nullptr; - EGLDisplay m_eglDisplay = nullptr; - EGLDeviceEXT m_eglDevice = nullptr; - uint m_failedAssetsNo = 0; - - 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; - PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr; - PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR = nullptr; - PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = nullptr; - PFNEGLQUERYDMABUFFORMATSEXTPROC eglQueryDmaBufFormatsEXT = nullptr; - PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT = nullptr; - PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = nullptr; - PFNEGLDEBUGMESSAGECONTROLKHRPROC eglDebugMessageControlKHR = nullptr; - PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT = nullptr; - PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT = nullptr; - PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT = nullptr; - PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR = nullptr; - PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR = nullptr; - PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID = nullptr; - PFNEGLWAITSYNCKHRPROC eglWaitSyncKHR = nullptr; - } m_proc; - - struct { - bool EXT_read_format_bgra = false; - bool EXT_image_dma_buf_import = false; - bool EXT_image_dma_buf_import_modifiers = 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; - - enum eEGLContextVersion : uint8_t { - EGL_CONTEXT_GLES_2_0 = 0, - EGL_CONTEXT_GLES_3_0, - EGL_CONTEXT_GLES_3_2, + struct SPreparedShaders { + std::string TEXVERTSRC; + std::string TEXVERTSRC320; + std::array>, Render::SH_FRAG_LAST> fragVariants; }; - eEGLContextVersion m_eglContextVersion = EGL_CONTEXT_GLES_3_2; + struct SCurrentRenderData { + PHLMONITORREF pMonitor; + Mat3x3 projection; + Mat3x3 savedProjection; + Mat3x3 monitorProjection; - enum eCachedCapStatus : uint8_t { - CAP_STATUS_BLEND = 0, - CAP_STATUS_SCISSOR_TEST, - CAP_STATUS_STENCIL_TEST, - CAP_STATUS_END + SP currentFB = nullptr; // current rendering to + SP mainFB = nullptr; // main to render to + SP outFB = nullptr; // out to render to (if offloaded, etc) + + CRegion damage; + CRegion finalDamage; // damage used for funal off -> main + + Render::SRenderModifData renderModif; + float mouseZoomFactor = 1.f; + bool mouseZoomUseMouse = true; // true by default + 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); + + CBox clipBox = {}; // scaled coordinates + CRegion clipRegion; + + uint32_t discardMode = DISCARD_OPAQUE; + float discardOpacity = 0.f; + + PHLLSREF currentLS; + PHLWINDOWREF currentWindow; + WP surface; }; - private: - struct { - GLint x = 0; - GLint y = 0; - GLsizei width = 0; - GLsizei height = 0; - } m_lastViewport; + class CEGLSync : public ISyncFDManager { + public: + static UP create(); + ~CEGLSync() override; - std::array m_capStatus; + bool isValid() override; - std::vector m_drmFormats; - bool m_hasModifiers = false; + private: + CEGLSync() : ISyncFDManager() {}; - int m_drmFD = -1; - std::string m_extensions; + EGLSyncKHR m_sync = EGL_NO_SYNC_KHR; - bool m_fakeFrame = false; - bool m_applyFinalShader = false; - bool m_blend = false; - bool m_offloadedFramebuffer = false; - bool m_cmSupported = true; + friend class CHyprOpenGLImpl; + }; - bool m_monitorTransformEnabled = false; // do not modify directly - std::stack m_monitorTransformStack; - SP m_missingAssetTexture; - SP m_lockDeadTexture; - SP m_lockDead2Texture; - SP m_lockTtyTextTexture; - SShader m_finalScreenShader; - CTimer m_globalTimer; - GLuint m_currentProgram; - ASP m_backgroundResource; - bool m_backgroundResourceFailed = false; + class CHyprOpenGLImpl { + public: + CHyprOpenGLImpl(); + ~CHyprOpenGLImpl(); - 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 initMissingAssetTexture(); - void requestBackgroundResource(); + struct SRectRenderData { + const CRegion* damage = nullptr; + int round = 0; + float roundingPower = 2.F; + bool blur = false; + float blurA = 1.F; + bool xray = false; + }; - // for the final shader - std::array m_pressedHistoryTimers = {}; - std::array m_pressedHistoryPositions = {}; - GLint m_pressedHistoryKilled = 0; - GLint m_pressedHistoryTouched = 0; + struct STextureRenderData { + bool blur = false; + float blurA = 1.F, overallA = 1.F; + bool blockBlurOptimization = false; + SP blurredBG; - // - std::optional> getModsForFormat(EGLint format); + const CRegion* damage = nullptr; + SP surface = nullptr; + float a = 1.F; + int round = 0; + float roundingPower = 2.F; + bool discardActive = false; + bool allowCustomUV = false; + bool allowDim = true; + bool noAA = false; // unused + GLenum wrapX = GL_CLAMP_TO_EDGE, wrapY = GL_CLAMP_TO_EDGE; + bool cmBackToSRGB = false; + bool finalMonitorCM = false; + SP cmBackToSRGBSource; - // returns the out FB, can be either Mirror or MirrorSwap - CFramebuffer* blurMainFramebufferWithDamage(float a, CRegion* damage); - CFramebuffer* blurFramebufferWithDamage(float a, CRegion* damage, CFramebuffer& source); + uint32_t discardMode = DISCARD_OPAQUE; + float discardOpacity = 0.f; - void passCMUniforms(SShader&, const NColorManagement::SImageDescription& imageDescription, const NColorManagement::SImageDescription& targetImageDescription, - bool modifySDR = false, float sdrMinLuminance = -1.0f, int sdrMaxLuminance = -1); - void passCMUniforms(SShader&, const NColorManagement::SImageDescription& 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); + CRegion clipRegion; + PHLLSREF currentLS; - void preBlurForCurrentMonitor(); + Vector2D primarySurfaceUVTopLeft = Vector2D(-1, -1); + Vector2D primarySurfaceUVBottomRight = Vector2D(-1, -1); + }; - friend class CHyprRenderer; - friend class CTexPassElement; - friend class CPreBlurElement; - friend class CSurfacePassElement; -}; + struct SBorderRenderData { + int round = 0; + float roundingPower = 2.F; + int borderSize = 1; + float a = 1.0; + int outerRound = -1; /* use round */ + }; -inline UP g_pHyprOpenGL; + void makeEGLCurrent(); + void begin(PHLMONITOR, const CRegion& damage, SP fb = nullptr, std::optional finalDamage = {}); + void beginSimple(PHLMONITOR, const CRegion& damage, SP rb = nullptr, SP fb = nullptr); + void end(); + + void renderRect(const CBox&, const CHyprColor&, SRectRenderData data); + void renderTexture(SP, const CBox&, STextureRenderData data); + void renderRoundedShadow(const CBox&, int round, float roundingPower, int range, const CHyprColor& color, float a = 1.0); + void renderInnerGlow(const CBox&, int round, float roundingPower, int range, const CHyprColor& color, int glowPower, float a = 1.0); + void renderBorder(const CBox&, const Config::CGradientValueData&, SBorderRenderData data); + void renderBorder(const CBox&, const Config::CGradientValueData&, const Config::CGradientValueData&, float lerp, SBorderRenderData data); + void renderTextureMatte(SP tex, const CBox& pBox, SP matte); + void renderTexturePrimitive(SP tex, const CBox& box); + + void setViewport(GLint x, GLint y, GLsizei width, GLsizei height); + void setCapStatus(int cap, bool status); + + void blend(bool enabled); + + void scissor(const CBox&, bool transform = true); + void scissor(const pixman_box32*, bool transform = true); + void scissor(const int x, const int y, const int w, const int h, bool transform = true); + + void destroyMonitorResources(PHLMONITORREF); + + void preRender(PHLMONITOR); + + void saveBufferForMirror(const CBox&); + + void applyScreenShader(const std::string& path); + + void renderOffToMain(SP off); + + std::vector getDRMFormats(); + std::vector getDRMFormatModifiers(DRMFormat format); + EGLImageKHR createEGLImage(const Aquamarine::SDMABUFAttrs& attrs); + + bool initShaders(const std::string& path = ""); + + WP useShader(WP prog); + + bool explicitSyncSupported(); + WP getShaderVariant(Render::ePreparedFragmentShader frag, Render::ShaderFeatureFlags features = 0); + + 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; + + std::map> m_monitorBGFBs; + + struct { + PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES = nullptr; + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr; + PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR = nullptr; + PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = nullptr; + PFNEGLQUERYDMABUFFORMATSEXTPROC eglQueryDmaBufFormatsEXT = nullptr; + PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT = nullptr; + PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = nullptr; + PFNEGLDEBUGMESSAGECONTROLKHRPROC eglDebugMessageControlKHR = nullptr; + PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT = nullptr; + PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT = nullptr; + PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT = nullptr; + PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR = nullptr; + PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR = nullptr; + PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID = nullptr; + PFNEGLWAITSYNCKHRPROC eglWaitSyncKHR = nullptr; + } m_proc; + + struct { + 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; + + enum eEGLContextVersion : uint8_t { + EGL_CONTEXT_GLES_2_0 = 0, + EGL_CONTEXT_GLES_3_0, + EGL_CONTEXT_GLES_3_2, + }; + + eEGLContextVersion m_eglContextVersion = EGL_CONTEXT_GLES_3_2; + + enum eCachedCapStatus : uint8_t { + CAP_STATUS_BLEND = 0, + CAP_STATUS_SCISSOR_TEST, + CAP_STATUS_STENCIL_TEST, + CAP_STATUS_END + }; + + private: + struct { + GLint x = 0; + GLint y = 0; + GLsizei width = 0; + GLsizei height = 0; + } m_lastViewport; + + std::array m_capStatus = {}; + + std::vector m_drmFormats; + bool m_hasModifiers = false; + + int m_drmFD = -1; + std::string m_extensions; + + bool m_fakeFrame = false; + bool m_applyFinalShader = false; + bool m_blend = false; + bool m_offloadedFramebuffer = false; + bool m_cmSupported = true; + + SP m_finalScreenShader; + GLuint m_currentProgram; + + void initDRMFormats(); + void initEGL(bool gbm); + EGLDeviceEXT eglDeviceFromDRMFD(int drmFD); + + // for the final shader + std::array m_pressedHistoryTimers = {}; + std::array m_pressedHistoryPositions = {}; + GLint m_pressedHistoryKilled = 0; + GLint m_pressedHistoryTouched = 0; + + // + std::optional> getModsForFormat(EGLint format); + + // returns the out FB, can be either Mirror or MirrorSwap + SP blurFramebufferWithDamage(float a, CRegion* damage, CGLFramebuffer& source); + + void passCMUniforms(WP, const NColorManagement::PImageDescription imageDescription, const NColorManagement::PImageDescription targetImageDescription, + bool modifySDR, float sdrMinLuminance, int sdrMaxLuminance, const SCMSettings& settings); + 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 passCMUniforms(WP, const NColorManagement::PImageDescription imageDescription, const SCMSettings& settings); + 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(SP tex, const STextureRenderData& data, eTextureType texType, const CBox& newBox); + void renderTextureInternal(SP, const CBox&, const STextureRenderData& data); + void renderTextureWithBlurInternal(SP, const CBox&, const STextureRenderData& data); + + friend class IHyprRenderer; + friend class CHyprGLRenderer; + friend class CGLElementRenderer; + friend class CTexPassElement; + friend class CPreBlurElement; + friend class CSurfacePassElement; + }; + + inline UP g_pHyprOpenGL; +} diff --git a/src/render/Renderbuffer.cpp b/src/render/Renderbuffer.cpp index d47a5195c..34ce3e6a2 100644 --- a/src/render/Renderbuffer.cpp +++ b/src/render/Renderbuffer.cpp @@ -1,76 +1,22 @@ #include "Renderbuffer.hpp" +#include "Framebuffer.hpp" #include "Renderer.hpp" -#include "OpenGL.hpp" -#include "../Compositor.hpp" -#include "../protocols/types/Buffer.hpp" +#include #include #include #include -CRenderbuffer::~CRenderbuffer() { - if (!g_pCompositor || g_pCompositor->m_isShuttingDown || !g_pHyprRenderer) - return; - - g_pHyprRenderer->makeEGLCurrent(); - - unbind(); - m_framebuffer.release(); - glDeleteRenderbuffers(1, &m_rbo); - - g_pHyprOpenGL->m_proc.eglDestroyImageKHR(g_pHyprOpenGL->m_eglDisplay, m_image); -} - -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) { - Debug::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) { - Debug::log(ERR, "rbo: glCheckFramebufferStatus failed"); - return; - } - - m_framebuffer.unbind(); +using namespace Render; +IRenderbuffer::IRenderbuffer(SP buffer, uint32_t format) : m_hlBuffer(buffer) { m_listeners.destroyBuffer = buffer->events.destroy.listen([this] { g_pHyprRenderer->onRenderbufferDestroy(this); }); - - m_good = true; } -bool CRenderbuffer::good() { +bool IRenderbuffer::good() { return m_good; } -void CRenderbuffer::bind() { - glBindRenderbuffer(GL_RENDERBUFFER, m_rbo); - bindFB(); -} - -void CRenderbuffer::bindFB() { - m_framebuffer.bind(); -} - -void CRenderbuffer::unbind() { - glBindRenderbuffer(GL_RENDERBUFFER, 0); - m_framebuffer.unbind(); -} - -CFramebuffer* CRenderbuffer::getFB() { - return &m_framebuffer; +SP IRenderbuffer::getFB() { + return m_framebuffer; } diff --git a/src/render/Renderbuffer.hpp b/src/render/Renderbuffer.hpp index c0924141d..e19af22fe 100644 --- a/src/render/Renderbuffer.hpp +++ b/src/render/Renderbuffer.hpp @@ -5,30 +5,26 @@ #include "Framebuffer.hpp" #include -class CMonitor; +namespace Render { + class IRenderbuffer { + public: + IRenderbuffer(SP buffer, uint32_t format); + virtual ~IRenderbuffer() = default; -class CRenderbuffer { - public: - CRenderbuffer(SP buffer, uint32_t format); - ~CRenderbuffer(); + bool good(); + SP getFB(); - bool good(); - void bind(); - void bindFB(); - void unbind(); - CFramebuffer* getFB(); - uint32_t getFormat(); + virtual void bind() = 0; + virtual void unbind() = 0; - WP m_hlBuffer; + WP m_hlBuffer; - private: - void* m_image = nullptr; - GLuint m_rbo = 0; - CFramebuffer m_framebuffer; - uint32_t m_drmFormat = 0; - bool m_good = false; + protected: + SP m_framebuffer; + bool m_good = false; - struct { - CHyprSignalListener destroyBuffer; - } m_listeners; -}; \ No newline at end of file + struct { + CHyprSignalListener destroyBuffer; + } m_listeners; + }; +} diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index c4c72c41b..a77e855d1 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -3,17 +3,17 @@ #include "../helpers/math/Math.hpp" #include #include +#include #include #include "../config/ConfigValue.hpp" -#include "../config/ConfigManager.hpp" +#include "../config/legacy/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/Window.hpp" -#include "../desktop/LayerSurface.hpp" +#include "../desktop/view/Window.hpp" +#include "../desktop/view/LayerSurface.hpp" +#include "../desktop/view/GlobalViewMethods.hpp" #include "../desktop/state/FocusState.hpp" #include "../protocols/SessionLock.hpp" #include "../protocols/LayerShell.hpp" @@ -24,28 +24,51 @@ #include "../protocols/DRMSyncobj.hpp" #include "../protocols/LinuxDMABUF.hpp" #include "../helpers/sync/SyncTimeline.hpp" -#include "../hyprerror/HyprError.hpp" -#include "../debug/HyprDebugOverlay.hpp" -#include "../debug/HyprNotificationOverlay.hpp" +#include "../errorOverlay/Overlay.hpp" +#include "../debug/Overlay.hpp" +#include "../notification/NotificationOverlay.hpp" +#include "../layout/LayoutManager.hpp" +#include "../layout/space/Space.hpp" #include "../i18n/Engine.hpp" -#include "helpers/CursorShapes.hpp" -#include "helpers/Monitor.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 "../managers/screenshare/ScreenshareManager.hpp" #include "pass/TexPassElement.hpp" #include "pass/ClearPassElement.hpp" #include "pass/RectPassElement.hpp" #include "pass/RendererHintsPassElement.hpp" #include "pass/SurfacePassElement.hpp" -#include "debug/Log.hpp" +#include "../debug/log/Logger.hpp" #include "../protocols/ColorManagement.hpp" #include "../protocols/types/ContentType.hpp" #include "../helpers/MiscFunctions.hpp" -#include "render/OpenGL.hpp" +#include "AsyncResourceGatherer.hpp" +#include "ElementRenderer.hpp" +#include "Framebuffer.hpp" +#include "OpenGL.hpp" +#include "Texture.hpp" +#include "./pass/PreBlurElement.hpp" +#include "types.hpp" +#include +#include +#include +#include +#include +#include +#include +#include #include +#include using namespace Hyprutils::Utils; using namespace Hyprutils::OS; using enum NContentType::eContentType; using namespace NColorManagement; +using namespace Render; extern "C" { #include @@ -57,7 +80,10 @@ static int cursorTicker(void* data) { return 0; } -CHyprRenderer::CHyprRenderer() { +IHyprRenderer::IHyprRenderer() { + m_globalTimer.reset(); + pushMonitorTransformEnabled(false); + if (g_pCompositor->m_aqBackend->hasSession()) { size_t drmDevices = 0; for (auto const& dev : g_pCompositor->m_aqBackend->session->sessionDevices) { @@ -75,14 +101,14 @@ CHyprRenderer::CHyprRenderer() { else if (name.contains("softpipe") || name.contains("Software Rasterizer") || name.contains("llvmpipe")) m_software = true; - Debug::log(LOG, "DRM driver information: {} v{}.{}.{} from {} description {}", name, DRMV->version_major, DRMV->version_minor, DRMV->version_patchlevel, - std::string{DRMV->date, DRMV->date_len}, std::string{DRMV->desc, DRMV->desc_len}); + Log::logger->log(Log::DEBUG, "DRM driver information: {} v{}.{}.{} from {} description {}", name, DRMV->version_major, DRMV->version_minor, DRMV->version_patchlevel, + std::string{DRMV->date, DRMV->date_len}, std::string{DRMV->desc, DRMV->desc_len}); drmFreeVersion(DRMV); } m_mgpu = drmDevices > 1; } else { - Debug::log(LOG, "Aq backend has no session, omitting full DRM node checks"); + Log::logger->log(Log::DEBUG, "Aq backend has no session, omitting full DRM node checks"); const auto DRMV = drmGetVersion(g_pCompositor->m_drm.fd); @@ -97,21 +123,21 @@ CHyprRenderer::CHyprRenderer() { else if (name.contains("softpipe") || name.contains("Software Rasterizer") || name.contains("llvmpipe")) m_software = true; - Debug::log(LOG, "Primary DRM driver information: {} v{}.{}.{} from {} description {}", name, DRMV->version_major, DRMV->version_minor, DRMV->version_patchlevel, - std::string{DRMV->date, DRMV->date_len}, std::string{DRMV->desc, DRMV->desc_len}); + Log::logger->log(Log::DEBUG, "Primary DRM driver information: {} v{}.{}.{} from {} description {}", name, DRMV->version_major, DRMV->version_minor, + DRMV->version_patchlevel, std::string{DRMV->date, DRMV->date_len}, std::string{DRMV->desc, DRMV->desc_len}); } else { - Debug::log(LOG, "No primary DRM driver information found"); + Log::logger->log(Log::DEBUG, "No primary DRM driver information found"); } drmFreeVersion(DRMV); } if (m_nvidia) - Debug::log(WARN, "NVIDIA detected, please remember to follow nvidia instructions on the wiki"); + Log::logger->log(Log::WARN, "NVIDIA detected, please remember to follow nvidia instructions on the wiki"); // cursor hiding stuff - static auto P = g_pHookSystem->hookDynamic("keyPress", [&](void* self, SCallbackInfo& info, std::any param) { + static auto P = Event::bus()->m_events.input.keyboard.key.listen([&](IKeyboard::SKeyEvent e, Event::SCallbackInfo&) { if (m_cursorHiddenConditions.hiddenOnKeyboard) return; @@ -119,19 +145,21 @@ CHyprRenderer::CHyprRenderer() { ensureCursorRenderingMode(); }); - 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.hiddenOnTimeout) + static auto P2 = Event::bus()->m_events.input.mouse.move.listen([&](Vector2D pos, Event::SCallbackInfo&) { + if (!m_cursorHiddenConditions.hiddenOnKeyboard && m_cursorHiddenConditions.hiddenOnTouch == g_pInputManager->m_lastInputTouch && + m_cursorHiddenConditions.hiddenOnTablet == g_pInputManager->m_lastInputTablet && !m_cursorHiddenConditions.hiddenOnTimeout) return; m_cursorHiddenConditions.hiddenOnKeyboard = false; m_cursorHiddenConditions.hiddenOnTimeout = false; m_cursorHiddenConditions.hiddenOnTouch = g_pInputManager->m_lastInputTouch; + m_cursorHiddenConditions.hiddenOnTablet = g_pInputManager->m_lastInputTablet; ensureCursorRenderingMode(); }); - static auto P3 = g_pHookSystem->hookDynamic("focusedMon", [&](void* self, SCallbackInfo& info, std::any param) { + static auto P3 = Event::bus()->m_events.monitor.focused.listen([&](PHLMONITOR mon) { g_pEventLoopManager->doLater([this]() { - if (!g_pHyprError->active()) + if (!ErrorOverlay::overlay()->active()) return; for (auto& m : g_pCompositor->m_monitors) { arrangeLayersForMonitor(m->m_id); @@ -139,11 +167,9 @@ CHyprRenderer::CHyprRenderer() { }); }); - static auto P4 = g_pHookSystem->hookDynamic("windowUpdateRules", [&](void* self, SCallbackInfo& info, std::any param) { - const auto PWINDOW = std::any_cast(param); - - if (PWINDOW->m_ruleApplicator->renderUnfocused().valueOrDefault()) - addWindowToRenderUnfocused(PWINDOW); + static auto P4 = Event::bus()->m_events.window.updateRules.listen([&](PHLWINDOW window) { + if (window->m_ruleApplicator->renderUnfocused().valueOrDefault()) + addWindowToRenderUnfocused(window); }); m_cursorTicker = wl_event_loop_add_timer(g_pCompositor->m_wlEventLoop, cursorTicker, nullptr); @@ -164,11 +190,11 @@ CHyprRenderer::CHyprRenderer() { continue; } - if (!w->m_wlSurface || !w->m_wlSurface->resource() || shouldRenderWindow(w.lock())) + if (!w->wlSurface() || !w->wlSurface()->resource() || shouldRenderWindow(w.lock())) continue; - w->m_wlSurface->resource()->frame(Time::steadyNow()); - auto FEEDBACK = makeUnique(w->m_wlSurface->resource()); + w->wlSurface()->resource()->frame(Time::steadyNow()); + auto FEEDBACK = makeUnique(w->wlSurface()->resource()); FEEDBACK->attachMonitor(Desktop::focusState()->monitor()); FEEDBACK->discarded(); PROTO::presentation->queueData(std::move(FEEDBACK)); @@ -185,12 +211,16 @@ CHyprRenderer::CHyprRenderer() { g_pEventLoopManager->addTimer(m_renderUnfocusedTimer); } -CHyprRenderer::~CHyprRenderer() { +IHyprRenderer::~IHyprRenderer() { if (m_cursorTicker) wl_event_source_remove(m_cursorTicker); } -bool CHyprRenderer::shouldRenderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor) { +WP IHyprRenderer::glBackend() { + return Render::GL::g_pHyprOpenGL; +} + +bool IHyprRenderer::shouldRenderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor) { if (!pWindow->visibleOnMonitor(pMonitor)) return false; @@ -256,7 +286,7 @@ bool CHyprRenderer::shouldRenderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor) { return false; } -bool CHyprRenderer::shouldRenderWindow(PHLWINDOW pWindow) { +bool IHyprRenderer::shouldRenderWindow(PHLWINDOW pWindow) { if (!validMapped(pWindow)) return false; @@ -283,12 +313,14 @@ bool CHyprRenderer::shouldRenderWindow(PHLWINDOW pWindow) { return false; } -void CHyprRenderer::renderWorkspaceWindowsFullscreen(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& time) { +void IHyprRenderer::renderWorkspaceWindowsFullscreen(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& time) { PHLWINDOW pWorkspaceWindow = nullptr; - EMIT_HOOK_EVENT("render", RENDER_PRE_WINDOWS); + Event::bus()->m_events.render.stage.emit(RENDER_PRE_WINDOWS); - // loop over the tiled windows that are fading out + // pre-filter renderable windows once for the tiled + floating passes + std::vector windows; + windows.reserve(g_pCompositor->m_windows.size()); for (auto const& w : g_pCompositor->m_windows) { if (!shouldRenderWindow(w, pMonitor)) continue; @@ -296,7 +328,15 @@ void CHyprRenderer::renderWorkspaceWindowsFullscreen(PHLMONITOR pMonitor, PHLWOR if (w->m_alpha->value() == 0.f) continue; - if (w->isFullscreen() || w->m_isFloating) + if (w->isFullscreen()) + continue; + + windows.emplace_back(w); + } + + // tiled windows that are fading out + for (auto const& w : windows) { + if (w->m_isFloating) continue; if (pWorkspace->m_isSpecialWorkspace != w->onSpecialWorkspace()) @@ -306,14 +346,8 @@ void CHyprRenderer::renderWorkspaceWindowsFullscreen(PHLMONITOR pMonitor, PHLWOR } // and floating ones too - for (auto const& w : g_pCompositor->m_windows) { - if (!shouldRenderWindow(w, pMonitor)) - continue; - - if (w->m_alpha->value() == 0.f) - continue; - - if (w->isFullscreen() || !w->m_isFloating) + for (auto const& w : windows) { + if (!w->m_isFloating) continue; if (w->m_monitor == pWorkspace->m_monitor && pWorkspace->m_isSpecialWorkspace != w->onSpecialWorkspace()) @@ -360,30 +394,38 @@ void CHyprRenderer::renderWorkspaceWindowsFullscreen(PHLMONITOR pMonitor, PHLWOR // then render windows over fullscreen. for (auto const& w : g_pCompositor->m_windows) { - if (w->workspaceID() != pWorkspaceWindow->workspaceID() || !w->m_isFloating || (!w->m_createdOverFullscreen && !w->m_pinned) || (!w->m_isMapped && !w->m_fadingOut) || - w->isFullscreen()) + const bool shouldSkipWindow = w->workspaceID() != pWorkspaceWindow->workspaceID() || !w->m_isFloating || (!w->m_createdOverFullscreen && !w->m_pinned) || + (!w->m_isMapped && !w->m_fadingOut) || w->isFullscreen(); + + if (shouldSkipWindow) continue; - if (w->m_monitor == pWorkspace->m_monitor && pWorkspace->m_isSpecialWorkspace != w->onSpecialWorkspace()) + const bool mismatchedSpecialWorkspace = w->m_monitor == pWorkspace->m_monitor && pWorkspace->m_isSpecialWorkspace != w->onSpecialWorkspace(); + + if (mismatchedSpecialWorkspace) continue; - if (pWorkspace->m_isSpecialWorkspace && w->m_monitor != pWorkspace->m_monitor) + const bool specialWorkspaceOnDifferentMonitor = pWorkspace->m_isSpecialWorkspace && w->m_monitor != pWorkspace->m_monitor; + + if (specialWorkspaceOnDifferentMonitor) continue; // special on another are rendered as a part of the base pass renderWindow(w, pMonitor, time, true, RENDER_PASS_ALL); } } -void CHyprRenderer::renderWorkspaceWindows(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& time) { +void IHyprRenderer::renderWorkspaceWindows(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& time) { PHLWINDOW lastWindow; - EMIT_HOOK_EVENT("render", RENDER_PRE_WINDOWS); + Event::bus()->m_events.render.stage.emit(RENDER_PRE_WINDOWS); std::vector windows, tiledFadingOut; windows.reserve(g_pCompositor->m_windows.size()); for (auto const& w : g_pCompositor->m_windows) { - if (w->isHidden() || (!w->m_isMapped && !w->m_fadingOut)) + const bool isNotRenderable = w->isHidden() || (!w->m_isMapped && !w->m_fadingOut); + + if (isNotRenderable) continue; if (!shouldRenderWindow(w, pMonitor)) @@ -472,7 +514,16 @@ void CHyprRenderer::renderWorkspaceWindows(PHLMONITOR pMonitor, PHLWORKSPACE pWo } } -void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const Time::steady_tp& time, bool decorate, eRenderPassMode mode, bool ignorePosition, bool standalone) { +void IHyprRenderer::bindOffMain() { + bindFB(m_renderData.pMonitor->resources()->getUnusedWorkBuffer()); + draw(CClearPassElement::SClearData{{0, 0, 0, 0}}); +} + +void IHyprRenderer::bindBackOnMain() { + bindFB(m_renderData.mainFB); +} + +void IHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const Time::steady_tp& time, bool decorate, eRenderPassMode mode, bool ignorePosition, bool standalone) { if (pWindow->isHidden() && !standalone) return; @@ -516,7 +567,7 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T // whether to use m_fMovingToWorkspaceAlpha, only if fading out into an invisible ws const bool USE_WORKSPACE_FADE_ALPHA = pWindow->m_monitorMovedFrom != -1 && (!PWORKSPACE || !PWORKSPACE->isVisible()); - renderdata.surface = pWindow->m_wlSurface->resource(); + renderdata.surface = pWindow->wlSurface()->resource(); renderdata.dontRound = pWindow->isEffectiveInternalFSMode(FSMODE_FULLSCREEN); renderdata.fadeAlpha = pWindow->m_alpha->value() * (pWindow->m_pinned || USE_WORKSPACE_FADE_ALPHA ? 1.f : PWORKSPACE->m_alpha->value()) * (USE_WORKSPACE_FADE_ALPHA ? pWindow->m_movingToWorkspaceAlpha->value() : 1.F) * pWindow->m_movingFromWorkspaceAlpha->value(); @@ -539,14 +590,14 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T renderdata.pWindow = pWindow; // for plugins - g_pHyprOpenGL->m_renderData.currentWindow = pWindow; + m_renderData.currentWindow = pWindow; - EMIT_HOOK_EVENT("render", RENDER_PRE_WINDOW); + Event::bus()->m_events.render.stage.emit(RENDER_PRE_WINDOW); const auto fullAlpha = renderdata.alpha * renderdata.fadeAlpha; if (*PDIMAROUND && pWindow->m_ruleApplicator->dimAround().valueOrDefault() && !m_bRenderingSnapshot && mode != RENDER_PASS_POPUP) { - CBox monbox = {0, 0, g_pHyprOpenGL->m_renderData.pMonitor->m_transformedSize.x, g_pHyprOpenGL->m_renderData.pMonitor->m_transformedSize.y}; + CBox monbox = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}; CRectPassElement::SRectData data; data.color = CHyprColor(0, 0, 0, *PDIMAROUND * fullAlpha); data.box = monbox; @@ -569,7 +620,7 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T const bool TRANSFORMERSPRESENT = !pWindow->m_transformers.empty(); if (TRANSFORMERSPRESENT) { - g_pHyprOpenGL->bindOffMain(); + bindOffMain(); for (auto const& t : pWindow->m_transformers) { t->preWindowRender(&renderdata); @@ -596,7 +647,7 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T if ((pWindow->m_isX11 && *PXWLUSENN) || pWindow->m_ruleApplicator->nearestNeighbor().valueOrDefault()) renderdata.useNearestNeighbor = true; - if (pWindow->m_wlSurface->small() && !pWindow->m_wlSurface->m_fillIgnoreSmall && renderdata.blur) { + if (pWindow->wlSurface()->small() && !pWindow->wlSurface()->m_fillIgnoreSmall && renderdata.blur) { CBox wb = {renderdata.pos.x - pMonitor->m_position.x, renderdata.pos.y - pMonitor->m_position.y, renderdata.w, renderdata.h}; wb.scale(pMonitor->m_scale).round(); CRectPassElement::SRectData data; @@ -605,13 +656,13 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T data.round = renderdata.dontRound ? 0 : renderdata.rounding - 1; data.blur = true; data.blurA = renderdata.fadeAlpha; - data.xray = g_pHyprOpenGL->shouldUseNewBlurOptimizations(nullptr, pWindow); + data.xray = shouldUseNewBlurOptimizations(nullptr, pWindow); m_renderPass.add(makeUnique(data)); renderdata.blur = false; } renderdata.surfaceCounter = 0; - pWindow->m_wlSurface->resource()->breadthfirst( + pWindow->wlSurface()->resource()->breadthfirst( [this, &renderdata, &pWindow](SP s, const Vector2D& offset, void* data) { if (!s->m_current.texture) return; @@ -622,7 +673,7 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T renderdata.localPos = offset; renderdata.texture = s->m_current.texture; renderdata.surface = s; - renderdata.mainSurface = s == pWindow->m_wlSurface->resource(); + renderdata.mainSurface = s == pWindow->wlSurface()->resource(); m_renderPass.add(makeUnique(renderdata)); renderdata.surfaceCounter++; }, @@ -640,17 +691,17 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T } if (TRANSFORMERSPRESENT) { - CFramebuffer* last = g_pHyprOpenGL->m_renderData.currentFB; + SP last = m_renderData.currentFB; for (auto const& t : pWindow->m_transformers) { last = t->transform(last); } - g_pHyprOpenGL->bindBackOnMain(); - g_pHyprOpenGL->renderOffToMain(last); + bindBackOnMain(); + renderOffToMain(last); } } - g_pHyprOpenGL->m_renderData.clipBox = CBox(); + m_renderData.clipBox = CBox(); if (mode == RENDER_PASS_ALL || mode == RENDER_PASS_POPUP) { if (!pWindow->m_isX11) { @@ -677,20 +728,21 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T renderdata.surfaceCounter = 0; pWindow->m_popupHead->breadthfirst( - [this, &renderdata](WP popup, void* data) { + [this, &renderdata](WP popup, void* data) { if (popup->m_fadingOut) { renderSnapshot(popup); return; } - if (!popup->m_wlSurface || !popup->m_wlSurface->resource() || !popup->m_mapped) + if (!popup->aliveAndVisible()) return; + const auto pos = popup->coordsRelativeToParent(); const Vector2D oldPos = renderdata.pos; renderdata.pos += pos; renderdata.fadeAlpha = popup->m_alpha->value(); - popup->m_wlSurface->resource()->breadthfirst( + popup->wlSurface()->resource()->breadthfirst( [this, &renderdata](SP s, const Vector2D& offset, void* data) { if (!s->m_current.texture) return; @@ -724,12 +776,120 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T } } - EMIT_HOOK_EVENT("render", RENDER_POST_WINDOW); + Event::bus()->m_events.render.stage.emit(RENDER_POST_WINDOW); - g_pHyprOpenGL->m_renderData.currentWindow.reset(); + m_renderData.currentWindow.reset(); } -void CHyprRenderer::renderLayer(PHLLS pLayer, PHLMONITOR pMonitor, const Time::steady_tp& time, bool popups, bool lockscreen) { +void IHyprRenderer::draw(WP element, const CRegion& damage) { + ASSERT(element); + if (!element) + return; + + elementRenderer()->drawElement(element, damage); +} + +void IHyprRenderer::draw(const CBorderPassElement::SBorderData& data, const CRegion& damage) { + elementRenderer()->drawElement(makeUnique(data), damage); +} + +void IHyprRenderer::draw(const CClearPassElement::SClearData& data, const CRegion& damage) { + elementRenderer()->drawElement(makeUnique(data), damage); +} + +void IHyprRenderer::draw(const CFramebufferElement::SFramebufferElementData& data, const CRegion& damage) { + elementRenderer()->drawElement(makeUnique(data), damage); +} + +void IHyprRenderer::draw(const CRectPassElement::SRectData& data, const CRegion& damage) { + elementRenderer()->drawElement(makeUnique(data), damage); +} + +void IHyprRenderer::draw(const CRendererHintsPassElement::SData& data, const CRegion& damage) { + elementRenderer()->drawElement(makeUnique(data), damage); +} + +void IHyprRenderer::draw(const CShadowPassElement::SShadowData& data, const CRegion& damage) { + elementRenderer()->drawElement(makeUnique(data), damage); +} + +void IHyprRenderer::draw(const CSurfacePassElement::SRenderData& data, const CRegion& damage) { + elementRenderer()->drawElement(makeUnique(data), damage); +} + +void IHyprRenderer::draw(const CTexPassElement::SRenderData& data, const CRegion& damage) { + elementRenderer()->drawElement(makeUnique(data), damage); +} + +void IHyprRenderer::draw(const CTextureMatteElement::STextureMatteData& data, const CRegion& damage) { + elementRenderer()->drawElement(makeUnique(data), damage); +} + +void IHyprRenderer::bindFB(SP fb) { + fb->bind(); + m_renderData.currentFB = fb; +} + +UP IHyprRenderer::bindTempFB(SP fb) { + const auto oldFB = m_renderData.currentFB; + bindFB(fb); + return makeUnique([this, oldFB] { bindFB(oldFB); }); +} + +bool IHyprRenderer::preBlurQueued(PHLMONITORREF pMonitor) { + static auto PBLURNEWOPTIMIZE = CConfigValue("decoration:blur:new_optimizations"); + static auto PBLUR = CConfigValue("decoration:blur:enabled"); + + if (!pMonitor) + return false; + return m_renderData.pMonitor->m_blurFBDirty && *PBLURNEWOPTIMIZE && *PBLUR && m_renderData.pMonitor->m_blurFBShouldRender; +} + +void IHyprRenderer::pushMonitorTransformEnabled(bool enabled) { + m_monitorTransformStack.push(enabled); + m_monitorTransformEnabled = enabled; +} + +void IHyprRenderer::popMonitorTransformEnabled() { + m_monitorTransformStack.pop(); + m_monitorTransformEnabled = m_monitorTransformStack.top(); +} + +bool IHyprRenderer::monitorTransformEnabled() { + return m_monitorTransformEnabled; +} + +SP IHyprRenderer::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 IHyprRenderer::renderLayer(PHLLS pLayer, PHLMONITOR pMonitor, const Time::steady_tp& time, bool popups, bool lockscreen) { if (!pLayer) return; @@ -742,7 +902,7 @@ void CHyprRenderer::renderLayer(PHLLS pLayer, PHLMONITOR pMonitor, const Time::s if (*PDIMAROUND && pLayer->m_ruleApplicator->dimAround().valueOrDefault() && !m_bRenderingSnapshot && !popups) { CRectPassElement::SRectData data; - data.box = {0, 0, g_pHyprOpenGL->m_renderData.pMonitor->m_transformedSize.x, g_pHyprOpenGL->m_renderData.pMonitor->m_transformedSize.y}; + data.box = {0, 0, pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y}; data.color = CHyprColor(0, 0, 0, *PDIMAROUND * pLayer->m_alpha->value()); m_renderPass.add(makeUnique(data)); } @@ -761,7 +921,7 @@ void CHyprRenderer::renderLayer(PHLLS pLayer, PHLMONITOR pMonitor, const Time::s CSurfacePassElement::SRenderData renderdata = {pMonitor, time, REALPOS}; renderdata.fadeAlpha = pLayer->m_alpha->value(); renderdata.blur = shouldBlur(pLayer); - renderdata.surface = pLayer->m_surface->resource(); + renderdata.surface = pLayer->wlSurface()->resource(); renderdata.decorate = false; renderdata.w = REALSIZ.x; renderdata.h = REALSIZ.y; @@ -769,14 +929,13 @@ void CHyprRenderer::renderLayer(PHLLS pLayer, PHLMONITOR pMonitor, const Time::s renderdata.blockBlurOptimization = pLayer->m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM || pLayer->m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; renderdata.clipBox = CBox{0, 0, pMonitor->m_size.x, pMonitor->m_size.y}.scale(pMonitor->m_scale); - if (renderdata.blur && pLayer->m_ruleApplicator->ignoreAlpha().hasValue()) { renderdata.discardMode |= DISCARD_ALPHA; renderdata.discardOpacity = pLayer->m_ruleApplicator->ignoreAlpha().valueOrDefault(); } if (!popups) - pLayer->m_surface->resource()->breadthfirst( + pLayer->wlSurface()->resource()->breadthfirst( [this, &renderdata, &pLayer](SP s, const Vector2D& offset, void* data) { if (!s->m_current.texture) return; @@ -787,7 +946,7 @@ void CHyprRenderer::renderLayer(PHLLS pLayer, PHLMONITOR pMonitor, const Time::s renderdata.localPos = offset; renderdata.texture = s->m_current.texture; renderdata.surface = s; - renderdata.mainSurface = s == pLayer->m_surface->resource(); + renderdata.mainSurface = s == pLayer->wlSurface()->resource(); m_renderPass.add(makeUnique(renderdata)); renderdata.surfaceCounter++; }, @@ -797,14 +956,20 @@ void CHyprRenderer::renderLayer(PHLLS pLayer, PHLMONITOR pMonitor, const Time::s renderdata.dontRound = true; renderdata.popup = true; renderdata.blur = pLayer->m_ruleApplicator->blurPopups().valueOrDefault(); - renderdata.surfaceCounter = 0; + renderdata.discardMode &= ~DISCARD_ALPHA; + renderdata.discardOpacity = 0.F; + if (renderdata.blur && pLayer->m_ruleApplicator->ignoreAlpha().hasValue()) { + renderdata.discardMode |= DISCARD_ALPHA; + renderdata.discardOpacity = pLayer->m_ruleApplicator->ignoreAlpha().valueOrDefault(); + } + renderdata.surfaceCounter = 0; if (popups) { pLayer->m_popupHead->breadthfirst( - [this, &renderdata](WP popup, void* data) { - if (!popup->m_wlSurface || !popup->m_wlSurface->resource() || !popup->m_mapped) + [this, &renderdata](WP popup, void* data) { + if (!popup->aliveAndVisible()) return; - const auto SURF = popup->m_wlSurface->resource(); + const auto SURF = popup->wlSurface()->resource(); if (!SURF->m_current.texture) return; @@ -824,7 +989,7 @@ void CHyprRenderer::renderLayer(PHLLS pLayer, PHLMONITOR pMonitor, const Time::s } } -void CHyprRenderer::renderIMEPopup(CInputPopup* pPopup, PHLMONITOR pMonitor, const Time::steady_tp& time) { +void IHyprRenderer::renderIMEPopup(CInputPopup* pPopup, PHLMONITOR pMonitor, const Time::steady_tp& time) { const auto POS = pPopup->globalBox().pos(); CSurfacePassElement::SRenderData renderdata = {pMonitor, time, POS}; @@ -864,7 +1029,7 @@ void CHyprRenderer::renderIMEPopup(CInputPopup* pPopup, PHLMONITOR pMonitor, con &renderdata); } -void CHyprRenderer::renderSessionLockSurface(WP pSurface, PHLMONITOR pMonitor, const Time::steady_tp& time) { +void IHyprRenderer::renderSessionLockSurface(WP pSurface, PHLMONITOR pMonitor, const Time::steady_tp& time) { CSurfacePassElement::SRenderData renderdata = {pMonitor, time, pMonitor->m_position, pMonitor->m_position}; renderdata.blur = false; @@ -891,35 +1056,31 @@ void CHyprRenderer::renderSessionLockSurface(WP pSurface, P &renderdata); } -void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& time, const Vector2D& translate, const float& scale) { +void IHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& time, const Vector2D& translate, const float& scale) { static auto PDIMSPECIAL = CConfigValue("decoration:dim_special"); static auto PBLURSPECIAL = CConfigValue("decoration:blur:special"); static auto PBLUR = CConfigValue("decoration:blur:enabled"); static auto PXPMODE = CConfigValue("render:xp_mode"); static auto PSESSIONLOCKXRAY = CConfigValue("misc:session_lock_xray"); - if (!pMonitor) + if UNLIKELY (!pMonitor) return; - if (g_pSessionLockManager->isSessionLocked() && !*PSESSIONLOCKXRAY) { + if UNLIKELY (g_pSessionLockManager->isSessionLocked() && !*PSESSIONLOCKXRAY) { // We stop to render workspaces as soon as the lockscreen was sent the "locked" or "finished" (aka denied) event. // In addition we make sure to stop rendering workspaces after misc:lockdead_screen_delay has passed. if (g_pSessionLockManager->shallConsiderLockMissing() || g_pSessionLockManager->clientLocked() || g_pSessionLockManager->clientDenied()) return; } - // todo: matrices are buggy atm for some reason, but probably would be preferable in the long run - // g_pHyprOpenGL->saveMatrix(); - // g_pHyprOpenGL->setMatrixScaleTranslate(translate, scale); - SRenderModifData RENDERMODIFDATA; if (translate != Vector2D{0, 0}) RENDERMODIFDATA.modifs.emplace_back(std::make_pair<>(SRenderModifData::eRenderModifType::RMOD_TYPE_TRANSLATE, translate)); - if (scale != 1.f) + if UNLIKELY (scale != 1.f) RENDERMODIFDATA.modifs.emplace_back(std::make_pair<>(SRenderModifData::eRenderModifType::RMOD_TYPE_SCALE, scale)); - if (!RENDERMODIFDATA.modifs.empty()) - g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{RENDERMODIFDATA})); + if UNLIKELY (!RENDERMODIFDATA.modifs.empty()) + m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{RENDERMODIFDATA})); CScopeGuard x([&RENDERMODIFDATA] { if (!RENDERMODIFDATA.modifs.empty()) { @@ -927,7 +1088,7 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA } }); - if (!pWorkspace) { + if UNLIKELY (!pWorkspace) { // allow rendering without a workspace. In this case, just render layers. renderBackground(pMonitor); @@ -936,7 +1097,7 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA renderLayer(ls.lock(), pMonitor, time); } - EMIT_HOOK_EVENT("render", RENDER_POST_WALLPAPER); + Event::bus()->m_events.render.stage.emit(RENDER_POST_WALLPAPER); for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]) { renderLayer(ls.lock(), pMonitor, time); @@ -953,14 +1114,14 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA return; } - if (!*PXPMODE) { + if LIKELY (!*PXPMODE) { renderBackground(pMonitor); for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]) { renderLayer(ls.lock(), pMonitor, time); } - EMIT_HOOK_EVENT("render", RENDER_POST_WALLPAPER); + Event::bus()->m_events.render.stage.emit(RENDER_POST_WALLPAPER); for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]) { renderLayer(ls.lock(), pMonitor, time); @@ -968,15 +1129,16 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA } // pre window pass - g_pHyprOpenGL->preWindowPass(); + if (preBlurQueued(pMonitor)) + m_renderPass.add(makeUnique()); - if (pWorkspace->m_hasFullscreenWindow) + if UNLIKELY /* subjective? */ (pWorkspace->m_hasFullscreenWindow) renderWorkspaceWindowsFullscreen(pMonitor, pWorkspace, time); else renderWorkspaceWindows(pMonitor, pWorkspace, time); // and then special - if (pMonitor->m_specialFade->value() != 0.F) { + if UNLIKELY (pMonitor->m_specialFade->value() != 0.F) { const auto SPECIALANIMPROGRS = pMonitor->m_specialFade->getCurveValue(); const bool ANIMOUT = !pMonitor->m_activeSpecialWorkspace; @@ -985,7 +1147,7 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA data.box = {translate.x, translate.y, pMonitor->m_transformedSize.x * scale, pMonitor->m_transformedSize.y * scale}; data.color = CHyprColor(0, 0, 0, *PDIMSPECIAL * (ANIMOUT ? (1.0 - SPECIALANIMPROGRS) : SPECIALANIMPROGRS)); - g_pHyprRenderer->m_renderPass.add(makeUnique(data)); + m_renderPass.add(makeUnique(data)); } if (*PBLURSPECIAL && *PBLUR) { @@ -995,7 +1157,7 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA data.blur = true; data.blurA = (ANIMOUT ? (1.0 - SPECIALANIMPROGRS) : SPECIALANIMPROGRS); - g_pHyprRenderer->m_renderPass.add(makeUnique(data)); + m_renderPass.add(makeUnique(data)); } } @@ -1025,7 +1187,7 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA renderWindow(w, pMonitor, time, true, RENDER_PASS_ALL); } - EMIT_HOOK_EVENT("render", RENDER_POST_WINDOWS); + Event::bus()->m_events.render.stage.emit(RENDER_POST_WINDOWS); // Render surfaces above windows for monitor for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { @@ -1048,27 +1210,359 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA } renderDragIcon(pMonitor, time); - - //g_pHyprOpenGL->restoreMatrix(); } -void CHyprRenderer::renderBackground(PHLMONITOR pMonitor) { +SP IHyprRenderer::getBackground(PHLMONITOR pMonitor) { + + if (m_backgroundResourceFailed) + return nullptr; + + if (!m_backgroundResource) { + // queue the asset to be created + requestBackgroundResource(); + return nullptr; + } + + if (!m_backgroundResource->m_ready) + return nullptr; + + Log::logger->log(Log::DEBUG, "Creating a texture for BGTex"); + SP backgroundTexture = createTexture(m_backgroundResource->m_asset.cairoSurface->cairo()); + if (!backgroundTexture->ok()) + return nullptr; + Log::logger->log(Log::DEBUG, "Background created for monitor {}", pMonitor->m_name); + + // clear the resource after we're done using it + g_pEventLoopManager->doLater([this] { m_backgroundResource.reset(); }); + + // set the animation to start for fading this background in nicely + pMonitor->m_backgroundOpacity->setValueAndWarp(0.F); + *pMonitor->m_backgroundOpacity = 1.F; + + return backgroundTexture; +} + +void IHyprRenderer::renderBackground(PHLMONITOR pMonitor) { static auto PRENDERTEX = CConfigValue("misc:disable_hyprland_logo"); static auto PBACKGROUNDCOLOR = CConfigValue("misc:background_color"); + static auto PNOSPLASH = CConfigValue("misc:disable_splash_rendering"); if (*PRENDERTEX /* inverted cfg flag */ || pMonitor->m_backgroundOpacity->isBeingAnimated()) m_renderPass.add(makeUnique(CClearPassElement::SClearData{CHyprColor(*PBACKGROUNDCOLOR)})); - if (!*PRENDERTEX) - g_pHyprOpenGL->clearWithTex(); // will apply the hypr "wallpaper" + if (!*PRENDERTEX) { + static auto PBACKGROUNDCOLOR = CConfigValue("misc:background_color"); + + if (!pMonitor->m_background) + pMonitor->m_background = getBackground(pMonitor); + + if (!pMonitor->m_background) + m_renderPass.add(makeUnique(CClearPassElement::SClearData{CHyprColor(*PBACKGROUNDCOLOR)})); + else { + CTexPassElement::SRenderData data; + const double MONRATIO = m_renderData.pMonitor->m_transformedSize.x / m_renderData.pMonitor->m_transformedSize.y; + const double WPRATIO = pMonitor->m_background->m_size.x / pMonitor->m_background->m_size.y; + Vector2D origin; + double scale = 1.0; + + if (MONRATIO > WPRATIO) { + scale = m_renderData.pMonitor->m_transformedSize.x / pMonitor->m_background->m_size.x; + origin.y = (m_renderData.pMonitor->m_transformedSize.y - pMonitor->m_background->m_size.y * scale) / 2.0; + } else { + scale = m_renderData.pMonitor->m_transformedSize.y / pMonitor->m_background->m_size.y; + origin.x = (m_renderData.pMonitor->m_transformedSize.x - pMonitor->m_background->m_size.x * scale) / 2.0; + } + + if (MONRATIO != WPRATIO) + m_renderPass.add(makeUnique(CClearPassElement::SClearData{CHyprColor(*PBACKGROUNDCOLOR)})); + + data.box = {origin, pMonitor->m_background->m_size * scale}; + data.a = m_renderData.pMonitor->m_backgroundOpacity->value(); + data.tex = pMonitor->m_background; + m_renderPass.add(makeUnique(std::move(data))); + } + } + + if (!*PNOSPLASH) { + auto monitorSize = pMonitor->m_transformedSize; + if (!pMonitor->m_splash) + pMonitor->m_splash = renderSplash([this, pMonitor](auto width, auto height, const auto DATA) { return createTexture(width, height, DATA); }, monitorSize.y / 76, + monitorSize.x, monitorSize.y); + + if (pMonitor->m_splash) { + CTexPassElement::SRenderData data; + data.box = {{(monitorSize.x - pMonitor->m_splash->m_size.x) / 2.0, monitorSize.y * 0.98 - pMonitor->m_splash->m_size.y}, pMonitor->m_splash->m_size}; + data.tex = pMonitor->m_splash; + m_renderPass.add(makeUnique(std::move(data))); + } + } } -void CHyprRenderer::renderLockscreen(PHLMONITOR pMonitor, const Time::steady_tp& now, const CBox& geometry) { +void IHyprRenderer::requestBackgroundResource() { + if (m_backgroundResource) + return; + + static auto PNOWALLPAPER = CConfigValue("misc:disable_hyprland_logo"); + static auto PFORCEWALLPAPER = CConfigValue("misc:force_default_wallpaper"); + + const auto FORCEWALLPAPER = std::clamp(*PFORCEWALLPAPER, sc(-1), sc(2)); + + if (*PNOWALLPAPER) + return; + + static bool once = true; + static std::string texPath = "wall"; + + if (once) { + // get the adequate tex + if (FORCEWALLPAPER == -1) { + std::mt19937_64 engine(time(nullptr)); + std::uniform_int_distribution<> distribution(0, 2); + + texPath += std::to_string(distribution(engine)); + } else + texPath += std::to_string(std::clamp(*PFORCEWALLPAPER, sc(0), sc(2))); + + texPath += ".png"; + + texPath = resolveAssetPath(texPath); + + once = false; + } + + if (texPath.empty()) { + m_backgroundResourceFailed = true; + return; + } + + m_backgroundResource = makeAtomicShared(texPath); + + // doesn't have to be ASP as it's passed + SP executor = makeShared([this] { + for (const auto& m : g_pCompositor->m_monitors) { + damageMonitor(m); + } + }); + + m_backgroundResource->m_events.finished.listenStatic([executor] { + // this is in the worker thread. + executor->signal(); + }); + + g_pAsyncResourceGatherer->enqueue(m_backgroundResource); +} + +std::string IHyprRenderer::resolveAssetPath(const std::string& filename) { + std::string fullPath; + for (auto& e : ASSET_PATHS) { + std::string p = std::string{e} + "/hypr/" + filename; + std::error_code ec; + if (std::filesystem::exists(p, ec)) { + fullPath = p; + break; + } else + Log::logger->log(Log::DEBUG, "resolveAssetPath: looking at {} unsuccessful: ec {}", filename, ec.message()); + } + + if (fullPath.empty()) { + m_failedAssetsNo++; + Log::logger->log(Log::ERR, "resolveAssetPath: looking for {} failed (no provider found)", filename); + return ""; + } + + return fullPath; +} + +SP IHyprRenderer::loadAsset(const std::string& filename) { + + const std::string fullPath = resolveAssetPath(filename); + + if (fullPath.empty()) + return m_missingAssetTexture; + + const auto CAIROSURFACE = cairo_image_surface_create_from_png(fullPath.c_str()); + + if (!CAIROSURFACE) { + m_failedAssetsNo++; + Log::logger->log(Log::ERR, "loadAsset: failed to load {} (corrupt / inaccessible / not png)", fullPath); + return m_missingAssetTexture; + } + + auto tex = createTexture(CAIROSURFACE); + + cairo_surface_destroy(CAIROSURFACE); + + return tex; +} + +SP IHyprRenderer::getBlurTexture(PHLMONITORREF pMonitor) { + return pMonitor->resources()->m_blurFB->getTexture(); +} + +bool IHyprRenderer::shouldUseNewBlurOptimizations(PHLLS pLayer, PHLWINDOW pWindow) { + static auto PBLURNEWOPTIMIZE = CConfigValue("decoration:blur:new_optimizations"); + static auto PBLURXRAY = CConfigValue("decoration:blur:xray"); + + if (!getBlurTexture(m_renderData.pMonitor)) + return false; + + if (pWindow && pWindow->m_ruleApplicator->xray().hasValue() && !pWindow->m_ruleApplicator->xray().valueOrDefault()) + return false; + + if (pLayer && pLayer->m_ruleApplicator->xray().valueOrDefault() == 0) + return false; + + if ((*PBLURNEWOPTIMIZE && pWindow && !pWindow->m_isFloating && !pWindow->onSpecialWorkspace()) || *PBLURXRAY) + return true; + + if ((pLayer && pLayer->m_ruleApplicator->xray().valueOrDefault() == 1) || (pWindow && pWindow->m_ruleApplicator->xray().valueOrDefault())) + return true; + + return false; +} + +void IHyprRenderer::initMissingAssetTexture() { + + const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 512, 512); + const auto CAIRO = cairo_create(CAIROSURFACE); + + cairo_set_antialias(CAIRO, CAIRO_ANTIALIAS_NONE); + cairo_save(CAIRO); + cairo_set_source_rgba(CAIRO, 0, 0, 0, 1); + cairo_set_operator(CAIRO, CAIRO_OPERATOR_SOURCE); + cairo_paint(CAIRO); + cairo_set_source_rgba(CAIRO, 1, 0, 1, 1); + cairo_rectangle(CAIRO, 256, 0, 256, 256); + cairo_fill(CAIRO); + cairo_rectangle(CAIRO, 0, 256, 256, 256); + cairo_fill(CAIRO); + cairo_restore(CAIRO); + + cairo_surface_flush(CAIROSURFACE); + + auto tex = createTexture(CAIROSURFACE); + + cairo_surface_destroy(CAIROSURFACE); + cairo_destroy(CAIRO); + + m_missingAssetTexture = tex; +} + +void IHyprRenderer::initAssets() { + initMissingAssetTexture(); + + m_screencopyDeniedTexture = renderText("Permission denied to share screen", Colors::WHITE, 20); +} + +SP IHyprRenderer::renderText(const std::string& text, CHyprColor col, int pt, bool italic, const std::string& fontFamily, int maxWidth, int weight) { + static auto FONT = CConfigValue("misc:font_family"); + + const auto FONTFAMILY = fontFamily.empty() ? *FONT : fontFamily; + const auto FONTSIZE = pt; + const auto COLOR = col; + + auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1920, 1080 /* arbitrary, just for size */); + auto CAIRO = cairo_create(CAIROSURFACE); + + PangoLayout* layoutText = pango_cairo_create_layout(CAIRO); + PangoFontDescription* pangoFD = pango_font_description_new(); + + pango_font_description_set_family_static(pangoFD, FONTFAMILY.c_str()); + pango_font_description_set_absolute_size(pangoFD, FONTSIZE * PANGO_SCALE); + pango_font_description_set_style(pangoFD, italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL); + pango_font_description_set_weight(pangoFD, sc(weight)); + pango_layout_set_font_description(layoutText, pangoFD); + + cairo_set_source_rgba(CAIRO, COLOR.r, COLOR.g, COLOR.b, COLOR.a); + + int textW = 0, textH = 0; + pango_layout_set_text(layoutText, text.c_str(), -1); + + if (maxWidth > 0) { + pango_layout_set_width(layoutText, maxWidth * PANGO_SCALE); + pango_layout_set_ellipsize(layoutText, PANGO_ELLIPSIZE_END); + } + + pango_layout_get_size(layoutText, &textW, &textH); + textW /= PANGO_SCALE; + textH /= PANGO_SCALE; + + pango_font_description_free(pangoFD); + g_object_unref(layoutText); + cairo_destroy(CAIRO); + cairo_surface_destroy(CAIROSURFACE); + + CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, textW, textH); + CAIRO = cairo_create(CAIROSURFACE); + + layoutText = pango_cairo_create_layout(CAIRO); + pangoFD = pango_font_description_new(); + + pango_font_description_set_family_static(pangoFD, FONTFAMILY.c_str()); + pango_font_description_set_absolute_size(pangoFD, FONTSIZE * PANGO_SCALE); + pango_font_description_set_style(pangoFD, italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL); + pango_font_description_set_weight(pangoFD, sc(weight)); + pango_layout_set_font_description(layoutText, pangoFD); + pango_layout_set_text(layoutText, text.c_str(), -1); + + cairo_set_source_rgba(CAIRO, COLOR.r, COLOR.g, COLOR.b, COLOR.a); + + cairo_move_to(CAIRO, 0, 0); + pango_cairo_show_layout(CAIRO, layoutText); + + pango_font_description_free(pangoFD); + g_object_unref(layoutText); + + cairo_surface_flush(CAIROSURFACE); + + auto tex = createTexture(CAIROSURFACE); + + cairo_destroy(CAIRO); + cairo_surface_destroy(CAIROSURFACE); + + return tex; +} + +SP IHyprRenderer::renderText(Hyprgraphics::CTextResource::STextResourceData&& data) { + auto res = makeAtomicShared(std::move(data)); + g_pAsyncResourceGatherer->enqueue(res); + g_pAsyncResourceGatherer->await(res); + + if (!res->m_asset.cairoSurface) + return nullptr; + + return createTexture(res->m_asset.pixelSize.x, res->m_asset.pixelSize.y, res->m_asset.cairoSurface->data()); +} + +void IHyprRenderer::ensureLockTexturesRendered(bool load) { + static bool loaded = false; + + if (loaded == load) + return; + + loaded = load; + + if (load) { + // this will cause a small hitch. I don't think we can do much, other than wasting VRAM and having this loaded all the time. + m_lockDeadTexture = loadAsset("lockdead.png"); + m_lockDead2Texture = loadAsset("lockdead2.png"); + + const auto VT = g_pCompositor->getVTNr(); + + m_lockTtyTextTexture = renderText(std::format("Running on tty {}", VT.has_value() ? std::to_string(*VT) : "unknown"), CHyprColor{0.9F, 0.9F, 0.9F, 0.7F}, 20, true); + } else { + m_lockDeadTexture.reset(); + m_lockDead2Texture.reset(); + m_lockTtyTextTexture.reset(); + } +} + +void IHyprRenderer::renderLockscreen(PHLMONITOR pMonitor, const Time::steady_tp& now, const CBox& geometry) { TRACY_GPU_ZONE("RenderLockscreen"); const bool LOCKED = g_pSessionLockManager->isSessionLocked(); if (!LOCKED) { - g_pHyprOpenGL->ensureLockTexturesRendered(false); + ensureLockTexturesRendered(false); return; } @@ -1079,7 +1573,7 @@ void CHyprRenderer::renderLockscreen(PHLMONITOR pMonitor, const Time::steady_tp& const auto PSLS = g_pSessionLockManager->getSessionLockSurfaceForMonitor(pMonitor->m_id); const bool RENDERLOCKMISSING = (PSLS.expired() || g_pSessionLockManager->clientDenied()) && g_pSessionLockManager->shallConsiderLockMissing(); - g_pHyprOpenGL->ensureLockTexturesRendered(RENDERLOCKMISSING); + ensureLockTexturesRendered(RENDERLOCKMISSING); if (RENDERLOCKMISSING) renderSessionLockMissing(pMonitor); @@ -1101,7 +1595,7 @@ void CHyprRenderer::renderLockscreen(PHLMONITOR pMonitor, const Time::steady_tp& } } -void CHyprRenderer::renderSessionLockPrimer(PHLMONITOR pMonitor) { +void IHyprRenderer::renderSessionLockPrimer(PHLMONITOR pMonitor) { static auto PSESSIONLOCKXRAY = CConfigValue("misc:session_lock_xray"); if (*PSESSIONLOCKXRAY) return; @@ -1110,153 +1604,302 @@ void CHyprRenderer::renderSessionLockPrimer(PHLMONITOR pMonitor) { data.color = CHyprColor(0, 0, 0, 1.f); data.box = CBox{{}, pMonitor->m_pixelSize}; - m_renderPass.add(makeUnique(std::move(data))); + m_renderPass.add(makeUnique(data)); } -void CHyprRenderer::renderSessionLockMissing(PHLMONITOR pMonitor) { +void IHyprRenderer::renderSessionLockMissing(PHLMONITOR pMonitor) { const bool ANY_PRESENT = g_pSessionLockManager->anySessionLockSurfacesPresent(); // ANY_PRESENT: render image2, without instructions. Lock still "alive", unless texture dead // else: render image, with instructions. Lock is gone. CBox monbox = {{}, pMonitor->m_pixelSize}; CTexPassElement::SRenderData data; - data.tex = (ANY_PRESENT) ? g_pHyprOpenGL->m_lockDead2Texture : g_pHyprOpenGL->m_lockDeadTexture; + data.tex = (ANY_PRESENT) ? m_lockDead2Texture : m_lockDeadTexture; data.box = monbox; data.a = 1; m_renderPass.add(makeUnique(data)); - if (!ANY_PRESENT && g_pHyprOpenGL->m_lockTtyTextTexture) { + if (!ANY_PRESENT && m_lockTtyTextTexture) { // also render text for the tty number - CBox texbox = {{}, g_pHyprOpenGL->m_lockTtyTextTexture->m_size}; - data.tex = g_pHyprOpenGL->m_lockTtyTextTexture; + CBox texbox = {{}, m_lockTtyTextTexture->m_size}; + data.tex = m_lockTtyTextTexture; data.box = texbox; m_renderPass.add(makeUnique(std::move(data))); } } -static std::optional getSurfaceExpectedSize(PHLWINDOW pWindow, SP pSurface, PHLMONITOR pMonitor, bool main) { - const auto CAN_USE_WINDOW = pWindow && main; - const auto WINDOW_SIZE_MISALIGN = CAN_USE_WINDOW && pWindow->getReportedSize() != pWindow->m_wlSurface->resource()->m_current.size; +bool IHyprRenderer::beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMode mode, SP buffer, SP fb, bool simple) { + m_renderPass.clear(); + clearCMSettingsCache(); + m_renderMode = mode; + m_renderData.pMonitor = pMonitor; - if (pSurface->m_current.viewport.hasDestination) - return (pSurface->m_current.viewport.destination * pMonitor->m_scale).round(); + if (simple) + setProjectionType(fb ? fb->m_size : buffer->m_texture->m_size); + else + setProjectionType(RPT_MONITOR); - if (pSurface->m_current.viewport.hasSource) - return (pSurface->m_current.viewport.source.size() * pMonitor->m_scale).round(); + const bool HAS_MIRROR_FB = g_pHyprRenderer->m_renderData.pMonitor->resources()->hasMirrorFB(); + const bool NEEDS_COPY_FB = g_pHyprRenderer->m_renderData.pMonitor->needsACopyFB(); - if (WINDOW_SIZE_MISALIGN) - return (pSurface->m_current.size * pMonitor->m_scale).round(); + if (HAS_MIRROR_FB && !NEEDS_COPY_FB) + g_pHyprRenderer->m_renderData.pMonitor->resources()->mirrorFB()->release(); - if (CAN_USE_WINDOW) - return (pWindow->getReportedSize() * pMonitor->m_scale).round(); + if (m_renderMode == RENDER_MODE_FULL_FAKE) + return beginFullFakeRenderInternal(pMonitor, damage, fb, simple); - return std::nullopt; + int bufferAge = 0; + + if (!buffer) { + m_currentBuffer = pMonitor->m_output->swapchain->next(&bufferAge); + if (!m_currentBuffer) { + Log::logger->log(Log::ERR, "Failed to acquire swapchain buffer for {}", pMonitor->m_name); + return false; + } + } else + m_currentBuffer = buffer; + + initRender(); + + if (!initRenderBuffer(m_currentBuffer, pMonitor->m_output->state->state().drmFormat)) { + Log::logger->log(Log::ERR, "failed to start a render pass for output {}, no RBO could be obtained", pMonitor->m_name); + return false; + } + + if (m_renderMode == RENDER_MODE_NORMAL) { + damage = pMonitor->m_damage.getBufferDamage(bufferAge); + pMonitor->m_damage.rotate(); + } + + const auto res = beginRenderInternal(pMonitor, damage, simple); + static bool initial = true; + if (initial) { + initAssets(); + initial = false; + } + + return res; } -void CHyprRenderer::calculateUVForSurface(PHLWINDOW pWindow, SP pSurface, PHLMONITOR pMonitor, bool main, const Vector2D& projSize, - const Vector2D& projSizeUnscaled, bool fixMisalignedFSV1) { - if (!pWindow || !pWindow->m_isX11) { - static auto PEXPANDEDGES = CConfigValue("render:expand_undersized_textures"); +void IHyprRenderer::setDamage(const CRegion& damage_, std::optional finalDamage) { + m_renderData.damage.set(damage_); + m_renderData.finalDamage.set(finalDamage.value_or(damage_)); +} - Vector2D uvTL; - Vector2D uvBR = Vector2D(1, 1); +static Mat3x3 getMirrorProjection(PHLMONITORREF monitor) { + return Mat3x3::identity() + .translate(monitor->m_pixelSize / 2.0) + .transform(Math::wlTransformToHyprutils(monitor->m_transform)) + .transform(Math::wlTransformToHyprutils(Math::invertTransform(monitor->m_mirrorOf->m_transform))) + .translate(-monitor->m_transformedSize / 2.0); +} - if (pSurface->m_current.viewport.hasSource) { - // we stretch it to dest. if no dest, to 1,1 - Vector2D const& bufferSize = pSurface->m_current.bufferSize; - auto const& bufferSource = pSurface->m_current.viewport.source; +static Mat3x3 getFBProjection(PHLMONITORREF pMonitor, const Vector2D& size) { + if (pMonitor->m_transform == WL_OUTPUT_TRANSFORM_NORMAL) + return Mat3x3::identity(); - // calculate UV for the basic src_box. Assume dest == size. Scale to dest later - uvTL = Vector2D(bufferSource.x / bufferSize.x, bufferSource.y / bufferSize.y); - uvBR = Vector2D((bufferSource.x + bufferSource.width) / bufferSize.x, (bufferSource.y + bufferSource.height) / bufferSize.y); + const Vector2D tfmd = pMonitor->m_transform % 2 == 1 ? Vector2D{size.y, size.x} : size; + return Mat3x3::identity().translate(size / 2.0).transform(Math::wlTransformToHyprutils(pMonitor->m_transform)).translate(-tfmd / 2.0); +} - if (uvBR.x < 0.01f || uvBR.y < 0.01f) { - uvTL = Vector2D(); - uvBR = Vector2D(1, 1); - } - } +void IHyprRenderer::setProjectionType(const Vector2D& fbSize) { + m_renderData.fbSize = fbSize; + setProjectionType(RPT_FB); +} - if (projSize != Vector2D{} && fixMisalignedFSV1) { - // instead of nearest_neighbor (we will repeat / skip) - // just cut off / expand surface - const Vector2D PIXELASUV = Vector2D{1, 1} / pSurface->m_current.bufferSize; - const auto& BUFFER_SIZE = pSurface->m_current.bufferSize; - - // compute MISALIGN from the adjusted UV coordinates. - const Vector2D MISALIGNMENT = (uvBR - uvTL) * BUFFER_SIZE - projSize; - - if (MISALIGNMENT != Vector2D{}) - uvBR -= MISALIGNMENT * PIXELASUV; - } else { - // if the surface is smaller than our viewport, extend its edges. - // this will break if later on xdg geometry is hit, but we really try - // to let the apps know to NOT add CSD. Also if source is there. - // there is no way to fix this if that's the case - const auto MONITOR_WL_SCALE = std::ceil(pMonitor->m_scale); - const bool SCALE_UNAWARE = pMonitor->m_scale != 1.f && (MONITOR_WL_SCALE == pSurface->m_current.scale || !pSurface->m_current.viewport.hasDestination); - const auto EXPECTED_SIZE = getSurfaceExpectedSize(pWindow, pSurface, pMonitor, main).value_or((projSize * pMonitor->m_scale).round()); - - const auto RATIO = projSize / EXPECTED_SIZE; - if (!SCALE_UNAWARE || MONITOR_WL_SCALE == 1) { - if (*PEXPANDEDGES && !SCALE_UNAWARE && (RATIO.x > 1 || RATIO.y > 1)) { - const auto FIX = RATIO.clamp(Vector2D{1, 1}, Vector2D{1000000, 1000000}); - uvBR = uvBR * FIX; - } - - // FIXME: probably do this for in anims on all views... - const auto SHOULD_SKIP = !pWindow || pWindow->m_animatingIn; - if (!SHOULD_SKIP && (RATIO.x < 1 || RATIO.y < 1)) { - const auto FIX = RATIO.clamp(Vector2D{0.0001, 0.0001}, Vector2D{1, 1}); - uvBR = uvBR * FIX; - } - } - } - - g_pHyprOpenGL->m_renderData.primarySurfaceUVTopLeft = uvTL; - g_pHyprOpenGL->m_renderData.primarySurfaceUVBottomRight = uvBR; - - if (g_pHyprOpenGL->m_renderData.primarySurfaceUVTopLeft == Vector2D() && g_pHyprOpenGL->m_renderData.primarySurfaceUVBottomRight == Vector2D(1, 1)) { - // No special UV mods needed - g_pHyprOpenGL->m_renderData.primarySurfaceUVTopLeft = Vector2D(-1, -1); - g_pHyprOpenGL->m_renderData.primarySurfaceUVBottomRight = Vector2D(-1, -1); - } - - if (!main || !pWindow) - return; - - // FIXME: this doesn't work. We always set MAXIMIZED anyways, so this doesn't need to work, but it's problematic. - - // CBox geom = pWindow->m_xdgSurface->m_current.geometry; - - // // Adjust UV based on the xdg_surface geometry - // if (geom.x != 0 || geom.y != 0 || geom.w != 0 || geom.h != 0) { - // const auto XPERC = geom.x / pSurface->m_current.size.x; - // const auto YPERC = geom.y / pSurface->m_current.size.y; - // const auto WPERC = (geom.x + geom.w ? geom.w : pSurface->m_current.size.x) / pSurface->m_current.size.x; - // const auto HPERC = (geom.y + geom.h ? geom.h : pSurface->m_current.size.y) / pSurface->m_current.size.y; - - // const auto TOADDTL = Vector2D(XPERC * (uvBR.x - uvTL.x), YPERC * (uvBR.y - uvTL.y)); - // uvBR = uvBR - Vector2D((1.0 - WPERC) * (uvBR.x - uvTL.x), (1.0 - HPERC) * (uvBR.y - uvTL.y)); - // uvTL = uvTL + TOADDTL; - // } - - g_pHyprOpenGL->m_renderData.primarySurfaceUVTopLeft = uvTL; - g_pHyprOpenGL->m_renderData.primarySurfaceUVBottomRight = uvBR; - - if (g_pHyprOpenGL->m_renderData.primarySurfaceUVTopLeft == Vector2D() && g_pHyprOpenGL->m_renderData.primarySurfaceUVBottomRight == Vector2D(1, 1)) { - // No special UV mods needed - g_pHyprOpenGL->m_renderData.primarySurfaceUVTopLeft = Vector2D(-1, -1); - g_pHyprOpenGL->m_renderData.primarySurfaceUVBottomRight = Vector2D(-1, -1); - } - } else { - g_pHyprOpenGL->m_renderData.primarySurfaceUVTopLeft = Vector2D(-1, -1); - g_pHyprOpenGL->m_renderData.primarySurfaceUVBottomRight = Vector2D(-1, -1); +void IHyprRenderer::setProjectionType(eRenderProjectionType projectionType) { + m_renderData.projectionType = projectionType; + switch (projectionType) { + case RPT_MONITOR: m_renderData.targetProjection = m_renderData.pMonitor->getTransformMatrix(); break; + case RPT_MIRROR: m_renderData.targetProjection = getMirrorProjection(m_renderData.pMonitor); break; + case RPT_FB: m_renderData.targetProjection = getFBProjection(m_renderData.pMonitor, m_renderData.fbSize); break; + case RPT_EXPORT: m_renderData.targetProjection = Mat3x3::identity(); break; + default: UNREACHABLE(); } } -void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { +Mat3x3 IHyprRenderer::getBoxProjection(const CBox& box, std::optional transform) { + return m_renderData.targetProjection.projectBox( + box, transform.value_or(Math::wlTransformToHyprutils(Math::invertTransform(!monitorTransformEnabled() ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform))), + box.rot); +} + +Mat3x3 IHyprRenderer::projectBoxToTarget(const CBox& box, std::optional transform) { + return (m_renderData.projectionType == RPT_EXPORT ? Mat3x3::outputProjection(m_renderData.fbSize, HYPRUTILS_TRANSFORM_NORMAL) : m_renderData.pMonitor->getScaleMatrix()) + .copy() + .multiply(getBoxProjection(box, transform)); +} + +SP IHyprRenderer::blurMainFramebuffer(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.pMonitor->resources()->m_blurFB->getTexture(); // return something to sample from at least + } + + auto guard = bindTempFB(m_renderData.currentFB); // blurFramebuffer messes with FB bindings + return blurFramebuffer(m_renderData.currentFB, a, originalDamage); +} + +void IHyprRenderer::preBlurForCurrentMonitor(CRegion* fakeDamage) { + + const auto blurredTex = blurMainFramebuffer(1, fakeDamage); + + // render onto blurFB + auto guard = bindTempFB(m_renderData.pMonitor->resources()->m_blurFB); + const auto SAVE_TRANSFORM = blurredTex->m_transform; + blurredTex->m_transform = Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)); + + draw(CClearPassElement::SClearData{{0, 0, 0, 0}}); + + pushMonitorTransformEnabled(true); + + draw( + CTexPassElement::SRenderData{ + .tex = blurredTex, + .box = CBox{0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}, + .damage = *fakeDamage, + }, + *fakeDamage); // .noAA = true + + popMonitorTransformEnabled(); + + blurredTex->m_transform = SAVE_TRANSFORM; +} + +static bool isSDR2HDR(const NColorManagement::SImageDescription& imageDescription, const NColorManagement::SImageDescription& targetImageDescription) { + // might be too strict + return (imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB || + imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22) && + (targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ || + targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_HLG || + (targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_EXT_LINEAR && + g_pHyprRenderer->m_renderData.pMonitor->m_imageDescription->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ)); +} + +static bool isHDR2SDR(const NColorManagement::SImageDescription& imageDescription, const NColorManagement::SImageDescription& targetImageDescription) { + // might be too strict + return (imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ || + imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_HLG) && + (targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB || + targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22); +} + +void IHyprRenderer::clearCMSettingsCache() { + m_cmSettingsCache.clear(); +} + +SCMSettings IHyprRenderer::getCMSettings(const NColorManagement::PImageDescription imageDescription, const NColorManagement::PImageDescription targetImageDescription, + SP surface, bool modifySDR, float sdrMinLuminance, int sdrMaxLuminance, bool shouldUseSurface) { + const auto srcId = imageDescription->id(); + const auto dstId = targetImageDescription->id(); + void* sPtr = shouldUseSurface ? m_renderData.surface.get() : nullptr; + + for (auto const& entry : m_cmSettingsCache) { + if (entry.srcDescId == srcId && entry.dstDescId == dstId && entry.surfacePtr == sPtr && entry.modifySDR == modifySDR && entry.sdrMinLuminance == sdrMinLuminance && + entry.sdrMaxLuminance == sdrMaxLuminance) + return entry.settings; + } + + const auto sdrEOTF = NTransferFunction::fromConfig(); + NColorManagement::eTransferFunction srcTF; + + if (shouldUseSurface && m_renderData.surface.valid() && + (imageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_GAMMA22 || imageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_SRGB)) { + 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 = needsSDRmod && + ((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)); + + auto result = SCMSettings{ + .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, + }; + + m_cmSettingsCache.push_back({ + .srcDescId = srcId, + .dstDescId = dstId, + .surfacePtr = sPtr, + .modifySDR = modifySDR, + .sdrMinLuminance = sdrMinLuminance, + .sdrMaxLuminance = sdrMaxLuminance, + .settings = result, + }); + + return result; +} + +void IHyprRenderer::renderMirrored() { + auto monitor = m_renderData.pMonitor; + auto mirrored = monitor->m_mirrorOf; + + // saveBufferForMirror should create it + if (!mirrored->resources()->hasMirrorFB()) + return; + + const double scale = std::min(monitor->m_transformedSize.x / mirrored->m_transformedSize.x, monitor->m_transformedSize.y / mirrored->m_transformedSize.y); + CBox monbox = {0, 0, mirrored->m_transformedSize.x * scale, mirrored->m_transformedSize.y * scale}; + + // transform box as it will be drawn on a transformed projection + monbox.transform(Math::wlTransformToHyprutils(mirrored->m_transform), mirrored->m_transformedSize.x * scale, mirrored->m_transformedSize.y * scale); + + monbox.x = (monitor->m_transformedSize.x - monbox.w) / 2; + monbox.y = (monitor->m_transformedSize.y - monbox.h) / 2; + + const auto MIRROR_TEX = mirrored->resources()->getMirrorTexture(); + + m_renderPass.add(makeUnique(CClearPassElement::SClearData{CHyprColor(0, 0, 0, 0)})); + + CTexPassElement::SRenderData data; + data.tex = MIRROR_TEX; + data.box = monbox; + data.useMirrorProjection = true; + + m_renderPass.add(makeUnique(std::move(data))); +} + +void IHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { + if (!pMonitor) + return; 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(); static std::chrono::high_resolution_clock::time_point endRenderOverlay = std::chrono::high_resolution_clock::now(); @@ -1264,14 +1907,15 @@ 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 PVFR = CConfigValue("misc:vfr"); + static auto PSOLDAMAGE = CConfigValue("debug:render_solitary_wo_damage"); + static auto PVFR = CConfigValue("debug:vfr"); static int damageBlinkCleanup = 0; // because double-buffered const float ZOOMFACTOR = pMonitor->m_cursorZoom->value(); if (pMonitor->m_pixelSize.x < 1 || pMonitor->m_pixelSize.y < 1) { - Debug::log(ERR, "Refusing to render a monitor because of an invalid pixel size: {}", pMonitor->m_pixelSize); + Log::logger->log(Log::ERR, "Refusing to render a monitor because of an invalid pixel size: {}", pMonitor->m_pixelSize); return; } @@ -1280,49 +1924,61 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { if (*PDEBUGOVERLAY == 1) { renderStart = std::chrono::high_resolution_clock::now(); - g_pDebugOverlay->frameData(pMonitor); + Debug::overlay()->frameData(pMonitor); } if (!g_pCompositor->m_sessionActive) return; + Event::bus()->m_events.render.preChecks.emit(pMonitor); + if (g_pAnimationManager) g_pAnimationManager->frameTick(); - if (pMonitor->m_id == m_mostHzMonitor->m_id || - *PVFR == 1) { // unfortunately with VFR we don't have the guarantee mostHz is going to be updated all the time, so we have to ignore that - - g_pConfigManager->dispatchExecOnce(); // We exec-once when at least one monitor starts refreshing, meaning stuff has init'd - - if (g_pConfigManager->m_wantsMonitorReload) - g_pConfigManager->performMonitorReload(); + { + static bool once = true; + if (once) { + Event::bus()->m_events.start.emit(); + once = false; + } } if (pMonitor->m_scheduledRecalc) { pMonitor->m_scheduledRecalc = false; - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitor->m_id); + if (pMonitor->m_activeWorkspace) // might be missing (mirror) + pMonitor->m_activeWorkspace->m_space->recalculate(); } if (!pMonitor->m_output->needsFrame && pMonitor->m_forceFullFrames == 0) return; // tearing and DS first - bool shouldTear = pMonitor->updateTearing(); + bool shouldTear = pMonitor->updateTearing(); + const bool canAttemptDirectScanout = pMonitor->canAttemptDirectScanoutFast(); - if (pMonitor->attemptDirectScanout()) { - return; - } else if (!pMonitor->m_lastScanout.expired()) { - Debug::log(LOG, "Left a direct scanout."); - pMonitor->m_lastScanout.reset(); + if (canAttemptDirectScanout) { + if (pMonitor->attemptDirectScanout()) { + if (!pMonitor->m_directScanoutIsActive) { + pMonitor->m_previousFSWindow.reset(); // recalc fs settings + pMonitor->m_directScanoutIsActive = true; + } + handleFullscreenSettings(pMonitor); + return; + } else if (!pMonitor->m_lastScanout.expired() || pMonitor->m_directScanoutIsActive) { + Log::logger->log(Log::DEBUG, "Left a direct scanout."); + pMonitor->m_lastScanout.reset(); + pMonitor->m_previousFSWindow.reset(); // recalc fs settings + 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) - pMonitor->m_output->state->setFormat(pMonitor->m_prevDrmFormat); + // reset DRM format, but only if needed since it might modeset + if (pMonitor->m_output->state->state().drmFormat != pMonitor->m_prevDrmFormat) + pMonitor->m_output->state->setFormat(pMonitor->m_prevDrmFormat); - pMonitor->m_drmFormat = pMonitor->m_prevDrmFormat; + pMonitor->m_drmFormat = pMonitor->m_prevDrmFormat; + } } - EMIT_HOOK_EVENT("preRender", pMonitor); + Event::bus()->m_events.render.pre.emit(pMonitor); const auto NOW = Time::steadyNow(); @@ -1333,16 +1989,17 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { return; if (*PDAMAGETRACKINGMODE == -1) { - Debug::log(CRIT, "Damage tracking mode -1 ????"); + Log::logger->log(Log::CRIT, "Damage tracking mode -1 ????"); return; } - EMIT_HOOK_EVENT("render", RENDER_PRE); + Event::bus()->m_events.render.stage.emit(RENDER_PRE); pMonitor->m_renderingActive = true; - // we need to cleanup fading out when rendering the appropriate context - g_pCompositor->cleanupFadingOut(pMonitor->m_id); + // Most frames have no fading-out windows or layers for this monitor. + if (!g_pCompositor->m_windowsFadingOut.empty() || !g_pCompositor->m_surfacesFadingOut.empty()) + g_pCompositor->cleanupFadingOut(pMonitor->m_id); // TODO: this is getting called with extents being 0,0,0,0 should it be? // potentially can save on resources. @@ -1358,20 +2015,19 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { zoomLock = true; } - if (pMonitor == g_pCompositor->getMonitorFromCursor()) - g_pHyprOpenGL->m_renderData.mouseZoomFactor = std::clamp(ZOOMFACTOR, 1.f, INFINITY); - else - g_pHyprOpenGL->m_renderData.mouseZoomFactor = 1.f; + m_renderData.mouseZoomFactor = 1.f; + if (ZOOMFACTOR != 1.f && pMonitor == g_pCompositor->getMonitorFromCursor()) + m_renderData.mouseZoomFactor = std::clamp(ZOOMFACTOR, 1.f, INFINITY); if (pMonitor->m_zoomAnimProgress->value() != 1) { - g_pHyprOpenGL->m_renderData.mouseZoomFactor = 2.0 - pMonitor->m_zoomAnimProgress->value(); // 2x zoom -> 1x zoom - g_pHyprOpenGL->m_renderData.mouseZoomUseMouse = false; - g_pHyprOpenGL->m_renderData.useNearestNeighbor = false; + m_renderData.mouseZoomFactor = 2.0 - pMonitor->m_zoomAnimProgress->value(); // 2x zoom -> 1x zoom + m_renderData.mouseZoomUseMouse = false; + m_renderData.useNearestNeighbor = false; } CRegion damage, finalDamage; if (!beginRender(pMonitor, damage, RENDER_MODE_NORMAL)) { - Debug::log(ERR, "renderer: couldn't beginRender()!"); + Log::logger->log(Log::ERR, "renderer: couldn't beginRender()!"); return; } @@ -1382,7 +2038,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { finalDamage = damage; // update damage in renderdata as we modified it - g_pHyprOpenGL->setDamage(damage, finalDamage); + setDamage(damage, finalDamage); if (pMonitor->m_forceFullFrames > 0) { pMonitor->m_forceFullFrames -= 1; @@ -1390,50 +2046,49 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { pMonitor->m_forceFullFrames = 0; } - EMIT_HOOK_EVENT("render", RENDER_BEGIN); + Event::bus()->m_events.render.stage.emit(RENDER_BEGIN); bool renderCursor = true; - if (!finalDamage.empty()) { - if (pMonitor->m_solitaryClient.expired()) { - if (pMonitor->isMirror()) { - g_pHyprOpenGL->blend(false); - g_pHyprOpenGL->renderMirrored(); - g_pHyprOpenGL->blend(true); - EMIT_HOOK_EVENT("render", RENDER_POST_MIRROR); - renderCursor = false; - } else { - CBox renderBox = {0, 0, sc(pMonitor->m_pixelSize.x), sc(pMonitor->m_pixelSize.y)}; - renderWorkspace(pMonitor, pMonitor->m_activeWorkspace, NOW, renderBox); + if (pMonitor->m_solitaryClient && (!finalDamage.empty() || *PSOLDAMAGE)) + renderWindow(pMonitor->m_solitaryClient.lock(), pMonitor, NOW, false, RENDER_PASS_MAIN /* solitary = no popups */); + else if (!finalDamage.empty()) { + if (pMonitor->isMirror()) { + blend(false); + renderMirrored(); + blend(true); + Event::bus()->m_events.render.stage.emit(RENDER_POST_MIRROR); + renderCursor = false; + } else { + CBox renderBox = {0, 0, sc(pMonitor->m_pixelSize.x), sc(pMonitor->m_pixelSize.y)}; + renderWorkspace(pMonitor, pMonitor->m_activeWorkspace, NOW, renderBox); - renderLockscreen(pMonitor, NOW, renderBox); + renderLockscreen(pMonitor, NOW, renderBox); - if (pMonitor == Desktop::focusState()->monitor()) { - g_pHyprNotificationOverlay->draw(pMonitor); - g_pHyprError->draw(); - } - - // for drawing the debug overlay - if (pMonitor == g_pCompositor->m_monitors.front() && *PDEBUGOVERLAY == 1) { - renderStartOverlay = std::chrono::high_resolution_clock::now(); - g_pDebugOverlay->draw(); - endRenderOverlay = std::chrono::high_resolution_clock::now(); - } - - if (*PDAMAGEBLINK && damageBlinkCleanup == 0) { - CRectPassElement::SRectData data; - data.box = {0, 0, pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y}; - data.color = CHyprColor(1.0, 0.0, 1.0, 100.0 / 255.0); - m_renderPass.add(makeUnique(data)); - damageBlinkCleanup = 1; - } else if (*PDAMAGEBLINK) { - damageBlinkCleanup++; - if (damageBlinkCleanup > 3) - damageBlinkCleanup = 0; - } + if (pMonitor == Desktop::focusState()->monitor()) { + Notification::overlay()->draw(pMonitor); + ErrorOverlay::overlay()->draw(); } - } else - renderWindow(pMonitor->m_solitaryClient.lock(), pMonitor, NOW, false, RENDER_PASS_MAIN /* solitary = no popups */); + + // for drawing the debug overlay + if (pMonitor == g_pCompositor->m_monitors.front() && *PDEBUGOVERLAY == 1) { + renderStartOverlay = std::chrono::high_resolution_clock::now(); + Debug::overlay()->draw(); + endRenderOverlay = std::chrono::high_resolution_clock::now(); + } + + if (*PDAMAGEBLINK && damageBlinkCleanup == 0) { + CRectPassElement::SRectData data; + data.box = {0, 0, pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y}; + data.color = CHyprColor(1.0, 0.0, 1.0, 100.0 / 255.0); + m_renderPass.add(makeUnique(data)); + damageBlinkCleanup = 1; + } else if (*PDAMAGEBLINK) { + damageBlinkCleanup++; + if (damageBlinkCleanup > 3) + damageBlinkCleanup = 0; + } + } } else if (!pMonitor->isMirror()) { sendFrameEventsToWorkspace(pMonitor, pMonitor->m_activeWorkspace, NOW); if (pMonitor->m_activeSpecialWorkspace) @@ -1444,7 +2099,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { if (renderCursor) { TRACY_GPU_ZONE("RenderCursor"); - g_pPointerManager->renderSoftwareCursorsFor(pMonitor->m_self.lock(), NOW, g_pHyprOpenGL->m_renderData.damage); + g_pPointerManager->renderSoftwareCursorsFor(pMonitor->m_self.lock(), NOW, m_renderData.damage); } if (pMonitor->m_dpmsBlackOpacity->value() != 0.F) { @@ -1455,16 +2110,16 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { m_renderPass.add(makeUnique(data)); } - EMIT_HOOK_EVENT("render", RENDER_LAST_MOMENT); + Event::bus()->m_events.render.stage.emit(RENDER_LAST_MOMENT); endRender(); TRACY_GPU_COLLECT; - CRegion frameDamage{g_pHyprOpenGL->m_renderData.damage}; + CRegion frameDamage{m_renderData.damage}; - const auto TRANSFORM = invertTransform(pMonitor->m_transform); - frameDamage.transform(wlTransformToHyprutils(TRANSFORM), pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y); + const auto TRANSFORM = Math::invertTransform(pMonitor->m_transform); + frameDamage.transform(Math::wlTransformToHyprutils(TRANSFORM), pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y); if (*PDAMAGETRACKINGMODE == DAMAGE_TRACKING_NONE || *PDAMAGETRACKINGMODE == DAMAGE_TRACKING_MONITOR) frameDamage.add(0, 0, sc(pMonitor->m_transformedSize.x), sc(pMonitor->m_transformedSize.y)); @@ -1477,7 +2132,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { pMonitor->m_renderingActive = false; - EMIT_HOOK_EVENT("render", RENDER_POST); + Event::bus()->m_events.render.stage.emit(RENDER_POST); pMonitor->m_output->state->addDamage(frameDamage); pMonitor->m_output->state->setPresentationMode(shouldTear ? Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE : @@ -1496,13 +2151,13 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { if (*PDEBUGOVERLAY == 1) { const float durationUs = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - renderStart).count() / 1000.f; - g_pDebugOverlay->renderData(pMonitor, durationUs); + Debug::overlay()->renderData(pMonitor, durationUs); if (pMonitor == g_pCompositor->m_monitors.front()) { const float noOverlayUs = durationUs - std::chrono::duration_cast(endRenderOverlay - renderStartOverlay).count() / 1000.f; - g_pDebugOverlay->renderDataNoOverlay(pMonitor, noOverlayUs); + Debug::overlay()->renderDataNoOverlay(pMonitor, noOverlayUs); } else - g_pDebugOverlay->renderDataNoOverlay(pMonitor, durationUs); + Debug::overlay()->renderDataNoOverlay(pMonitor, durationUs); } } @@ -1511,6 +2166,7 @@ static const hdr_output_metadata NO_HDR_METADATA = {.hdmi_metadata_type1 = hdr_m static hdr_output_metadata createHDRMetadata(SImageDescription settings, SP monitor) { uint8_t eotf = 0; switch (settings.transferFunction) { + case CM_TRANSFER_FUNCTION_GAMMA22: case CM_TRANSFER_FUNCTION_SRGB: eotf = 0; break; // used to send primaries and luminances to AQ. ignored for now case CM_TRANSFER_FUNCTION_ST2084_PQ: eotf = 2; break; case CM_TRANSFER_FUNCTION_EXT_LINEAR: @@ -1523,83 +2179,75 @@ static hdr_output_metadata createHDRMetadata(SImageDescription settings, S const auto toNits = [](uint32_t value) { return sc(std::round(value)); }; const auto to16Bit = [](float value) { return sc(std::round(value * 50000)); }; - auto colorimetry = settings.primariesNameSet || settings.primaries == SPCPRimaries{} ? getPrimaries(settings.primariesNamed) : settings.primaries; + auto colorimetry = settings.getPrimaries(); auto luminances = settings.masteringLuminances.max > 0 ? settings.masteringLuminances : - SImageDescription::SPCMasteringLuminances{.min = monitor->minLuminance(), .max = monitor->maxLuminance(10000)}; + (settings.luminances != SImageDescription::SPCLuminances{} ? + SImageDescription::SPCMasteringLuminances{.min = settings.luminances.min, .max = settings.luminances.max} : + SImageDescription::SPCMasteringLuminances{.min = monitor->minLuminance(), .max = monitor->maxLuminance(10000)}); - Debug::log(TRACE, "ColorManagement primaries {},{} {},{} {},{} {},{}", colorimetry.red.x, colorimetry.red.y, colorimetry.green.x, colorimetry.green.y, colorimetry.blue.x, - colorimetry.blue.y, colorimetry.white.x, colorimetry.white.y); - Debug::log(TRACE, "ColorManagement min {}, max {}, cll {}, fall {}", luminances.min, luminances.max, settings.maxCLL, settings.maxFALL); + Log::logger->log(Log::TRACE, "ColorManagement primaries {},{} {},{} {},{} {},{}", colorimetry.red.x, colorimetry.red.y, colorimetry.green.x, colorimetry.green.y, + colorimetry.blue.x, colorimetry.blue.y, colorimetry.white.x, colorimetry.white.y); + Log::logger->log(Log::TRACE, "ColorManagement min {}, max {}, cll {}, fall {}", luminances.min, luminances.max, settings.maxCLL, settings.maxFALL); return hdr_output_metadata{ - .metadata_type = 0, - .hdmi_metadata_type1 = + .metadata_type = 0, + .hdmi_metadata_type1 = hdr_metadata_infoframe{ - .eotf = eotf, - .metadata_type = 0, - .display_primaries = - { + .eotf = eotf, + .metadata_type = 0, + .display_primaries = + { {.x = to16Bit(colorimetry.red.x), .y = to16Bit(colorimetry.red.y)}, {.x = to16Bit(colorimetry.green.x), .y = to16Bit(colorimetry.green.y)}, {.x = to16Bit(colorimetry.blue.x), .y = to16Bit(colorimetry.blue.y)}, }, - .white_point = {.x = to16Bit(colorimetry.white.x), .y = to16Bit(colorimetry.white.y)}, - .max_display_mastering_luminance = toNits(luminances.max), - .min_display_mastering_luminance = toNits(luminances.min * 10000), - .max_cll = toNits(settings.maxCLL), - .max_fall = toNits(settings.maxFALL), + .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()), }, }; } -bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { +void IHyprRenderer::handleFullscreenSettings(PHLMONITOR pMonitor) { static auto PCT = CConfigValue("render:send_content_type"); - static auto PPASS = CConfigValue("render:cm_fs_passthrough"); static auto PAUTOHDR = CConfigValue("render:cm_auto_hdr"); static auto PNONSHADER = CConfigValue("render:non_shader_cm"); - - static bool needsHDRupdate = false; + static auto PNSINTEROP = CConfigValue("render:non_shader_cm_interop"); const bool configuredHDR = (pMonitor->m_cmType == NCMType::CM_HDR_EDID || pMonitor->m_cmType == NCMType::CM_HDR); bool wantHDR = configuredHDR; - const auto FS_WINDOW = pMonitor->inFullscreenMode() ? pMonitor->m_activeWorkspace->getFullscreenWindow() : nullptr; + const auto FS_WINDOW = pMonitor->getFullscreenWindow(); if (pMonitor->supportsHDR()) { // HDR metadata determined by // HDR scRGB - monitor settings // HDR PQ surface & DS is active - surface settings - // PPASS = 0 monitor settings - // PPASS = 1 - // windowed: monitor settings - // fullscreen surface: surface settings FIXME: fullscreen SDR surface passthrough - pass degamma, gamma if needed - // PPASS = 2 - // windowed: monitor settings - // fullscreen SDR surface: monitor settings - // fullscreen HDR surface: surface settings bool hdrIsHandled = false; if (FS_WINDOW) { - const auto ROOT_SURF = FS_WINDOW->m_wlSurface->resource(); + const auto ROOT_SURF = FS_WINDOW->wlSurface()->resource(); const auto SURF = ROOT_SURF->findWithCM(); // we have a surface with image description if (SURF && SURF->m_colorManagement.valid() && SURF->m_colorManagement->hasImageDescription()) { const bool surfaceIsHDR = SURF->m_colorManagement->isHDR(); - if (!SURF->m_colorManagement->isWindowsScRGB() && (*PPASS == 1 || ((*PPASS == 2 || !pMonitor->m_lastScanout.expired()) && surfaceIsHDR))) { - // passthrough - bool needsHdrMetadataUpdate = SURF->m_colorManagement->needsHdrMetadataUpdate() || pMonitor->m_previousFSWindow != FS_WINDOW || needsHDRupdate; + wantHDR = *PAUTOHDR && surfaceIsHDR; + if (surfaceIsHDR && !SURF->m_colorManagement->isWindowsScRGB() && !pMonitor->m_lastScanout.expired()) { + // DS HDR + bool needsHdrMetadataUpdate = SURF->m_colorManagement->needsHdrMetadataUpdate() || pMonitor->m_previousFSWindow != FS_WINDOW || pMonitor->m_needsHDRupdate; if (SURF->m_colorManagement->needsHdrMetadataUpdate()) { - Debug::log(INFO, "[CM] Recreating HDR metadata for surface"); + Log::logger->log(Log::INFO, "[CM] Recreating HDR metadata for surface"); SURF->m_colorManagement->setHDRMetadata(createHDRMetadata(SURF->m_colorManagement->imageDescription(), pMonitor)); } if (needsHdrMetadataUpdate) { - Debug::log(INFO, "[CM] Updating HDR metadata from surface"); + Log::logger->log(Log::INFO, "[CM] Updating HDR metadata from surface"); pMonitor->m_output->state->setHDRMetadata(SURF->m_colorManagement->hdrMetadata()); } - hdrIsHandled = true; - needsHDRupdate = false; - } else if (*PAUTOHDR && surfaceIsHDR) - wantHDR = true; // auto-hdr: hdr on + hdrIsHandled = true; + pMonitor->m_needsHDRupdate = false; + } } } @@ -1610,29 +2258,29 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { // FIXME ok for now, will need some other logic if monitor image description can be modified some other way const auto targetCM = wantHDR ? (*PAUTOHDR == 2 ? NCMType::CM_HDR_EDID : NCMType::CM_HDR) : pMonitor->m_cmType; const auto targetSDREOTF = pMonitor->m_sdrEotf; - Debug::log(INFO, "[CM] Auto HDR: changing monitor cm to {}", sc(targetCM)); + Log::logger->log(Log::INFO, "[CM] Auto HDR: changing monitor cm to {}", sc(targetCM)); pMonitor->applyCMType(targetCM, targetSDREOTF); pMonitor->m_previousFSWindow.reset(); // trigger CTM update } - Debug::log(INFO, wantHDR ? "[CM] Updating HDR metadata from monitor" : "[CM] Restoring SDR mode"); - pMonitor->m_output->state->setHDRMetadata(wantHDR ? createHDRMetadata(pMonitor->m_imageDescription, pMonitor) : NO_HDR_METADATA); + Log::logger->log(Log::INFO, wantHDR ? "[CM] Updating HDR metadata from monitor" : "[CM] Restoring SDR mode"); + pMonitor->m_output->state->setHDRMetadata(wantHDR ? createHDRMetadata(pMonitor->m_imageDescription->value(), pMonitor) : NO_HDR_METADATA); } - needsHDRupdate = true; + pMonitor->m_needsHDRupdate = true; } } const bool needsWCG = pMonitor->wantsWideColor(); if (pMonitor->m_output->state->state().wideColorGamut != needsWCG) { - Debug::log(TRACE, "Setting wide color gamut {}", needsWCG ? "on" : "off"); + Log::logger->log(Log::TRACE, "Setting wide color gamut {}", needsWCG ? "on" : "off"); pMonitor->m_output->state->setWideColorGamut(needsWCG); // FIXME do not trust enabled10bit, auto switch to 10bit and back if needed if (needsWCG && !pMonitor->m_enabled10bit) { - Debug::log(WARN, "Wide color gamut is enabled but the display is not in 10bit mode"); + Log::logger->log(Log::WARN, "Wide color gamut is enabled but the display is not in 10bit mode"); static bool shown = false; if (!shown) { - g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, {{"name", pMonitor->m_name}}), CHyprColor{}, 15000, - ICON_WARNING); + Notification::overlay()->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, {{"name", pMonitor->m_name}}), CHyprColor{}, 15000, + ICON_WARNING); shown = true; } } @@ -1641,27 +2289,50 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { if (*PCT) pMonitor->m_output->state->setContentType(NContentType::toDRM(FS_WINDOW ? FS_WINDOW->getContentType() : CONTENT_TYPE_NONE)); - if (FS_WINDOW != pMonitor->m_previousFSWindow) { - if (*PNONSHADER == CM_NS_IGNORE || !FS_WINDOW || !pMonitor->needsCM() || !pMonitor->canNoShaderCM() || - (*PNONSHADER == CM_NS_ONDEMAND && pMonitor->m_lastScanout.expired() && *PPASS != 1)) { - if (pMonitor->m_noShaderCTM) { - Debug::log(INFO, "[CM] No fullscreen CTM, restoring previous one"); - pMonitor->m_noShaderCTM = false; - pMonitor->m_ctmUpdated = true; - } - } else { - const auto FS_DESC = pMonitor->getFSImageDescription(); - if (FS_DESC.has_value()) { - Debug::log(INFO, "[CM] Updating fullscreen CTM"); - pMonitor->m_noShaderCTM = true; - const auto mat = FS_DESC->getPrimaries().convertMatrix(pMonitor->m_imageDescription.getPrimaries()).mat(); + if (FS_WINDOW != pMonitor->m_previousFSWindow || (!FS_WINDOW && pMonitor->m_noShaderCTM) || pMonitor->m_ctmUpdated) { + const bool INTEROP = (*PNSINTEROP == 1 || (*PNSINTEROP == 2 && FS_WINDOW && FS_WINDOW->getContentType() == CONTENT_TYPE_NONE)); + bool resetCTM = !FS_WINDOW; + if (FS_WINDOW) { + if (*PNONSHADER == CM_NS_IGNORE) + resetCTM = true; + else if (const auto FS_DESC = pMonitor->getFSImageDescription(); pMonitor->needsCM() && pMonitor->canNoShaderCM(!pMonitor->m_lastScanout.expired()) && + FS_DESC.has_value() && (*PNONSHADER != CM_NS_ONDEMAND || !pMonitor->m_lastScanout.expired())) { + Log::logger->log(Log::INFO, "[CM] Updating fullscreen CTM"); + pMonitor->m_noShaderCTM = true; + pMonitor->m_ctmUpdated = false; + auto conversion = FS_DESC.value()->getPrimaries()->convertMatrix(pMonitor->m_imageDescription->getPrimaries()); + if (pMonitor->m_ctm != Mat3x3::identity() && INTEROP) { + const auto& ctm = pMonitor->m_ctm.getMatrix(); + std::array, 3> values = { + { + {ctm[0], ctm[1], ctm[2]}, + {ctm[3], ctm[4], ctm[5]}, + {ctm[6], ctm[7], ctm[8]}, + }, + }; + conversion = conversion * Hyprgraphics::CMatrix3(values); + } + const auto mat = conversion.mat(); const std::array CTM = { mat[0][0], mat[0][1], mat[0][2], // mat[1][0], mat[1][1], mat[1][2], // mat[2][0], mat[2][1], mat[2][2], // }; pMonitor->m_output->state->setCTM(CTM); - } + } else if (!INTEROP && pMonitor->m_ctm != Mat3x3::identity()) { + Log::logger->log(Log::INFO, "[CM] Setting identity CTM"); + pMonitor->m_noShaderCTM = true; + pMonitor->m_ctmUpdated = false; + + pMonitor->m_output->state->setCTM(Mat3x3::identity()); + } else + resetCTM = true; + } + + if (resetCTM && pMonitor->m_noShaderCTM) { + Log::logger->log(Log::INFO, "[CM] No fullscreen CTM, restoring previous one"); + pMonitor->m_noShaderCTM = false; + pMonitor->m_ctmUpdated = true; } } @@ -1671,17 +2342,21 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { } pMonitor->m_previousFSWindow = FS_WINDOW; +} + +bool IHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { + handleFullscreenSettings(pMonitor); bool ok = pMonitor->m_state.commit(); if (!ok) { if (pMonitor->m_inFence.isValid()) { - Debug::log(TRACE, "Monitor state commit failed, retrying without a fence"); + Log::logger->log(Log::TRACE, "Monitor state commit failed, retrying without a fence"); pMonitor->m_output->state->resetExplicitFences(); ok = pMonitor->m_state.commit(); } if (!ok) { - Debug::log(TRACE, "Monitor state commit failed"); + Log::logger->log(Log::TRACE, "Monitor state commit failed"); // rollback the buffer to avoid writing to the front buffer that is being // displayed pMonitor->m_output->swapchain->rollback(); @@ -1692,14 +2367,14 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { return ok; } -void CHyprRenderer::renderWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& now, const CBox& geometry) { +void IHyprRenderer::renderWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& now, const CBox& geometry) { Vector2D translate = {geometry.x, geometry.y}; float scale = sc(geometry.width) / pMonitor->m_pixelSize.x; TRACY_GPU_ZONE("RenderWorkspace"); if (!DELTALESSTHAN(sc(geometry.width) / sc(geometry.height), pMonitor->m_pixelSize.x / pMonitor->m_pixelSize.y, 0.01)) { - Debug::log(ERR, "Ignoring geometry in renderWorkspace: aspect ratio mismatch"); + Log::logger->log(Log::ERR, "Ignoring geometry in renderWorkspace: aspect ratio mismatch"); scale = 1.f; translate = Vector2D{}; } @@ -1707,28 +2382,16 @@ void CHyprRenderer::renderWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace renderAllClientsForWorkspace(pMonitor, pWorkspace, now, translate, scale); } -void CHyprRenderer::sendFrameEventsToWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& now) { - for (auto const& w : g_pCompositor->m_windows) { - if (w->isHidden() || !w->m_isMapped || w->m_fadingOut || !w->m_wlSurface->resource()) +void IHyprRenderer::sendFrameEventsToWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& now) { + for (const auto& view : Desktop::View::getViewsForWorkspace(pWorkspace)) { + if (!view->aliveAndVisible()) continue; - if (!shouldRenderWindow(w, pMonitor)) - continue; - - w->m_wlSurface->resource()->breadthfirst([now](SP r, const Vector2D& offset, void* d) { r->frame(now); }, nullptr); - } - - for (auto const& lsl : pMonitor->m_layerSurfaceLayers) { - for (auto const& ls : lsl) { - if (ls->m_fadingOut || !ls->m_surface->resource()) - continue; - - ls->m_surface->resource()->breadthfirst([now](SP r, const Vector2D& offset, void* d) { r->frame(now); }, nullptr); - } + view->wlSurface()->resource()->frame(now); } } -void CHyprRenderer::setSurfaceScanoutMode(SP surface, PHLMONITOR monitor) { +void IHyprRenderer::setSurfaceScanoutMode(SP surface, PHLMONITOR monitor) { if (!PROTO::linuxDma) return; @@ -1795,7 +2458,7 @@ static void applyExclusive(CBox& usableArea, uint32_t anchor, int32_t exclusive, } } -void CHyprRenderer::arrangeLayerArray(PHLMONITOR pMonitor, const std::vector& layerSurfaces, bool exclusiveZone, CBox* usableArea) { +void IHyprRenderer::arrangeLayerArray(PHLMONITOR pMonitor, const std::vector& layerSurfaces, bool exclusiveZone, CBox* usableArea) { CBox full_area = {pMonitor->m_position.x, pMonitor->m_position.y, pMonitor->m_size.x, pMonitor->m_size.y}; for (auto const& ls : layerSurfaces) { @@ -1864,7 +2527,7 @@ void CHyprRenderer::arrangeLayerArray(PHLMONITOR pMonitor, const std::vectormargin.bottom; if (box.width <= 0 || box.height <= 0) { - Debug::log(ERR, "LayerSurface {:x} has a negative/zero w/h???", rc(ls.get())); + Log::logger->log(Log::ERR, "LayerSurface {:x} has a negative/zero w/h???", rc(ls.get())); continue; } @@ -1882,31 +2545,17 @@ void CHyprRenderer::arrangeLayerArray(PHLMONITOR pMonitor, const std::vectorgetMonitorFromID(monitor); +void IHyprRenderer::arrangeLayersForMonitor(const MONITORID& monitor) { + const auto PMONITOR = g_pCompositor->getMonitorFromID(monitor); - static auto BAR_POSITION = CConfigValue("debug:error_position"); - - if (!PMONITOR) + if (!PMONITOR || PMONITOR->m_size.x <= 0 || PMONITOR->m_size.y <= 0) return; // Reset the reserved - PMONITOR->m_reservedBottomRight = Vector2D(); - PMONITOR->m_reservedTopLeft = Vector2D(); + PMONITOR->m_reservedArea.resetType(Desktop::RESERVED_DYNAMIC_TYPE_LS); - CBox usableArea = {PMONITOR->m_position.x, PMONITOR->m_position.y, PMONITOR->m_size.x, PMONITOR->m_size.y}; - - if (g_pHyprError->active() && Desktop::focusState()->monitor() == PMONITOR->m_self) { - const auto HEIGHT = g_pHyprError->height(); - if (*BAR_POSITION == 0) { - PMONITOR->m_reservedTopLeft.y = HEIGHT; - usableArea.y += HEIGHT; - usableArea.h -= HEIGHT; - } else { - PMONITOR->m_reservedBottomRight.y = HEIGHT; - usableArea.h -= HEIGHT; - } - } + const CBox ORIGINAL_USABLE_AREA = PMONITOR->logicalBoxMinusReserved(); + CBox usableArea = ORIGINAL_USABLE_AREA; for (auto& la : PMONITOR->m_layerSurfaceLayers) { std::ranges::stable_sort( @@ -1919,55 +2568,59 @@ void CHyprRenderer::arrangeLayersForMonitor(const MONITORID& monitor) { for (auto const& la : PMONITOR->m_layerSurfaceLayers) arrangeLayerArray(PMONITOR, la, false, &usableArea); - PMONITOR->m_reservedTopLeft = Vector2D(usableArea.x, usableArea.y) - PMONITOR->m_position; - PMONITOR->m_reservedBottomRight = PMONITOR->m_size - Vector2D(usableArea.width, usableArea.height) - PMONITOR->m_reservedTopLeft; - - auto ADDITIONALRESERVED = g_pConfigManager->m_mAdditionalReservedAreas.find(PMONITOR->m_name); - if (ADDITIONALRESERVED == g_pConfigManager->m_mAdditionalReservedAreas.end()) { - ADDITIONALRESERVED = g_pConfigManager->m_mAdditionalReservedAreas.find(""); // glob wildcard - } - - if (ADDITIONALRESERVED != g_pConfigManager->m_mAdditionalReservedAreas.end()) { - PMONITOR->m_reservedTopLeft = PMONITOR->m_reservedTopLeft + Vector2D(ADDITIONALRESERVED->second.left, ADDITIONALRESERVED->second.top); - PMONITOR->m_reservedBottomRight = PMONITOR->m_reservedBottomRight + Vector2D(ADDITIONALRESERVED->second.right, ADDITIONALRESERVED->second.bottom); - } + PMONITOR->m_reservedArea.addType(Desktop::RESERVED_DYNAMIC_TYPE_LS, Desktop::CReservedArea{ORIGINAL_USABLE_AREA, usableArea}); // damage the monitor if can damageMonitor(PMONITOR); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitor); + g_layoutManager->invalidateMonitorGeometries(PMONITOR); } -void CHyprRenderer::damageSurface(SP pSurface, double x, double y, double scale) { +void IHyprRenderer::damageSurface(SP pSurface, double x, double y, double scale) { if (!pSurface) return; // wut? if (g_pCompositor->m_unsafeState) return; - const auto WLSURF = CWLSurface::fromResource(pSurface); - CRegion damageBox = WLSURF ? WLSURF->computeDamage() : CRegion{}; + const auto WLSURF = Desktop::View::CWLSurface::fromResource(pSurface); if (!WLSURF) { - Debug::log(ERR, "BUG THIS: No CWLSurface for surface in damageSurface!!!"); + 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; + const auto EXTENTS = damageBox.getExtents(); + + CRegion damageBoxForEach; for (auto const& m : g_pCompositor->m_monitors) { if (!m->m_output) continue; + if (!EXTENTS.overlaps(m->logicalBox())) + continue; damageBoxForEach.set(damageBox); damageBoxForEach.translate({-m->m_position.x, -m->m_position.y}).scale(m->m_scale); @@ -1978,11 +2631,11 @@ void CHyprRenderer::damageSurface(SP pSurface, double x, dou static auto PLOGDAMAGE = CConfigValue("debug:log_damage"); if (*PLOGDAMAGE) - Debug::log(LOG, "Damage: Surface (extents): xy: {}, {} wh: {}, {}", damageBox.pixman()->extents.x1, damageBox.pixman()->extents.y1, - damageBox.pixman()->extents.x2 - damageBox.pixman()->extents.x1, damageBox.pixman()->extents.y2 - damageBox.pixman()->extents.y1); + Log::logger->log(Log::DEBUG, "Damage: Surface (extents): xy: {}, {} wh: {}, {}", damageBox.pixman()->extents.x1, damageBox.pixman()->extents.y1, + damageBox.pixman()->extents.x2 - damageBox.pixman()->extents.x1, damageBox.pixman()->extents.y2 - damageBox.pixman()->extents.y1); } -void CHyprRenderer::damageWindow(PHLWINDOW pWindow, bool forceFull) { +void IHyprRenderer::damageWindow(PHLWINDOW pWindow, bool forceFull) { if (g_pCompositor->m_unsafeState) return; @@ -2000,16 +2653,13 @@ void CHyprRenderer::damageWindow(PHLWINDOW pWindow, bool forceFull) { } } - for (auto const& wd : pWindow->m_windowDecorations) - wd->damageEntire(); - static auto PLOGDAMAGE = CConfigValue("debug:log_damage"); if (*PLOGDAMAGE) - Debug::log(LOG, "Damage: Window ({}): xy: {}, {} wh: {}, {}", pWindow->m_title, windowBox.x, windowBox.y, windowBox.width, windowBox.height); + Log::logger->log(Log::DEBUG, "Damage: Window ({}): xy: {}, {} wh: {}, {}", pWindow->m_title, windowBox.x, windowBox.y, windowBox.width, windowBox.height); } -void CHyprRenderer::damageMonitor(PHLMONITOR pMonitor) { +void IHyprRenderer::damageMonitor(PHLMONITOR pMonitor) { if (g_pCompositor->m_unsafeState || pMonitor->isMirror()) return; @@ -2019,10 +2669,10 @@ void CHyprRenderer::damageMonitor(PHLMONITOR pMonitor) { static auto PLOGDAMAGE = CConfigValue("debug:log_damage"); if (*PLOGDAMAGE) - Debug::log(LOG, "Damage: Monitor {}", pMonitor->m_name); + Log::logger->log(Log::DEBUG, "Damage: Monitor {}", pMonitor->m_name); } -void CHyprRenderer::damageBox(const CBox& box, bool skipFrameSchedule) { +void IHyprRenderer::damageBox(const CBox& box, bool skipFrameSchedule) { if (g_pCompositor->m_unsafeState) return; @@ -2039,19 +2689,19 @@ void CHyprRenderer::damageBox(const CBox& box, bool skipFrameSchedule) { static auto PLOGDAMAGE = CConfigValue("debug:log_damage"); if (*PLOGDAMAGE) - Debug::log(LOG, "Damage: Box: xy: {}, {} wh: {}, {}", box.x, box.y, box.w, box.h); + Log::logger->log(Log::DEBUG, "Damage: Box: xy: {}, {} wh: {}, {}", box.x, box.y, box.w, box.h); } -void CHyprRenderer::damageBox(const int& x, const int& y, const int& w, const int& h) { +void IHyprRenderer::damageBox(const int& x, const int& y, const int& w, const int& h) { CBox box = {x, y, w, h}; damageBox(box); } -void CHyprRenderer::damageRegion(const CRegion& rg) { +void IHyprRenderer::damageRegion(const CRegion& rg) { rg.forEachRect([this](const auto& RECT) { damageBox(RECT.x1, RECT.y1, RECT.x2 - RECT.x1, RECT.y2 - RECT.y1); }); } -void CHyprRenderer::damageMirrorsWith(PHLMONITOR pMonitor, const CRegion& pRegion) { +void IHyprRenderer::damageMirrorsWith(PHLMONITOR pMonitor, const CRegion& pRegion) { for (auto const& mirror : pMonitor->m_mirrors) { // transform the damage here, so it won't get clipped by the monitor damage ring @@ -2066,7 +2716,7 @@ void CHyprRenderer::damageMirrorsWith(PHLMONITOR pMonitor, const CRegion& pRegio monbox.y = (monitor->m_transformedSize.y - monbox.h) / 2; transformed.scale(scale); - transformed.transform(wlTransformToHyprutils(pMonitor->m_transform), pMonitor->m_pixelSize.x * scale, pMonitor->m_pixelSize.y * scale); + transformed.transform(Math::wlTransformToHyprutils(pMonitor->m_transform), pMonitor->m_pixelSize.x * scale, pMonitor->m_pixelSize.y * scale); transformed.translate(Vector2D(monbox.x, monbox.y)); mirror->addDamage(transformed); @@ -2075,12 +2725,12 @@ void CHyprRenderer::damageMirrorsWith(PHLMONITOR pMonitor, const CRegion& pRegio } } -void CHyprRenderer::renderDragIcon(PHLMONITOR pMonitor, const Time::steady_tp& time) { +void IHyprRenderer::renderDragIcon(PHLMONITOR pMonitor, const Time::steady_tp& time) { PROTO::data->renderDND(pMonitor, time); } -void CHyprRenderer::setCursorSurface(SP surf, int hotspotX, int hotspotY, bool force) { - m_cursorHasSurface = surf; +void IHyprRenderer::setCursorSurface(SP surf, int hotspotX, int hotspotY, bool force) { + m_cursorHasSurface = surf && surf->resource(); m_lastCursorData.name = ""; m_lastCursorData.surf = surf; @@ -2093,7 +2743,7 @@ void CHyprRenderer::setCursorSurface(SP surf, int hotspotX, int hots g_pCursorManager->setCursorSurface(surf, {hotspotX, hotspotY}); } -void CHyprRenderer::setCursorFromName(const std::string& name, bool force) { +void IHyprRenderer::setCursorFromName(const std::string& name, bool force) { m_cursorHasSurface = true; if (name == m_lastCursorData.name && !force) @@ -2144,56 +2794,49 @@ void CHyprRenderer::setCursorFromName(const std::string& name, bool force) { g_pCursorManager->setCursorFromName(name); } -void CHyprRenderer::ensureCursorRenderingMode() { +void IHyprRenderer::ensureCursorRenderingMode() { static auto PINVISIBLE = CConfigValue("cursor:invisible"); static auto PCURSORTIMEOUT = CConfigValue("cursor:inactive_timeout"); static auto PHIDEONTOUCH = CConfigValue("cursor:hide_on_touch"); + static auto PHIDEONTABLET = CConfigValue("cursor:hide_on_tablet"); static auto PHIDEONKEY = CConfigValue("cursor:hide_on_key_press"); if (*PCURSORTIMEOUT <= 0) m_cursorHiddenConditions.hiddenOnTimeout = false; if (*PHIDEONTOUCH == 0) m_cursorHiddenConditions.hiddenOnTouch = false; + if (*PHIDEONTABLET == 0) + m_cursorHiddenConditions.hiddenOnTablet = false; if (*PHIDEONKEY == 0) m_cursorHiddenConditions.hiddenOnKeyboard = false; if (*PCURSORTIMEOUT > 0) m_cursorHiddenConditions.hiddenOnTimeout = *PCURSORTIMEOUT < g_pInputManager->m_lastCursorMovement.getSeconds(); - m_cursorHiddenByCondition = m_cursorHiddenConditions.hiddenOnTimeout || m_cursorHiddenConditions.hiddenOnTouch || m_cursorHiddenConditions.hiddenOnKeyboard; + m_cursorHiddenByCondition = + m_cursorHiddenConditions.hiddenOnTimeout || m_cursorHiddenConditions.hiddenOnTouch || m_cursorHiddenConditions.hiddenOnTablet || m_cursorHiddenConditions.hiddenOnKeyboard; const bool HIDE = m_cursorHiddenByCondition || (*PINVISIBLE != 0); if (HIDE == m_cursorHidden) return; - if (HIDE) { - Debug::log(LOG, "Hiding the cursor (hl-mandated)"); + if (HIDE) + Log::logger->log(Log::DEBUG, "Hiding the cursor (hl-mandated)"); + else + Log::logger->log(Log::DEBUG, "Showing the cursor (hl-mandated)"); - for (auto const& m : g_pCompositor->m_monitors) { - if (!g_pPointerManager->softwareLockedFor(m)) - continue; + for (auto const& m : g_pCompositor->m_monitors) { + if (!g_pPointerManager->softwareLockedFor(m)) + continue; - damageMonitor(m); // TODO: maybe just damage the cursor area? - } - - setCursorHidden(true); - - } else { - Debug::log(LOG, "Showing the cursor (hl-mandated)"); - - for (auto const& m : g_pCompositor->m_monitors) { - if (!g_pPointerManager->softwareLockedFor(m)) - continue; - - damageMonitor(m); // TODO: maybe just damage the cursor area? - } - - setCursorHidden(false); + g_pPointerManager->damageCursor(m, m->shouldSkipScheduleFrameOnMouseEvent()); } + + setCursorHidden(HIDE); } -void CHyprRenderer::setCursorHidden(bool hide) { +void IHyprRenderer::setCursorHidden(bool hide) { if (hide == m_cursorHidden) return; @@ -2213,21 +2856,19 @@ void CHyprRenderer::setCursorHidden(bool hide) { setCursorFromName("left_ptr", true); } -bool CHyprRenderer::shouldRenderCursor() { +bool IHyprRenderer::shouldRenderCursor() { return !m_cursorHidden && m_cursorHasSurface; } -std::tuple CHyprRenderer::getRenderTimes(PHLMONITOR pMonitor) { - const auto POVERLAY = &g_pDebugOverlay->m_monitorOverlays[pMonitor]; +std::tuple IHyprRenderer::getRenderTimes(PHLMONITOR pMonitor) { + const auto POVERLAY = &Debug::overlay()->m_monitorOverlays[pMonitor]; float avgRenderTime = 0; float maxRenderTime = 0; float minRenderTime = 9999; for (auto const& rt : POVERLAY->m_lastRenderTimes) { - if (rt > maxRenderTime) - maxRenderTime = rt; - if (rt < minRenderTime) - minRenderTime = rt; + maxRenderTime = std::max(rt, maxRenderTime); + minRenderTime = std::min(rt, minRenderTime); avgRenderTime += rt; } avgRenderTime /= POVERLAY->m_lastRenderTimes.empty() ? 1 : POVERLAY->m_lastRenderTimes.size(); @@ -2237,8 +2878,8 @@ std::tuple CHyprRenderer::getRenderTimes(PHLMONITOR pMonito static int handleCrashLoop(void* data) { - g_pHyprNotificationOverlay->addNotification("Hyprland will crash in " + std::to_string(10 - sc(g_pHyprRenderer->m_crashingDistort * 2.f)) + "s.", CHyprColor(0), 5000, - ICON_INFO); + Notification::overlay()->addNotification("Hyprland will crash in " + std::to_string(10 - sc(g_pHyprRenderer->m_crashingDistort * 2.f)) + "s.", CHyprColor(0), 5000, + ICON_INFO); g_pHyprRenderer->m_crashingDistort += 0.5f; @@ -2250,8 +2891,8 @@ static int handleCrashLoop(void* data) { return 1; } -void CHyprRenderer::initiateManualCrash() { - g_pHyprNotificationOverlay->addNotification("Manual crash initiated. Farewell...", CHyprColor(0), 5000, ICON_INFO); +void IHyprRenderer::initiateManualCrash() { + Notification::overlay()->addNotification("Manual crash initiated. Farewell...", CHyprColor(0), 5000, ICON_INFO); m_crashingLoop = wl_event_loop_add_timer(g_pCompositor->m_wlEventLoop, handleCrashLoop, nullptr); wl_event_source_timer_update(m_crashingLoop, 1000); @@ -2259,20 +2900,22 @@ void CHyprRenderer::initiateManualCrash() { m_crashingInProgress = true; m_crashingDistort = 0.5; - g_pHyprOpenGL->m_globalTimer.reset(); + m_globalTimer.reset(); - static auto PDT = rc(g_pConfigManager->getConfigValuePtr("debug:damage_tracking")); - - **PDT = 0; + **rc(Config::mgr()->getConfigValue("debug:damage_tracking").dataptr) = 0; } -SP CHyprRenderer::getOrCreateRenderbuffer(SP buffer, uint32_t fmt) { +const SRenderData& IHyprRenderer::renderData() { + return m_renderData; +} + +SP IHyprRenderer::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 = getOrCreateRenderbufferInternal(buffer, fmt); if (!buf->good()) return nullptr; @@ -2281,180 +2924,40 @@ SP CHyprRenderer::getOrCreateRenderbuffer(SP return buf; } -void CHyprRenderer::makeEGLCurrent() { - if (!g_pCompositor || !g_pHyprOpenGL) - return; - - if (eglGetCurrentContext() != g_pHyprOpenGL->m_eglContext) - eglMakeCurrent(g_pHyprOpenGL->m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, g_pHyprOpenGL->m_eglContext); +bool IHyprRenderer::beginFullFakeRender(PHLMONITOR pMonitor, CRegion& damage, SP fb) { + return beginRender(pMonitor, damage, RENDER_MODE_FULL_FAKE, nullptr, fb, true); } -void CHyprRenderer::unsetEGL() { - if (!g_pHyprOpenGL) - return; - - eglMakeCurrent(g_pHyprOpenGL->m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); +bool IHyprRenderer::beginRenderToBuffer(PHLMONITOR pMonitor, CRegion& damage, SP buffer, bool simple) { + return beginRender(pMonitor, damage, RENDER_MODE_TO_BUFFER, buffer, nullptr, simple); } -bool CHyprRenderer::beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMode mode, SP buffer, CFramebuffer* fb, bool simple) { - - makeEGLCurrent(); - - m_renderPass.clear(); - - m_renderMode = mode; - - g_pHyprOpenGL->m_renderData.pMonitor = pMonitor; // has to be set cuz allocs - - if (mode == RENDER_MODE_FULL_FAKE) { - RASSERT(fb, "Cannot render FULL_FAKE without a provided fb!"); - fb->bind(); - if (simple) - g_pHyprOpenGL->beginSimple(pMonitor, damage, nullptr, fb); - else - g_pHyprOpenGL->begin(pMonitor, damage, fb); - return true; - } - - int bufferAge = 0; - - if (!buffer) { - m_currentBuffer = pMonitor->m_output->swapchain->next(&bufferAge); - if (!m_currentBuffer) { - Debug::log(ERR, "Failed to acquire swapchain buffer for {}", pMonitor->m_name); - return false; - } - } else - m_currentBuffer = buffer; - - try { - m_currentRenderbuffer = getOrCreateRenderbuffer(m_currentBuffer, pMonitor->m_output->state->state().drmFormat); - } catch (std::exception& e) { - Debug::log(ERR, "getOrCreateRenderbuffer failed for {}", pMonitor->m_name); - return false; - } - - if (!m_currentRenderbuffer) { - Debug::log(ERR, "failed to start a render pass for output {}, no RBO could be obtained", pMonitor->m_name); - return false; - } - - if (mode == RENDER_MODE_NORMAL) { - damage = pMonitor->m_damage.getBufferDamage(bufferAge); - pMonitor->m_damage.rotate(); - } - - m_currentRenderbuffer->bind(); - if (simple) - g_pHyprOpenGL->beginSimple(pMonitor, damage, m_currentRenderbuffer); - else - g_pHyprOpenGL->begin(pMonitor, damage); - - return true; -} - -void CHyprRenderer::endRender(const std::function& renderingDoneCallback) { - const auto PMONITOR = g_pHyprOpenGL->m_renderData.pMonitor; - static auto PNVIDIAANTIFLICKER = CConfigValue("opengl:nvidia_anti_flicker"); - - g_pHyprOpenGL->m_renderData.damage = m_renderPass.render(g_pHyprOpenGL->m_renderData.damage); - - auto cleanup = CScopeGuard([this]() { - if (m_currentRenderbuffer) - m_currentRenderbuffer->unbind(); - m_currentRenderbuffer = nullptr; - m_currentBuffer = nullptr; - }); - - if (m_renderMode != RENDER_MODE_TO_BUFFER_READ_ONLY) - g_pHyprOpenGL->end(); - else { - g_pHyprOpenGL->m_renderData.pMonitor.reset(); - g_pHyprOpenGL->m_renderData.mouseZoomFactor = 1.f; - g_pHyprOpenGL->m_renderData.mouseZoomUseMouse = true; - } - - if (m_renderMode == RENDER_MODE_FULL_FAKE) - return; - - if (m_renderMode == RENDER_MODE_NORMAL) - PMONITOR->m_output->state->setBuffer(m_currentBuffer); - - if (!g_pHyprOpenGL->explicitSyncSupported()) { - Debug::log(TRACE, "renderer: Explicit sync unsupported, falling back to implicit in endRender"); - - // nvidia doesn't have implicit sync, so we have to explicitly wait here, llvmpipe and other software renderer seems to bug out aswell. - if ((isNvidia() && *PNVIDIAANTIFLICKER) || isSoftware()) - glFinish(); - else - glFlush(); // mark an implicit sync point - - m_usedAsyncBuffers.clear(); // release all buffer refs and hope implicit sync works - if (renderingDoneCallback) - renderingDoneCallback(); - - return; - } - - UP eglSync = CEGLSync::create(); - if (eglSync && eglSync->isValid()) { - for (auto const& buf : m_usedAsyncBuffers) { - for (const auto& releaser : buf->m_syncReleasers) { - releaser->addSyncFileFd(eglSync->fd()); - } - } - - // release buffer refs with release points now, since syncReleaser handles actual buffer release based on EGLSync - std::erase_if(m_usedAsyncBuffers, [](const auto& buf) { return !buf->m_syncReleasers.empty(); }); - - // release buffer refs without release points when EGLSync sync_file/fence is signalled - g_pEventLoopManager->doOnReadable(eglSync->fd().duplicate(), [renderingDoneCallback, prevbfs = std::move(m_usedAsyncBuffers)]() mutable { - prevbfs.clear(); - if (renderingDoneCallback) - renderingDoneCallback(); - }); - m_usedAsyncBuffers.clear(); - - if (m_renderMode == RENDER_MODE_NORMAL) { - PMONITOR->m_inFence = eglSync->takeFd(); - PMONITOR->m_output->state->setExplicitInFence(PMONITOR->m_inFence.get()); - } - } else { - Debug::log(ERR, "renderer: Explicit sync failed, releasing resources"); - - m_usedAsyncBuffers.clear(); // release all buffer refs and hope implicit sync works - if (renderingDoneCallback) - renderingDoneCallback(); - } -} - -void CHyprRenderer::onRenderbufferDestroy(CRenderbuffer* rb) { +void IHyprRenderer::onRenderbufferDestroy(IRenderbuffer* rb) { std::erase_if(m_renderbuffers, [&](const auto& rbo) { return rbo.get() == rb; }); } -SP CHyprRenderer::getCurrentRBO() { - return m_currentRenderbuffer; -} - -bool CHyprRenderer::isNvidia() { +bool IHyprRenderer::isNvidia() { return m_nvidia; } -bool CHyprRenderer::isIntel() { +bool IHyprRenderer::isIntel() { return m_intel; } -bool CHyprRenderer::isSoftware() { +bool IHyprRenderer::isSoftware() { return m_software; } -bool CHyprRenderer::isMgpu() { +bool IHyprRenderer::isMgpu() { return m_mgpu; } -void CHyprRenderer::addWindowToRenderUnfocused(PHLWINDOW window) { +void IHyprRenderer::addWindowToRenderUnfocused(PHLWINDOW window) { static auto PFPS = CConfigValue("misc:render_unfocused_fps"); + if (*PFPS <= 0) + return; + if (std::ranges::find(m_renderUnfocused, window) != m_renderUnfocused.end()) return; @@ -2464,7 +2967,7 @@ void CHyprRenderer::addWindowToRenderUnfocused(PHLWINDOW window) { m_renderUnfocusedTimer->updateTimeout(std::chrono::milliseconds(1000 / *PFPS)); } -void CHyprRenderer::makeSnapshot(PHLWINDOW pWindow) { +void IHyprRenderer::makeSnapshot(PHLWINDOW pWindow) { // we trust the window is valid. const auto PMONITOR = pWindow->m_monitor.lock(); @@ -2474,7 +2977,7 @@ void CHyprRenderer::makeSnapshot(PHLWINDOW pWindow) { if (!shouldRenderWindow(pWindow)) return; // ignore, window is not being rendered - Debug::log(LOG, "renderer: making a snapshot of {:x}", rc(pWindow.get())); + Log::logger->log(Log::DEBUG, "renderer: making a snapshot of {:x}", rc(pWindow.get())); // we need to "damage" the entire monitor // so that we render the entire window @@ -2483,84 +2986,101 @@ void CHyprRenderer::makeSnapshot(PHLWINDOW pWindow) { PHLWINDOWREF ref{pWindow}; - makeEGLCurrent(); + if (!ref->m_snapshotFB) + ref->m_snapshotFB = createFB("window snapshot"); - const auto PFRAMEBUFFER = &g_pHyprOpenGL->m_windowFramebuffers[ref]; + const auto PFRAMEBUFFER = ref->m_snapshotFB; PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888); - beginRender(PMONITOR, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, PFRAMEBUFFER); + beginFullFakeRender(PMONITOR, fakeDamage, PFRAMEBUFFER); m_bRenderingSnapshot = true; - g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 0)); // JIC + draw(CClearPassElement::SClearData{CHyprColor(0, 0, 0, 0)}); + startRenderPass(); + + Log::logger->log(Log::DEBUG, "renderer: cleared a snapshot of {:x}", rc(pWindow.get())); renderWindow(pWindow, PMONITOR, Time::steadyNow(), !pWindow->m_X11DoesntWantBorders, RENDER_PASS_ALL); + Log::logger->log(Log::DEBUG, "renderer: rendered a snapshot of {:x}", rc(pWindow.get())); + endRender(); + Log::logger->log(Log::DEBUG, "renderer: made a snapshot of {:x}", rc(pWindow.get())); + m_bRenderingSnapshot = false; } -void CHyprRenderer::makeSnapshot(PHLLS pLayer) { +void IHyprRenderer::makeSnapshot(PHLLS pLayer) { // we trust the window is valid. const auto PMONITOR = pLayer->m_monitor.lock(); if (!PMONITOR || !PMONITOR->m_output || PMONITOR->m_pixelSize.x <= 0 || PMONITOR->m_pixelSize.y <= 0) return; - Debug::log(LOG, "renderer: making a snapshot of {:x}", rc(pLayer.get())); + Log::logger->log(Log::DEBUG, "renderer: making a snapshot of layer {:x}", rc(pLayer.get())); // we need to "damage" the entire monitor // so that we render the entire window // this is temporary, doesn't mess with the actual damage CRegion fakeDamage{0, 0, sc(PMONITOR->m_transformedSize.x), sc(PMONITOR->m_transformedSize.y)}; - makeEGLCurrent(); + if (!pLayer->m_snapshotFB) + pLayer->m_snapshotFB = createFB("layer snapshot"); - const auto PFRAMEBUFFER = &g_pHyprOpenGL->m_layerFramebuffers[pLayer]; + const auto PFRAMEBUFFER = pLayer->m_snapshotFB; PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888); - beginRender(PMONITOR, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, PFRAMEBUFFER); + beginFullFakeRender(PMONITOR, fakeDamage, PFRAMEBUFFER); m_bRenderingSnapshot = true; - g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 0)); // JIC + draw(CClearPassElement::SClearData{CHyprColor(0, 0, 0, 0)}); + startRenderPass(); + + Log::logger->log(Log::DEBUG, "renderer: cleared a snapshot of layer {:x}", rc(pLayer.get())); // draw the layer renderLayer(pLayer, PMONITOR, Time::steadyNow()); + Log::logger->log(Log::DEBUG, "renderer: rendered a snapshot of layer {:x}", rc(pLayer.get())); + endRender(); + Log::logger->log(Log::DEBUG, "renderer: made a snapshot of layer {:x}", rc(pLayer.get())); + m_bRenderingSnapshot = false; } -void CHyprRenderer::makeSnapshot(WP popup) { +void IHyprRenderer::makeSnapshot(WP popup) { // we trust the window is valid. const auto PMONITOR = popup->getMonitor(); if (!PMONITOR || !PMONITOR->m_output || PMONITOR->m_pixelSize.x <= 0 || PMONITOR->m_pixelSize.y <= 0) return; - if (!popup->m_wlSurface || !popup->m_wlSurface->resource() || !popup->m_mapped) + if (!popup->aliveAndVisible()) return; - Debug::log(LOG, "renderer: making a snapshot of {:x}", rc(popup.get())); + Log::logger->log(Log::DEBUG, "renderer: making a snapshot of {:x}", rc(popup.get())); CRegion fakeDamage{0, 0, PMONITOR->m_transformedSize.x, PMONITOR->m_transformedSize.y}; - makeEGLCurrent(); + if (!popup->m_snapshotFB) + popup->m_snapshotFB = createFB("popup shapshot"); - const auto PFRAMEBUFFER = &g_pHyprOpenGL->m_popupFramebuffers[popup]; + const auto PFRAMEBUFFER = popup->m_snapshotFB; PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888); - beginRender(PMONITOR, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, PFRAMEBUFFER); + beginFullFakeRender(PMONITOR, fakeDamage, PFRAMEBUFFER); m_bRenderingSnapshot = true; - g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 0)); // JIC + draw(CClearPassElement::SClearData{CHyprColor(0, 0, 0, 0)}); CSurfacePassElement::SRenderData renderdata; renderdata.pos = popup->coordsGlobal(); @@ -2571,7 +3091,7 @@ void CHyprRenderer::makeSnapshot(WP popup) { renderdata.popup = true; renderdata.blur = false; - popup->m_wlSurface->resource()->breadthfirst( + popup->wlSurface()->resource()->breadthfirst( [this, &renderdata](SP s, const Vector2D& offset, void* data) { if (!s->m_current.texture) return; @@ -2593,15 +3113,15 @@ void CHyprRenderer::makeSnapshot(WP popup) { m_bRenderingSnapshot = false; } -void CHyprRenderer::renderSnapshot(PHLWINDOW pWindow) { +void IHyprRenderer::renderSnapshot(PHLWINDOW pWindow) { static auto PDIMAROUND = CConfigValue("decoration:dim_around"); PHLWINDOWREF ref{pWindow}; - if (!g_pHyprOpenGL->m_windowFramebuffers.contains(ref)) + if (!ref->m_snapshotFB) return; - const auto FBDATA = &g_pHyprOpenGL->m_windowFramebuffers.at(ref); + const auto FBDATA = ref->m_snapshotFB; if (!FBDATA->getTexture()) return; @@ -2624,7 +3144,7 @@ void CHyprRenderer::renderSnapshot(PHLWINDOW pWindow) { if (*PDIMAROUND && pWindow->m_ruleApplicator->dimAround().valueOrDefault()) { CRectPassElement::SRectData data; - data.box = {0, 0, g_pHyprOpenGL->m_renderData.pMonitor->m_pixelSize.x, g_pHyprOpenGL->m_renderData.pMonitor->m_pixelSize.y}; + data.box = {0, 0, m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y}; data.color = CHyprColor(0, 0, 0, *PDIMAROUND * pWindow->m_alpha->value()); m_renderPass.add(makeUnique(data)); @@ -2640,7 +3160,7 @@ void CHyprRenderer::renderSnapshot(PHLWINDOW pWindow) { data.roundingPower = pWindow->roundingPower(); data.xray = pWindow->m_ruleApplicator->xray().valueOr(false); - m_renderPass.add(makeUnique(std::move(data))); + m_renderPass.add(makeUnique(data)); } CTexPassElement::SRenderData data; @@ -2653,11 +3173,11 @@ void CHyprRenderer::renderSnapshot(PHLWINDOW pWindow) { m_renderPass.add(makeUnique(std::move(data))); } -void CHyprRenderer::renderSnapshot(PHLLS pLayer) { - if (!g_pHyprOpenGL->m_layerFramebuffers.contains(pLayer)) +void IHyprRenderer::renderSnapshot(PHLLS pLayer) { + if (!pLayer->m_snapshotFB) return; - const auto FBDATA = &g_pHyprOpenGL->m_layerFramebuffers.at(pLayer); + const auto FBDATA = pLayer->m_snapshotFB; if (!FBDATA->getTexture()) return; @@ -2695,13 +3215,13 @@ void CHyprRenderer::renderSnapshot(PHLLS pLayer) { m_renderPass.add(makeUnique(std::move(data))); } -void CHyprRenderer::renderSnapshot(WP popup) { - if (!g_pHyprOpenGL->m_popupFramebuffers.contains(popup)) +void IHyprRenderer::renderSnapshot(WP popup) { + if (!popup->m_snapshotFB) return; static CConfigValue PBLURIGNOREA = CConfigValue("decoration:blur:popups_ignorealpha"); - const auto FBDATA = &g_pHyprOpenGL->m_popupFramebuffers.at(popup); + const auto FBDATA = popup->m_snapshotFB; if (!FBDATA->getTexture()) return; @@ -2724,13 +3244,28 @@ void CHyprRenderer::renderSnapshot(WP popup) { data.blur = SHOULD_BLUR; data.blurA = sqrt(popup->m_alpha->value()); // sqrt makes the blur fadeout more realistic. data.blockBlurOptimization = SHOULD_BLUR; // force no xray on this (popups never have xray) - if (SHOULD_BLUR) - data.ignoreAlpha = std::max(*PBLURIGNOREA, 0.01F); /* ignore the alpha 0 regions */ + if (SHOULD_BLUR) { + if (const auto PLAYER = popup->layerOwner(); PLAYER && PLAYER->m_ruleApplicator->ignoreAlpha().hasValue()) + data.ignoreAlpha = std::max(PLAYER->m_ruleApplicator->ignoreAlpha().valueOrDefault(), 0.01F); + else + data.ignoreAlpha = std::max(*PBLURIGNOREA, 0.01F); /* ignore the alpha 0 regions */ + } m_renderPass.add(makeUnique(std::move(data))); } -bool CHyprRenderer::shouldBlur(PHLLS ls) { +NColorManagement::PImageDescription IHyprRenderer::workBufferImageDescription() { + // 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->useFP16() ? + LINEAR_IMAGE_DESCRIPTION : + m_renderData.pMonitor->m_imageDescription; //CImageDescription::from(NColorManagement::SImageDescription{.transferFunction = CHOSEN_SDR_EOTF}); +} + +bool IHyprRenderer::shouldBlur(PHLLS ls) { if (m_bRenderingSnapshot) return false; @@ -2738,7 +3273,7 @@ bool CHyprRenderer::shouldBlur(PHLLS ls) { return *PBLUR && ls->m_ruleApplicator->blur().valueOrDefault(); } -bool CHyprRenderer::shouldBlur(PHLWINDOW w) { +bool IHyprRenderer::shouldBlur(PHLWINDOW w) { if (m_bRenderingSnapshot) return false; @@ -2747,9 +3282,70 @@ bool CHyprRenderer::shouldBlur(PHLWINDOW w) { return *PBLUR && !DONT_BLUR; } -bool CHyprRenderer::shouldBlur(WP p) { +bool IHyprRenderer::shouldBlur(WP p) { static CConfigValue PBLURPOPUPS = CConfigValue("decoration:blur:popups"); static CConfigValue PBLUR = CConfigValue("decoration:blur:enabled"); return *PBLURPOPUPS && *PBLUR; } + +SP IHyprRenderer::renderSplash(const std::function(const int, const int, unsigned char* const)>& handleData, const int fontSize, const int maxWidth, + const int maxHeight) { + static auto PSPLASHCOLOR = CConfigValue("misc:col.splash"); + static auto PSPLASHFONT = CConfigValue("misc:splash_font_family"); + static auto FALLBACKFONT = CConfigValue("misc:font_family"); + + const auto FONTFAMILY = *PSPLASHFONT != STRVAL_EMPTY ? *PSPLASHFONT : *FALLBACKFONT; + const auto COLOR = CHyprColor(*PSPLASHCOLOR); + + const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, maxWidth, maxHeight); + const auto CAIRO = cairo_create(CAIROSURFACE); + + cairo_set_antialias(CAIRO, CAIRO_ANTIALIAS_GOOD); + cairo_save(CAIRO); + cairo_set_source_rgba(CAIRO, 0, 0, 0, 0); + cairo_set_operator(CAIRO, CAIRO_OPERATOR_SOURCE); + cairo_paint(CAIRO); + cairo_restore(CAIRO); + + PangoLayout* layoutText = pango_cairo_create_layout(CAIRO); + PangoFontDescription* pangoFD = pango_font_description_new(); + + pango_font_description_set_family_static(pangoFD, FONTFAMILY.c_str()); + pango_font_description_set_absolute_size(pangoFD, fontSize * PANGO_SCALE); + pango_font_description_set_style(pangoFD, PANGO_STYLE_NORMAL); + pango_font_description_set_weight(pangoFD, PANGO_WEIGHT_NORMAL); + pango_layout_set_font_description(layoutText, pangoFD); + + cairo_set_source_rgba(CAIRO, COLOR.r, COLOR.g, COLOR.b, COLOR.a); + int textW = 0, textH = 0; + pango_layout_set_text(layoutText, g_pCompositor->m_currentSplash.c_str(), -1); + pango_layout_get_size(layoutText, &textW, &textH); + textW = std::ceil((float)textW / PANGO_SCALE + fontSize / 10.f); + textH = std::ceil((float)textH / PANGO_SCALE + fontSize / 10.f); + + cairo_move_to(CAIRO, 0, 0); + pango_cairo_show_layout(CAIRO, layoutText); + + pango_font_description_free(pangoFD); + g_object_unref(layoutText); + + cairo_surface_flush(CAIROSURFACE); + + const auto smallSurf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, textW, textH); + const auto small = cairo_create(smallSurf); + cairo_set_source_surface(small, CAIROSURFACE, 0, 0); + cairo_rectangle(small, 0, 0, textW, textH); + cairo_set_operator(small, CAIRO_OPERATOR_SOURCE); + cairo_fill(small); + cairo_surface_flush(smallSurf); + + auto tex = handleData(textW, textH, cairo_image_surface_get_data(smallSurf)); + + cairo_surface_destroy(smallSurf); + cairo_destroy(small); + + cairo_surface_destroy(CAIROSURFACE); + cairo_destroy(CAIRO); + return tex; +} diff --git a/src/render/Renderer.hpp b/src/render/Renderer.hpp index 1980984d2..f52b9e3a0 100644 --- a/src/render/Renderer.hpp +++ b/src/render/Renderer.hpp @@ -1,175 +1,308 @@ #pragma once #include "../defines.hpp" +#include +#include +#include +#include #include -#include "../helpers/Monitor.hpp" -#include "../desktop/LayerSurface.hpp" +#include #include "OpenGL.hpp" +#include "./SyncFDManager.hpp" +#include "./pass/Pass.hpp" +#include "./pass/BorderPassElement.hpp" +#include "./pass/ClearPassElement.hpp" +#include "./pass/FramebufferElement.hpp" +#include "./pass/RectPassElement.hpp" +#include "./pass/RendererHintsPassElement.hpp" +#include "./pass/ShadowPassElement.hpp" +#include "./pass/SurfacePassElement.hpp" +#include "./pass/TexPassElement.hpp" +#include "./pass/TextureMatteElement.hpp" +#include "types.hpp" +#include "../helpers/Monitor.hpp" +#include "../desktop/view/LayerSurface.hpp" #include "Renderbuffer.hpp" #include "../helpers/time/Timer.hpp" #include "../helpers/math/Math.hpp" #include "../helpers/time/Time.hpp" #include "../../protocols/cursor-shape-v1.hpp" +#include "desktop/view/Popup.hpp" +#include "Framebuffer.hpp" +#include "Texture.hpp" + +#include struct SMonitorRule; class CWorkspace; -class CWindow; class CInputPopup; class IHLBuffer; class CEventLoopTimer; - -enum eDamageTrackingModes : int8_t { - DAMAGE_TRACKING_INVALID = -1, - DAMAGE_TRACKING_NONE = 0, - DAMAGE_TRACKING_MONITOR, - DAMAGE_TRACKING_FULL, -}; - -enum eRenderPassMode : uint8_t { - RENDER_PASS_ALL = 0, - RENDER_PASS_MAIN, - RENDER_PASS_POPUP -}; - -enum eRenderMode : uint8_t { - RENDER_MODE_NORMAL = 0, - RENDER_MODE_FULL_FAKE = 1, - RENDER_MODE_TO_BUFFER = 2, - RENDER_MODE_TO_BUFFER_READ_ONLY = 3, -}; - class CToplevelExportProtocolManager; class CInputManager; struct SSessionLockSurface; - -struct SRenderWorkspaceUntilData { - PHLLS ls; - PHLWINDOW w; +namespace Screenshare { + class CScreenshareFrame; }; -class CHyprRenderer { - public: - CHyprRenderer(); - ~CHyprRenderer(); +namespace Render { + using CScopeGuard = Hyprutils::Utils::CScopeGuard; - void renderMonitor(PHLMONITOR pMonitor, bool commit = true); - void arrangeLayersForMonitor(const MONITORID&); - void damageSurface(SP, double, double, double scale = 1.0); - void damageWindow(PHLWINDOW, bool forceFull = false); - void damageBox(const CBox&, bool skipFrameSchedule = false); - void damageBox(const int& x, const int& y, const int& w, const int& h); - void damageRegion(const CRegion&); - void damageMonitor(PHLMONITOR); - void damageMirrorsWith(PHLMONITOR, const CRegion&); - bool shouldRenderWindow(PHLWINDOW, PHLMONITOR); - bool shouldRenderWindow(PHLWINDOW); - void ensureCursorRenderingMode(); - bool shouldRenderCursor(); - void setCursorHidden(bool hide); - void calculateUVForSurface(PHLWINDOW, SP, PHLMONITOR pMonitor, bool main = false, const Vector2D& projSize = {}, const Vector2D& projSizeUnscaled = {}, - bool fixMisalignedFSV1 = false); - std::tuple getRenderTimes(PHLMONITOR pMonitor); // avg max min - void renderLockscreen(PHLMONITOR pMonitor, const Time::steady_tp& now, const CBox& geometry); - void setCursorSurface(SP surf, int hotspotX, int hotspotY, bool force = false); - void setCursorFromName(const std::string& name, bool force = false); - void onRenderbufferDestroy(CRenderbuffer* rb); - SP getCurrentRBO(); - bool isNvidia(); - bool isIntel(); - bool isSoftware(); - bool isMgpu(); - void makeEGLCurrent(); - void unsetEGL(); - void addWindowToRenderUnfocused(PHLWINDOW window); - void makeSnapshot(PHLWINDOW); - void makeSnapshot(PHLLS); - void makeSnapshot(WP); - void renderSnapshot(PHLWINDOW); - void renderSnapshot(PHLLS); - void renderSnapshot(WP); + class IElementRenderer; + class CRenderPass; - // if RENDER_MODE_NORMAL, provided damage will be written to. - // otherwise, it will be the one used. - bool beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMode mode = RENDER_MODE_NORMAL, SP buffer = {}, CFramebuffer* fb = nullptr, bool simple = false); - void endRender(const std::function& renderingDoneCallback = {}); + class IHyprRenderer { + public: + IHyprRenderer(); + virtual ~IHyprRenderer(); - bool m_bBlockSurfaceFeedback = false; - bool m_bRenderingSnapshot = false; - PHLMONITORREF m_mostHzMonitor; - bool m_directScanoutBlocked = false; + enum eType : uint8_t { + RT_GL = 1, + RT_VK = 2, + }; - void setSurfaceScanoutMode(SP surface, PHLMONITOR monitor); // nullptr monitor resets - void initiateManualCrash(); + virtual eType type() = 0; + WP glBackend(); - bool m_crashingInProgress = false; - float m_crashingDistort = 0.5f; - wl_event_source* m_crashingLoop = nullptr; - wl_event_source* m_cursorTicker = nullptr; + void renderMonitor(PHLMONITOR pMonitor, bool commit = true); + void arrangeLayersForMonitor(const MONITORID&); + void damageSurface(SP, double, double, double scale = 1.0); + void damageWindow(PHLWINDOW, bool forceFull = false); + void damageBox(const CBox&, bool skipFrameSchedule = false); + void damageBox(const int& x, const int& y, const int& w, const int& h); + void damageRegion(const CRegion&); + void damageMonitor(PHLMONITOR); + void damageMirrorsWith(PHLMONITOR, const CRegion&); + bool shouldRenderWindow(PHLWINDOW, PHLMONITOR); + bool shouldRenderWindow(PHLWINDOW); + void ensureCursorRenderingMode(); + bool shouldRenderCursor(); + void setCursorHidden(bool hide); - std::vector m_usedAsyncBuffers; + std::tuple getRenderTimes(PHLMONITOR pMonitor); // avg max min + void ensureLockTexturesRendered(bool load); + 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(IRenderbuffer* rb); + bool isNvidia(); + bool isIntel(); + bool isSoftware(); + bool isMgpu(); + void addWindowToRenderUnfocused(PHLWINDOW window); + void makeSnapshot(PHLWINDOW); + void makeSnapshot(PHLLS); + void makeSnapshot(WP); + void renderSnapshot(PHLWINDOW); + void renderSnapshot(PHLLS); + void renderSnapshot(WP); + bool beginFullFakeRender(PHLMONITOR pMonitor, CRegion& damage, SP fb); + bool beginRenderToBuffer(PHLMONITOR pMonitor, CRegion& damage, SP buffer, bool simple = false); + virtual void startRenderPass() {}; + virtual void endRender(const std::function& renderingDoneCallback = {}) = 0; - struct { - int hotspotX = 0; - int hotspotY = 0; - wpCursorShapeDeviceV1Shape shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT; - wpCursorShapeDeviceV1Shape shapePrevious = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT; - CTimer switchedTimer; - std::optional> surf; - std::string name; - } m_lastCursorData; + NColorManagement::PImageDescription workBufferImageDescription(); + bool m_bBlockSurfaceFeedback = false; + bool m_bRenderingSnapshot = false; + PHLMONITORREF m_mostHzMonitor; + bool m_directScanoutBlocked = false; - CRenderPass m_renderPass = {}; + void setSurfaceScanoutMode(SP surface, PHLMONITOR monitor); // nullptr monitor resets - private: - void arrangeLayerArray(PHLMONITOR, const std::vector&, bool, CBox*); - void renderWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& now, const CBox& geometry); - void renderWorkspaceWindowsFullscreen(PHLMONITOR, PHLWORKSPACE, const Time::steady_tp&); // renders workspace windows (fullscreen) (tiled, floating, pinned, but no special) - void renderWorkspaceWindows(PHLMONITOR, PHLWORKSPACE, const Time::steady_tp&); // renders workspace windows (no fullscreen) (tiled, floating, pinned, but no special) - void renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& now, const Vector2D& translate = {0, 0}, const float& scale = 1.f); - void renderWindow(PHLWINDOW, PHLMONITOR, const Time::steady_tp&, bool, eRenderPassMode, bool ignorePosition = false, bool standalone = false); - void renderLayer(PHLLS, PHLMONITOR, const Time::steady_tp&, bool popups = false, bool lockscreen = false); - void renderSessionLockSurface(WP, PHLMONITOR, const Time::steady_tp&); - void renderDragIcon(PHLMONITOR, const Time::steady_tp&); - void renderIMEPopup(CInputPopup*, PHLMONITOR, const Time::steady_tp&); - void sendFrameEventsToWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& now); // sends frame displayed events but doesn't actually render anything - void renderSessionLockPrimer(PHLMONITOR pMonitor); - void renderSessionLockMissing(PHLMONITOR pMonitor); - void renderBackground(PHLMONITOR pMonitor); + void initiateManualCrash(); + const SRenderData& renderData(); - bool commitPendingAndDoExplicitSync(PHLMONITOR pMonitor); + bool m_crashingInProgress = false; + float m_crashingDistort = 0.5f; + wl_event_source* m_crashingLoop = nullptr; + wl_event_source* m_cursorTicker = nullptr; - bool shouldBlur(PHLLS ls); - bool shouldBlur(PHLWINDOW w); - bool shouldBlur(WP p); + std::vector m_usedAsyncBuffers; - bool m_cursorHidden = false; - bool m_cursorHiddenByCondition = false; - bool m_cursorHasSurface = false; - SP m_currentRenderbuffer = nullptr; - SP m_currentBuffer = nullptr; - eRenderMode m_renderMode = RENDER_MODE_NORMAL; - bool m_nvidia = false; - bool m_intel = false; - bool m_software = false; - bool m_mgpu = false; + struct { + int hotspotX = 0; + int hotspotY = 0; + wpCursorShapeDeviceV1Shape shape = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT; + wpCursorShapeDeviceV1Shape shapePrevious = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT; + CTimer switchedTimer; + std::optional> surf; + std::string name; + } m_lastCursorData; - struct { - bool hiddenOnTouch = false; - bool hiddenOnTimeout = false; - bool hiddenOnKeyboard = false; - } m_cursorHiddenConditions; + CRenderPass m_renderPass; - SP getOrCreateRenderbuffer(SP buffer, uint32_t fmt); - std::vector> m_renderbuffers; - std::vector m_renderUnfocused; - SP m_renderUnfocusedTimer; + SP renderSplash(const std::function(const int, const int, unsigned char* const)>& handleData, const int fontSize, const int maxWidth = 1024, + const int maxHeight = 1024); - friend class CHyprOpenGLImpl; - friend class CToplevelExportFrame; - friend class CInputManager; - friend class CPointerManager; - friend class CMonitor; - friend class CMonitorFrameScheduler; -}; + virtual SP getOrCreateRenderbuffer(SP buffer, uint32_t fmt); // TODO? move to protected and fix CPointerManager::renderHWCursorBuffer + bool commitPendingAndDoExplicitSync(PHLMONITOR pMonitor); // TODO? move to protected and fix CMonitorFrameScheduler::onPresented + SRenderData m_renderData; // TODO? move to protected and fix CRenderPass + SP m_screencopyDeniedTexture; // TODO? make readonly + uint m_failedAssetsNo = 0; // TODO? make readonly + bool m_reloadScreenShader = true; // at launch it can be set + CTimer m_globalTimer; -inline UP g_pHyprRenderer; + void draw(WP element, const CRegion& damage = {}); + void draw(const CBorderPassElement::SBorderData& data, const CRegion& damage = {}); + void draw(const CClearPassElement::SClearData& data, const CRegion& damage = {}); + void draw(const CFramebufferElement::SFramebufferElementData& data, const CRegion& damage = {}); + void draw(const CRectPassElement::SRectData& data, const CRegion& damage = {}); + void draw(const CRendererHintsPassElement::SData& data, const CRegion& damage = {}); + void draw(const CShadowPassElement::SShadowData& data, const CRegion& damage = {}); + void draw(const CSurfacePassElement::SRenderData& data, const CRegion& damage = {}); + void draw(const CTexPassElement::SRenderData& data, const CRegion& damage = {}); + void draw(const CTextureMatteElement::STextureMatteData& data, const CRegion& damage = {}); + virtual void bindFB(SP fb); + UP bindTempFB(SP fb); + virtual UP createSyncFDManager() = 0; + virtual WP elementRenderer() = 0; + virtual SP createStencilTexture(const int width, const int height) = 0; + virtual SP createTexture(bool opaque = false) = 0; + virtual SP createTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy = false, bool opaque = false) = 0; + virtual SP createTexture(const Aquamarine::SDMABUFAttrs&, bool opaque = false) = 0; + virtual SP createTexture(const int width, const int height, unsigned char* const) = 0; + virtual SP createTexture(cairo_surface_t* cairo) = 0; + virtual SP createTexture(std::span lut3D, size_t N) = 0; + virtual SP createTexture(const SP buffer, bool keepDataCopy = false); + virtual SP renderText(const std::string& text, CHyprColor col, int pt, bool italic = false, const std::string& fontFamily = "", int maxWidth = 0, + int weight = 400); + virtual SP renderText(Hyprgraphics::CTextResource::STextResourceData&& data); + SP loadAsset(const std::string& filename); + virtual bool shouldUseNewBlurOptimizations(PHLLS pLayer, PHLWINDOW pWindow); + virtual bool explicitSyncSupported() = 0; + virtual std::vector getDRMFormats() = 0; + virtual std::vector getDRMFormatModifiers(DRMFormat format) = 0; + virtual SP createFB(const std::string& name = "") = 0; + virtual void disableScissor() = 0; + virtual void blend(bool enabled) = 0; + virtual void drawShadow(const CBox& box, int round, float roundingPower, int range, CHyprColor color, float a) = 0; + virtual void setViewport(int x, int y, int width, int height) = 0; + + bool preBlurQueued(PHLMONITORREF pMonitor); + void pushMonitorTransformEnabled(bool enabled); + void popMonitorTransformEnabled(); + bool monitorTransformEnabled(); + + void setProjectionType(const Vector2D& fbSize); + void setProjectionType(eRenderProjectionType projectionType); + Mat3x3 getBoxProjection(const CBox& box, std::optional transform = std::nullopt); + Mat3x3 projectBoxToTarget(const CBox& box, std::optional transform = std::nullopt); + + SP blurMainFramebuffer(float a, CRegion* originalDamage); + virtual SP blurFramebuffer(SP source, float a, CRegion* originalDamage) = 0; + void preBlurForCurrentMonitor(CRegion* fakeDamage); + + SCMSettings getCMSettings(const NColorManagement::PImageDescription imageDescription, const NColorManagement::PImageDescription targetImageDescription, + SP surface = nullptr, bool modifySDR = false, float sdrMinLuminance = -1.0f, int sdrMaxLuminance = -1, + bool shouldUseSurface = false); + void clearCMSettingsCache(); + virtual bool reloadShaders(const std::string& path = "") = 0; + + protected: + virtual void renderOffToMain(SP off) = 0; + virtual SP getOrCreateRenderbufferInternal(SP buffer, uint32_t fmt) = 0; + void renderMirrored(); + void setDamage(const CRegion& damage_, std::optional finalDamage); + // 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); + + virtual bool beginRenderInternal(PHLMONITOR pMonitor, CRegion& damage, bool simple = false) { + return false; + }; + virtual bool beginFullFakeRenderInternal(PHLMONITOR pMonitor, CRegion& damage, SP fb, bool simple = false) { + return false; + }; + virtual void initRender() {}; + virtual bool initRenderBuffer(SP buffer, uint32_t fmt) { + return false; + }; + + SP getBackground(PHLMONITOR pMonitor); + virtual SP getBlurTexture(PHLMONITORREF pMonitor); + + struct SCMSettingsCacheEntry { + uint64_t srcDescId = 0, dstDescId = 0; + void* surfacePtr = nullptr; // read-only!! + bool modifySDR = false; + float sdrMinLuminance = -1.F; + int sdrMaxLuminance = -1; + SCMSettings settings; + }; + std::vector m_cmSettingsCache; + + SP m_lockDeadTexture; + SP m_lockDead2Texture; + SP m_lockTtyTextTexture; + bool m_monitorTransformEnabled = false; // do not modify directly + std::stack m_monitorTransformStack; + + void handleFullscreenSettings(PHLMONITOR pMonitor); + + // old private: + void arrangeLayerArray(PHLMONITOR, const std::vector&, bool, CBox*); + void renderWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& now, const CBox& geometry); + void renderWorkspaceWindowsFullscreen(PHLMONITOR, PHLWORKSPACE, const Time::steady_tp&); // renders workspace windows (fullscreen) (tiled, floating, pinned, but no special) + void renderWorkspaceWindows(PHLMONITOR, PHLWORKSPACE, const Time::steady_tp&); // renders workspace windows (no fullscreen) (tiled, floating, pinned, but no special) + void renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& now, const Vector2D& translate = {0, 0}, const float& scale = 1.f); + void renderWindow(PHLWINDOW, PHLMONITOR, const Time::steady_tp&, bool, eRenderPassMode, bool ignorePosition = false, bool standalone = false); + void renderLayer(PHLLS, PHLMONITOR, const Time::steady_tp&, bool popups = false, bool lockscreen = false); + void renderSessionLockSurface(WP, PHLMONITOR, const Time::steady_tp&); + void renderDragIcon(PHLMONITOR, const Time::steady_tp&); + void renderIMEPopup(CInputPopup*, PHLMONITOR, const Time::steady_tp&); + void sendFrameEventsToWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, + const Time::steady_tp& now); // sends frame displayed events but doesn't actually render anything + void renderSessionLockPrimer(PHLMONITOR pMonitor); + void renderSessionLockMissing(PHLMONITOR pMonitor); + void renderBackground(PHLMONITOR pMonitor); + void requestBackgroundResource(); + std::string resolveAssetPath(const std::string& file); + void initMissingAssetTexture(); + void initAssets(); + SP m_missingAssetTexture; + ASP m_backgroundResource; + bool m_backgroundResourceFailed = false; + + bool shouldBlur(PHLLS ls); + bool shouldBlur(PHLWINDOW w); + bool shouldBlur(WP p); + + bool m_cursorHidden = false; + bool m_cursorHiddenByCondition = false; + bool m_cursorHasSurface = false; + SP m_currentBuffer = nullptr; + eRenderMode m_renderMode = RENDER_MODE_NORMAL; + bool m_nvidia = false; + bool m_intel = false; + bool m_software = false; + bool m_mgpu = false; + + struct { + bool hiddenOnTouch = false; + bool hiddenOnTablet = false; + bool hiddenOnTimeout = false; + bool hiddenOnKeyboard = false; + } m_cursorHiddenConditions; + + std::vector> m_renderbuffers; + std::vector m_renderUnfocused; + SP m_renderUnfocusedTimer; + + friend class CRenderPass; + friend class Render::GL::CHyprOpenGLImpl; + friend class CToplevelExportFrame; + friend class Screenshare::CScreenshareFrame; + friend class CInputManager; + friend class CPointerManager; + friend class CMonitor; + friend class CMonitorFrameScheduler; + + private: + void bindOffMain(); + void bindBackOnMain(); + }; + +} + +inline UP g_pHyprRenderer; \ No newline at end of file diff --git a/src/render/Shader.cpp b/src/render/Shader.cpp index 5081d4c42..3359de660 100644 --- a/src/render/Shader.cpp +++ b/src/render/Shader.cpp @@ -1,8 +1,12 @@ #include "Shader.hpp" -#include "render/OpenGL.hpp" +#include "../errorOverlay/Overlay.hpp" +#include "../config/ConfigValue.hpp" +#include "OpenGL.hpp" #define EPSILON(x, y) (std::abs((x) - (y)) < 1e-5f) +using namespace Render::GL; + static bool compareFloat(auto a, auto b) { if (a.size() != b.size()) return false; @@ -14,51 +18,237 @@ static bool compareFloat(auto a, auto b) { return true; } -SShader::SShader() { - uniformLocations.fill(-1); +CShader::CShader() { + m_uniformLocations.fill(-1); } -SShader::~SShader() { +CShader::~CShader() { destroy(); } -void SShader::createVao() { - GLuint shaderVao = 0, shaderVbo = 0, shaderVboUv = 0; +void CShader::logShaderError(const GLuint& shader, bool program, bool silent) { + GLint maxLength = 0; + if (program) + glGetProgramiv(shader, GL_INFO_LOG_LENGTH, &maxLength); + else + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); + + std::vector errorLog(maxLength); + if (program) + glGetProgramInfoLog(shader, maxLength, &maxLength, errorLog.data()); + else + glGetShaderInfoLog(shader, maxLength, &maxLength, errorLog.data()); + std::string errorStr(errorLog.begin(), errorLog.end()); + + const auto FULLERROR = (program ? "Screen shader parser: Error linking program:" : "Screen shader parser: Error compiling shader: ") + errorStr; + + Log::logger->log(Log::ERR, "Failed to link shader: {}", FULLERROR); + + if (!silent) + ErrorOverlay::overlay()->queueError(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"); + m_uniformLocations[SHADER_WINDOW_TOP_LEFT] = getUniform("windowTopLeft"); + m_uniformLocations[SHADER_WINDOW_BOTTOM_RIGHT] = getUniform("windowBottomRight"); + + // compat for screenshaders + auto fullSize = getUniform("fullSize"); + if (fullSize == -1) + fullSize = getUniform("screen_size"); + if (fullSize == -1) + fullSize = getUniform("screenSize"); + m_uniformLocations[SHADER_FULL_SIZE] = fullSize; + + m_uniformLocations[SHADER_FULL_SIZE_UNTRANSFORMED] = getUniform("fullSizeUntransformed"); + m_uniformLocations[SHADER_RADIUS] = getUniform("radius"); + m_uniformLocations[SHADER_RADIUS_OUTER] = getUniform("radiusOuter"); + m_uniformLocations[SHADER_ROUNDING_POWER] = getUniform("roundingPower"); + m_uniformLocations[SHADER_THICK] = getUniform("thick"); + m_uniformLocations[SHADER_HALFPIXEL] = getUniform("halfpixel"); + m_uniformLocations[SHADER_RANGE] = getUniform("range"); + m_uniformLocations[SHADER_SHADOW_POWER] = getUniform("shadowPower"); + m_uniformLocations[SHADER_USE_ALPHA_MATTE] = getUniform("useAlphaMatte"); + m_uniformLocations[SHADER_APPLY_TINT] = getUniform("applyTint"); + m_uniformLocations[SHADER_TINT] = getUniform("tint"); + m_uniformLocations[SHADER_GRADIENT] = getUniform("gradient"); + m_uniformLocations[SHADER_GRADIENT_LENGTH] = getUniform("gradientLength"); + m_uniformLocations[SHADER_GRADIENT2] = getUniform("gradient2"); + m_uniformLocations[SHADER_GRADIENT2_LENGTH] = getUniform("gradient2Length"); + m_uniformLocations[SHADER_ANGLE] = getUniform("angle"); + m_uniformLocations[SHADER_ANGLE2] = getUniform("angle2"); + m_uniformLocations[SHADER_GRADIENT_LERP] = getUniform("gradientLerp"); + m_uniformLocations[SHADER_TIME] = getUniform("time"); + m_uniformLocations[SHADER_DISTORT] = getUniform("distort"); + m_uniformLocations[SHADER_WL_OUTPUT] = getUniform("wl_output"); + m_uniformLocations[SHADER_CONTRAST] = getUniform("contrast"); + m_uniformLocations[SHADER_PASSES] = getUniform("passes"); + m_uniformLocations[SHADER_VIBRANCY] = getUniform("vibrancy"); + m_uniformLocations[SHADER_VIBRANCY_DARKNESS] = getUniform("vibrancy_darkness"); + m_uniformLocations[SHADER_BRIGHTNESS] = getUniform("brightness"); + m_uniformLocations[SHADER_NOISE] = getUniform("noise"); + m_uniformLocations[SHADER_POINTER] = getUniform("pointer_position"); + m_uniformLocations[SHADER_POINTER_SHAPE] = getUniform("pointer_shape"); + m_uniformLocations[SHADER_POINTER_SWITCH_TIME] = getUniform("pointer_switch_time"); + m_uniformLocations[SHADER_POINTER_SHAPE_PREVIOUS] = getUniform("pointer_shape_previous"); + m_uniformLocations[SHADER_POINTER_PRESSED_POSITIONS] = getUniform("pointer_pressed_positions"); + m_uniformLocations[SHADER_POINTER_HIDDEN] = getUniform("pointer_hidden"); + m_uniformLocations[SHADER_POINTER_KILLING] = getUniform("pointer_killing"); + m_uniformLocations[SHADER_POINTER_PRESSED_TIMES] = getUniform("pointer_pressed_times"); + m_uniformLocations[SHADER_POINTER_PRESSED_KILLED] = getUniform("pointer_pressed_killed"); + m_uniformLocations[SHADER_POINTER_PRESSED_TOUCHED] = getUniform("pointer_pressed_touched"); + m_uniformLocations[SHADER_POINTER_INACTIVE_TIMEOUT] = getUniform("pointer_inactive_timeout"); + m_uniformLocations[SHADER_POINTER_LAST_ACTIVE] = getUniform("pointer_last_active"); + m_uniformLocations[SHADER_POINTER_SIZE] = getUniform("pointer_size"); +} + +void CShader::createVao() { + GLuint shaderVao = 0, shaderVbo = 0; glGenVertexArrays(1, &shaderVao); glBindVertexArray(shaderVao); - if (uniformLocations[SHADER_POS_ATTRIB] != -1) { + if (m_uniformLocations[SHADER_POS_ATTRIB] != -1) { glGenBuffers(1, &shaderVbo); glBindBuffer(GL_ARRAY_BUFFER, shaderVbo); - glBufferData(GL_ARRAY_BUFFER, sizeof(fullVerts), fullVerts, GL_STATIC_DRAW); - glEnableVertexAttribArray(uniformLocations[SHADER_POS_ATTRIB]); - glVertexAttribPointer(uniformLocations[SHADER_POS_ATTRIB], 2, GL_FLOAT, GL_FALSE, 0, nullptr); + glBufferData(GL_ARRAY_BUFFER, sizeof(fullVerts), fullVerts.data(), GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(m_uniformLocations[SHADER_POS_ATTRIB]); + glVertexAttribPointer(m_uniformLocations[SHADER_POS_ATTRIB], 2, GL_FLOAT, GL_FALSE, sizeof(SVertex), (void*)offsetof(SVertex, x)); } // UV VBO (dynamic, may be updated per frame) - if (uniformLocations[SHADER_TEX_ATTRIB] != -1) { - glGenBuffers(1, &shaderVboUv); - glBindBuffer(GL_ARRAY_BUFFER, shaderVboUv); - glBufferData(GL_ARRAY_BUFFER, sizeof(fullVerts), fullVerts, GL_DYNAMIC_DRAW); // Initial dummy UVs - glEnableVertexAttribArray(uniformLocations[SHADER_TEX_ATTRIB]); - glVertexAttribPointer(uniformLocations[SHADER_TEX_ATTRIB], 2, GL_FLOAT, GL_FALSE, 0, nullptr); + if (m_uniformLocations[SHADER_TEX_ATTRIB] != -1 && shaderVbo != 0) { + glBindBuffer(GL_ARRAY_BUFFER, shaderVbo); + glEnableVertexAttribArray(m_uniformLocations[SHADER_TEX_ATTRIB]); + glVertexAttribPointer(m_uniformLocations[SHADER_TEX_ATTRIB], 2, GL_FLOAT, GL_FALSE, sizeof(SVertex), (void*)offsetof(SVertex, u)); } glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); - uniformLocations[SHADER_SHADER_VAO] = shaderVao; - uniformLocations[SHADER_SHADER_VBO_POS] = shaderVbo; - uniformLocations[SHADER_SHADER_VBO_UV] = shaderVboUv; + m_uniformLocations[SHADER_SHADER_VAO] = shaderVao; + m_uniformLocations[SHADER_SHADER_VBO] = shaderVbo; - RASSERT(uniformLocations[SHADER_SHADER_VAO] >= 0, "SHADER_SHADER_VAO could not be created"); - RASSERT(uniformLocations[SHADER_SHADER_VBO_POS] >= 0, "SHADER_SHADER_VBO_POS could not be created"); - RASSERT(uniformLocations[SHADER_SHADER_VBO_UV] >= 0, "SHADER_SHADER_VBO_UV could not be created"); + RASSERT(m_uniformLocations[SHADER_SHADER_VAO] >= 0, "SHADER_SHADER_VAO could not be created"); + RASSERT(m_uniformLocations[SHADER_SHADER_VBO] >= 0, "SHADER_SHADER_VBO_POS could not be created"); } -void SShader::setUniformInt(eShaderUniform location, GLint v0) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformInt(eShaderUniform location, GLint v0) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -67,11 +257,12 @@ void SShader::setUniformInt(eShaderUniform location, GLint v0) { return; cached = v0; - glUniform1i(uniformLocations[location], v0); + + GLCALL(glUniform1i(m_uniformLocations[location], v0)); } -void SShader::setUniformFloat(eShaderUniform location, GLfloat v0) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformFloat(eShaderUniform location, GLfloat v0) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -83,11 +274,11 @@ void SShader::setUniformFloat(eShaderUniform location, GLfloat v0) { } cached = v0; - glUniform1f(uniformLocations[location], v0); + GLCALL(glUniform1f(m_uniformLocations[location], v0)); } -void SShader::setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -99,11 +290,11 @@ void SShader::setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1) } cached = std::array{v0, v1}; - glUniform2f(uniformLocations[location], v0, v1); + GLCALL(glUniform2f(m_uniformLocations[location], v0, v1)); } -void SShader::setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -115,11 +306,11 @@ void SShader::setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, } cached = std::array{v0, v1, v2}; - glUniform3f(uniformLocations[location], v0, v1, v2); + GLCALL(glUniform3f(m_uniformLocations[location], v0, v1, v2)); } -void SShader::setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -131,11 +322,11 @@ void SShader::setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, } cached = std::array{v0, v1, v2, v3}; - glUniform4f(uniformLocations[location], v0, v1, v2, v3); + GLCALL(glUniform4f(m_uniformLocations[location], v0, v1, v2, v3)); } -void SShader::setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -147,11 +338,11 @@ void SShader::setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLbool } cached = SUniformMatrix3Data{.count = count, .transpose = transpose, .value = value}; - glUniformMatrix3fv(uniformLocations[location], count, transpose, value.data()); + GLCALL(glUniformMatrix3fv(m_uniformLocations[location], count, transpose, value.data())); } -void SShader::setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -163,11 +354,11 @@ void SShader::setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLbo } cached = SUniformMatrix4Data{.count = count, .transpose = transpose, .value = value}; - glUniformMatrix4x2fv(uniformLocations[location], count, transpose, value.data()); + GLCALL(glUniformMatrix4x2fv(m_uniformLocations[location], count, transpose, value.data())); } -void SShader::setUniformfv(eShaderUniform location, GLsizei count, const std::vector& value, GLsizei vec_size) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformfv(eShaderUniform location, GLsizei count, const std::vector& value, GLsizei vec_size) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -180,36 +371,35 @@ void SShader::setUniformfv(eShaderUniform location, GLsizei count, const std::ve cached = SUniformVData{.count = count, .value = value}; switch (vec_size) { - case 1: glUniform1fv(uniformLocations[location], count, value.data()); break; - case 2: glUniform2fv(uniformLocations[location], count, value.data()); break; - case 4: glUniform4fv(uniformLocations[location], count, value.data()); break; + case 1: GLCALL(glUniform1fv(m_uniformLocations[location], count, value.data())); break; + case 2: GLCALL(glUniform2fv(m_uniformLocations[location], count, value.data())); break; + case 4: GLCALL(glUniform4fv(m_uniformLocations[location], count, value.data())); break; default: UNREACHABLE(); } } -void SShader::setUniform1fv(eShaderUniform location, GLsizei count, const std::vector& value) { +void CShader::setUniform1fv(eShaderUniform location, GLsizei count, const std::vector& value) { setUniformfv(location, count, value, 1); } -void SShader::setUniform2fv(eShaderUniform location, GLsizei count, const std::vector& value) { +void CShader::setUniform2fv(eShaderUniform location, GLsizei count, const std::vector& value) { setUniformfv(location, count, value, 2); } -void SShader::setUniform4fv(eShaderUniform location, GLsizei count, const std::vector& value) { +void CShader::setUniform4fv(eShaderUniform location, GLsizei count, const std::vector& value) { setUniformfv(location, count, value, 4); } -void SShader::destroy() { +void CShader::destroy() { uniformStatus.fill(std::monostate()); - if (program == 0) + if (m_program == 0) return; - GLuint shaderVao, shaderVbo, shaderVboUv; + GLuint shaderVao, shaderVbo; - shaderVao = uniformLocations[SHADER_SHADER_VAO] == -1 ? 0 : uniformLocations[SHADER_SHADER_VAO]; - shaderVbo = uniformLocations[SHADER_SHADER_VBO_POS] == -1 ? 0 : uniformLocations[SHADER_SHADER_VBO_POS]; - shaderVboUv = uniformLocations[SHADER_SHADER_VBO_UV] == -1 ? 0 : uniformLocations[SHADER_SHADER_VBO_UV]; + shaderVao = m_uniformLocations[SHADER_SHADER_VAO] == -1 ? 0 : m_uniformLocations[SHADER_SHADER_VAO]; + shaderVbo = m_uniformLocations[SHADER_SHADER_VBO] == -1 ? 0 : m_uniformLocations[SHADER_SHADER_VBO]; if (shaderVao) glDeleteVertexArrays(1, &shaderVao); @@ -217,9 +407,22 @@ void SShader::destroy() { if (shaderVbo) glDeleteBuffers(1, &shaderVbo); - if (shaderVboUv) - glDeleteBuffers(1, &shaderVboUv); - - glDeleteProgram(program); - program = 0; + glDeleteProgram(m_program); + m_program = 0; +} + +GLint CShader::getUniformLocation(eShaderUniform location) const { + return m_uniformLocations[location]; +} + +GLuint CShader::program() const { + return m_program; +} + +int CShader::getInitialTime() const { + return m_initialTime; +} + +void CShader::setInitialTime(int time) { + m_initialTime = time; } diff --git a/src/render/Shader.hpp b/src/render/Shader.hpp index 4f5456426..84a4014cd 100644 --- a/src/render/Shader.hpp +++ b/src/render/Shader.hpp @@ -9,13 +9,13 @@ enum eShaderUniform : uint8_t { SHADER_COLOR, SHADER_ALPHA_MATTE, SHADER_TEX_TYPE, - SHADER_SKIP_CM, SHADER_SOURCE_TF, SHADER_TARGET_TF, SHADER_SRC_TF_RANGE, SHADER_DST_TF_RANGE, - SHADER_TARGET_PRIMARIES, + SHADER_TARGET_PRIMARIES_XYZ, SHADER_MAX_LUMINANCE, + SHADER_SRC_REF_LUMINANCE, SHADER_DST_MAX_LUMINANCE, SHADER_DST_REF_LUMINANCE, SHADER_SDR_SATURATION, @@ -30,10 +30,11 @@ enum eShaderUniform : uint8_t { SHADER_DISCARD_ALPHA, SHADER_DISCARD_ALPHA_VALUE, SHADER_SHADER_VAO, - SHADER_SHADER_VBO_POS, - SHADER_SHADER_VBO_UV, + SHADER_SHADER_VBO, SHADER_TOP_LEFT, SHADER_BOTTOM_RIGHT, + SHADER_WINDOW_TOP_LEFT, + SHADER_WINDOW_BOTTOM_RIGHT, SHADER_FULL_SIZE, SHADER_FULL_SIZE_UNTRANSFORMED, SHADER_RADIUS, @@ -75,19 +76,41 @@ enum eShaderUniform : uint8_t { SHADER_POINTER_INACTIVE_TIMEOUT, SHADER_POINTER_LAST_ACTIVE, SHADER_POINTER_SIZE, + SHADER_LUT_3D, + SHADER_LUT_SIZE, + SHADER_BLURRED_BG, + SHADER_UV_SIZE, + SHADER_UV_OFFSET, SHADER_LAST, }; -struct SShader { - SShader(); - ~SShader(); +class CShader { + public: + CShader(); + ~CShader(); - GLuint program = 0; + bool createProgram(const std::string& vert, const std::string& frag, bool dynamic = false, bool silent = false); + void setUniformInt(eShaderUniform location, GLint v0); + void setUniformFloat(eShaderUniform location, GLfloat v0); + void setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1); + void setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2); + void setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); + void setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value); + void setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value); + void setUniform1fv(eShaderUniform location, GLsizei count, const std::vector& value); + void setUniform2fv(eShaderUniform location, GLsizei count, const std::vector& value); + void setUniform4fv(eShaderUniform location, GLsizei count, const std::vector& value); + void destroy(); + GLuint program() const; + GLint getUniformLocation(eShaderUniform location) const; + int getInitialTime() const; + void setInitialTime(int time); - std::array uniformLocations; - - float initialTime = 0; + private: + GLuint m_program = 0; + float m_initialTime = 0; + std::array m_uniformLocations; struct SUniformMatrix3Data { GLsizei count = 0; @@ -113,19 +136,9 @@ struct SShader { uniformStatus; // - void createVao(); - void setUniformInt(eShaderUniform location, GLint v0); - void setUniformFloat(eShaderUniform location, GLfloat v0); - void setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1); - void setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2); - void setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); - void setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value); - void setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value); - void setUniform1fv(eShaderUniform location, GLsizei count, const std::vector& value); - void setUniform2fv(eShaderUniform location, GLsizei count, const std::vector& value); - void setUniform4fv(eShaderUniform location, GLsizei count, const std::vector& value); - void destroy(); - - private: - void setUniformfv(eShaderUniform location, GLsizei count, const std::vector& value, GLsizei vec_size); + void logShaderError(const GLuint&, bool program = false, bool silent = false); + GLuint compileShader(const GLuint&, std::string, bool dynamic = false, bool silent = false); + void getUniformLocations(); + void createVao(); + void setUniformfv(eShaderUniform location, GLsizei count, const std::vector& value, GLsizei vec_size); }; diff --git a/src/render/ShaderLoader.cpp b/src/render/ShaderLoader.cpp new file mode 100644 index 000000000..388c1d432 --- /dev/null +++ b/src/render/ShaderLoader.cpp @@ -0,0 +1,178 @@ +#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"}, + {"USE_MIRROR", features & SH_FEAT_MIRROR ? "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"; + } + + glslang_shader_delete(shader); + return code; +} + +std::string CShaderLoader::process(const std::string& filename) { + auto source = loadShader(filename); + return processSource(source, filename.ends_with(".vert") ? GLSLANG_STAGE_VERTEX : GLSLANG_STAGE_FRAGMENT); +} + +std::string CShaderLoader::process(const std::string& filename, const std::map& defines) { + m_overrideDefines = ""; + for (const auto& [name, value] : defines) { + m_overrideDefines += std::format("#define {} {}\n", name, value); + } + const auto& res = process(filename); + m_overrideDefines = ""; + return res; +} + +std::string CShaderLoader::getVariantSource(ePreparedFragmentShader frag, ShaderFeatureFlags features) { + static const auto PCM = CConfigValue("render:cm_enabled"); + if (!*PCM) + features &= ~(SH_FEAT_CM | SH_FEAT_TONEMAP | SH_FEAT_SDR_MOD); + + if (!m_fragVariants[frag].contains(features)) { + ASSERT(m_fragFiles[frag].length()); + m_overrideDefines = getDefines(features); + m_fragVariants[frag][features] = processSource(m_fragFiles[frag]); + m_overrideDefines = ""; + } + + return m_fragVariants[frag][features]; +} + +const std::map& CShaderLoader::includes() { + return m_includes; +} + +// TODO notify user if bundled shader is newer than ~/.config override +std::string CShaderLoader::loadShader(const std::string& filename) { + if (m_shaderPath.length()) { + std::filesystem::path path = m_shaderPath; + const auto src = NFsUtils::readFileAsString(path / filename); + if (src.has_value()) + return src.value(); + } + const auto home = Hyprutils::Path::getHome(); + if (home.has_value()) { + const auto src = NFsUtils::readFileAsString(home.value() + "/hypr/shaders/" + filename); + if (src.has_value()) + return src.value(); + } + for (auto& e : ASSET_PATHS) { + const auto src = NFsUtils::readFileAsString(std::string{e} + "/hypr/shaders/" + filename); + if (src.has_value()) + return src.value(); + } + if (SHADERS.contains(filename)) + return SHADERS.at(filename); + throw std::runtime_error(std::format("Couldn't load shader {}", filename)); +} diff --git a/src/render/ShaderLoader.hpp b/src/render/ShaderLoader.hpp new file mode 100644 index 000000000..58184659c --- /dev/null +++ b/src/render/ShaderLoader.hpp @@ -0,0 +1,79 @@ +#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), // + SH_FEAT_MIRROR = (1 << 9), // condition: mirror or screenshare + + // 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_INNER_GLOW, + 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/SyncFDManager.cpp b/src/render/SyncFDManager.cpp new file mode 100644 index 000000000..3895c4060 --- /dev/null +++ b/src/render/SyncFDManager.cpp @@ -0,0 +1,16 @@ +#include "SyncFDManager.hpp" +#include + +using namespace Render; + +Hyprutils::OS::CFileDescriptor& ISyncFDManager::fd() { + return m_fd; +} + +Hyprutils::OS::CFileDescriptor&& ISyncFDManager::takeFd() { + return std::move(m_fd); +} + +bool ISyncFDManager::isValid() { + return m_valid && m_fd.isValid(); +} diff --git a/src/render/SyncFDManager.hpp b/src/render/SyncFDManager.hpp new file mode 100644 index 000000000..5996fbeb7 --- /dev/null +++ b/src/render/SyncFDManager.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace Render { + class ISyncFDManager { + public: + virtual ~ISyncFDManager() = default; + + Hyprutils::OS::CFileDescriptor& fd(); + Hyprutils::OS::CFileDescriptor&& takeFd(); + virtual bool isValid(); + + protected: + ISyncFDManager() = default; + + Hyprutils::OS::CFileDescriptor m_fd; + bool m_valid = false; + }; +} diff --git a/src/render/Texture.cpp b/src/render/Texture.cpp index 17c416c73..1201f0445 100644 --- a/src/render/Texture.cpp +++ b/src/render/Texture.cpp @@ -1,207 +1,26 @@ #include "Texture.hpp" -#include "Renderer.hpp" -#include "../Compositor.hpp" -#include "../protocols/types/Buffer.hpp" -#include "../helpers/Format.hpp" #include -CTexture::CTexture() = default; +using namespace Render; -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) { - Debug::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) { - Debug::log(ERR, "Cannot create a texture: failed to create an EGLImage"); - return; - } - - createFromDma(attrs, image); -} - -void CTexture::createFromShm(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size_) { - g_pHyprRenderer->makeEGLCurrent(); - - const auto format = NFormatUtils::getPixelFormatFromDRM(drmFormat); - ASSERT(format); - - m_type = format->withAlpha ? TEXTURE_RGBA : TEXTURE_RGBX; - m_size = size_; - m_isSynchronous = true; - m_target = GL_TEXTURE_2D; - allocate(); - bind(); - setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - if (format->flipRB) { - setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); - setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); - } - - GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / format->bytesPerBlock)); - GLCALL(glTexImage2D(GL_TEXTURE_2D, 0, format->glInternalFormat ? format->glInternalFormat : format->glFormat, size_.x, size_.y, 0, format->glFormat, format->glType, pixels)); - GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0)); - unbind(); - - if (m_keepDataCopy) { - m_dataCopy.resize(stride * size_.y); - memcpy(m_dataCopy.data(), pixels, stride * size_.y); +ITexture::ITexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy, bool opaque) : + m_size(size), m_opaque(opaque), m_drmFormat(drmFormat), m_keepDataCopy(keepDataCopy) { + if (m_keepDataCopy && stride && pixels) { + m_dataCopy.resize(stride * size.y); + memcpy(m_dataCopy.data(), pixels, stride * size.y); } } -void CTexture::createFromDma(const Aquamarine::SDMABUFAttrs& attrs, void* image) { - if (!g_pHyprOpenGL->m_proc.glEGLImageTargetTexture2DOES) { - Debug::log(ERR, "Cannot create a dmabuf texture: no glEGLImageTargetTexture2DOES"); - return; - } +ITexture::ITexture(std::span lut3D, size_t N) : m_type(TEXTURE_3D_LUT), m_size(lut3D.size() / 3, 1), m_isSynchronous(true) {} - m_opaque = NFormatUtils::isFormatOpaque(attrs.format); - m_target = GL_TEXTURE_2D; - m_type = TEXTURE_RGBA; - m_size = attrs.size; - m_type = NFormatUtils::isFormatOpaque(attrs.format) ? TEXTURE_RGBX : TEXTURE_RGBA; - allocate(); - m_eglImage = image; - - bind(); - setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - GLCALL(g_pHyprOpenGL->m_proc.glEGLImageTargetTexture2DOES(m_target, image)); - unbind(); +bool ITexture::ok() { + return false; } -void CTexture::update(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const CRegion& damage) { - if (damage.empty()) - return; - - g_pHyprRenderer->makeEGLCurrent(); - - const auto format = NFormatUtils::getPixelFormatFromDRM(drmFormat); - ASSERT(format); - - bind(); - - if (format->flipRB) { - setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); - setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); - } - - damage.copy().intersect(CBox{{}, m_size}).forEachRect([&format, &stride, &pixels](const auto& rect) { - GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / format->bytesPerBlock)); - GLCALL(glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, rect.x1)); - GLCALL(glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, rect.y1)); - - int width = rect.x2 - rect.x1; - int height = rect.y2 - rect.y1; - GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x1, rect.y1, width, height, format->glFormat, format->glType, pixels)); - }); - - GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0)); - GLCALL(glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0)); - GLCALL(glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0)); - - unbind(); - - if (m_keepDataCopy) { - m_dataCopy.resize(stride * m_size.y); - memcpy(m_dataCopy.data(), pixels, stride * m_size.y); - } +bool ITexture::isDMA() { + return false; } -void CTexture::destroyTexture() { - if (m_texID) { - GLCALL(glDeleteTextures(1, &m_texID)); - m_texID = 0; - } - - if (m_eglImage) - g_pHyprOpenGL->m_proc.eglDestroyImageKHR(g_pHyprOpenGL->m_eglDisplay, m_eglImage); - m_eglImage = nullptr; - m_cachedStates.fill(std::nullopt); -} - -void CTexture::allocate() { - if (!m_texID) - GLCALL(glGenTextures(1, &m_texID)); -} - -const std::vector& CTexture::dataCopy() { +const std::vector& ITexture::dataCopy() { return m_dataCopy; } - -void CTexture::bind() { - GLCALL(glBindTexture(m_target, m_texID)); -} - -void CTexture::unbind() { - GLCALL(glBindTexture(m_target, 0)); -} - -constexpr std::optional CTexture::getCacheStateIndex(GLenum pname) { - switch (pname) { - case GL_TEXTURE_WRAP_S: return TEXTURE_PAR_WRAP_S; - case GL_TEXTURE_WRAP_T: return TEXTURE_PAR_WRAP_T; - case GL_TEXTURE_MAG_FILTER: return TEXTURE_PAR_MAG_FILTER; - case GL_TEXTURE_MIN_FILTER: return TEXTURE_PAR_MIN_FILTER; - case GL_TEXTURE_SWIZZLE_R: return TEXTURE_PAR_SWIZZLE_R; - case GL_TEXTURE_SWIZZLE_B: return TEXTURE_PAR_SWIZZLE_B; - default: return std::nullopt; - } -} - -void CTexture::setTexParameter(GLenum pname, GLint param) { - const auto cacheIndex = getCacheStateIndex(pname); - - if (!cacheIndex) { - GLCALL(glTexParameteri(m_target, pname, param)); - return; - } - - const auto idx = cacheIndex.value(); - - if (m_cachedStates[idx] == param) - return; - - m_cachedStates[idx] = param; - GLCALL(glTexParameteri(m_target, pname, param)); -} diff --git a/src/render/Texture.hpp b/src/render/Texture.hpp index b9811230a..473e5ad92 100644 --- a/src/render/Texture.hpp +++ b/src/render/Texture.hpp @@ -1,69 +1,64 @@ #pragma once #include "../defines.hpp" +#include "../helpers/cm/ColorManagement.hpp" #include #include +#include class IHLBuffer; HYPRUTILS_FORWARD(Math, CRegion); -enum eTextureType : int8_t { - TEXTURE_INVALID = -1, // Invalid - TEXTURE_RGBA = 0, // 4 channels - TEXTURE_RGBX, // discard A - TEXTURE_EXTERNAL, // EGLImage -}; - -class CTexture { - public: - CTexture(); - - CTexture(CTexture&) = delete; - CTexture(CTexture&&) = delete; - CTexture(const CTexture&&) = delete; - CTexture(const CTexture&) = delete; - - 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; - 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; - - 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, +namespace Render { + 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 }; - 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); + class ITexture { + public: + ITexture(ITexture&) = delete; + ITexture(ITexture&&) = delete; + ITexture(const ITexture&&) = delete; + ITexture(const ITexture&) = delete; - bool m_keepDataCopy = false; - std::vector m_dataCopy; - std::array, TEXTURE_PAR_LAST> m_cachedStates; -}; + virtual ~ITexture() = default; + + 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(); + + const std::vector& dataCopy(); + + eTextureType m_type = TEXTURE_RGBA; + Vector2D m_size = {}; + eTransform m_transform = HYPRUTILS_TRANSFORM_NORMAL; + bool m_opaque = false; + + uint32_t m_drmFormat = 0; // for shm + bool m_isSynchronous = false; + + // CM + NColorManagement::PImageDescription m_imageDescription; + + // TODO move to GLTexture + GLuint m_texID = 0; + GLenum magFilter = GL_LINEAR; // useNearestNeighbor overwrites these + GLenum minFilter = GL_LINEAR; + + protected: + ITexture() = default; + ITexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy = false, bool opaque = false); + ITexture(std::span lut3D, size_t N); + + bool m_keepDataCopy = false; + std::vector m_dataCopy; + }; +} diff --git a/src/render/Transformer.hpp b/src/render/Transformer.hpp index 048b18985..bebf55003 100644 --- a/src/render/Transformer.hpp +++ b/src/render/Transformer.hpp @@ -14,7 +14,7 @@ class IWindowTransformer { // called by Hyprland. For more data about what is being rendered, inspect render data. // returns the out fb. - virtual CFramebuffer* transform(CFramebuffer* in) = 0; + virtual SP transform(SP 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 a082f0738..603d86357 100644 --- a/src/render/decorations/CHyprBorderDecoration.cpp +++ b/src/render/decorations/CHyprBorderDecoration.cpp @@ -4,7 +4,6 @@ #include "../../managers/eventLoop/EventLoopManager.hpp" #include "../pass/BorderPassElement.hpp" #include "../Renderer.hpp" -#include "../../managers/HookSystemManager.hpp" CHyprBorderDecoration::CHyprBorderDecoration(PHLWINDOW pWindow) : IHyprWindowDecoration(pWindow), m_window(pWindow) { ; @@ -85,6 +84,7 @@ void CHyprBorderDecoration::draw(PHLMONITOR pMonitor, float const& a) { data.roundingPower = ROUNDINGPOWER; data.a = a; data.borderSize = borderSize; + data.window = m_window; if (ANIMATED) { data.hasGrad2 = true; @@ -115,30 +115,26 @@ void CHyprBorderDecoration::updateWindow(PHLWINDOW) { } void CHyprBorderDecoration::damageEntire() { - if (!validMapped(m_window)) + if (!validMapped(m_window) || m_window->m_fullscreenState.internal == FSMODE_FULLSCREEN) return; - auto surfaceBox = m_window->getWindowMainSurfaceBox(); - const auto ROUNDING = m_window->rounding(); - const auto ROUNDINGSIZE = ROUNDING - M_SQRT1_2 * ROUNDING + 2; - const auto BORDERSIZE = m_window->getRealBorderSize() + 1; + const auto GLOBAL_BOX = assignedBoxGlobal(); + const auto ROUNDING = m_window->rounding(); + const auto BORDERSIZE = m_window->getRealBorderSize() + 1; - const auto PWINDOWWORKSPACE = m_window->m_workspace; - if (PWINDOWWORKSPACE && PWINDOWWORKSPACE->m_renderOffset->isBeingAnimated() && !m_window->m_pinned) - surfaceBox.translate(PWINDOWWORKSPACE->m_renderOffset->value()); - surfaceBox.translate(m_window->m_floatingOffset); + CRegion borderRegion(GLOBAL_BOX); + borderRegion.subtract(GLOBAL_BOX.copy().expand(-(BORDERSIZE + ROUNDING))); + borderRegion.expand(2); // pad - CBox surfaceBoxExpandedBorder = surfaceBox; - surfaceBoxExpandedBorder.expand(BORDERSIZE); - CBox surfaceBoxShrunkRounding = surfaceBox; - surfaceBoxShrunkRounding.expand(-ROUNDINGSIZE); - - CRegion borderRegion(surfaceBoxExpandedBorder); - borderRegion.subtract(surfaceBoxShrunkRounding); + const CBox borderExtents = borderRegion.getExtents(); for (auto const& m : g_pCompositor->m_monitors) { + const CBox monitorBox = {m->m_position, m->m_size}; + if (borderExtents.intersection(monitorBox).empty()) + continue; + if (!g_pHyprRenderer->shouldRenderWindow(m_window.lock(), m)) { - const CRegion monitorRegion({m->m_position, m->m_size}); + const CRegion monitorRegion(monitorBox); borderRegion.subtract(monitorRegion); } } diff --git a/src/render/decorations/CHyprDropShadowDecoration.cpp b/src/render/decorations/CHyprDropShadowDecoration.cpp index dd82abc53..3085facbd 100644 --- a/src/render/decorations/CHyprDropShadowDecoration.cpp +++ b/src/render/decorations/CHyprDropShadowDecoration.cpp @@ -4,6 +4,8 @@ #include "../../config/ConfigValue.hpp" #include "../pass/ShadowPassElement.hpp" #include "../Renderer.hpp" +#include "../pass/RectPassElement.hpp" +#include "../pass/TextureMatteElement.hpp" CHyprDropShadowDecoration::CHyprDropShadowDecoration(PHLWINDOW pWindow) : IHyprWindowDecoration(pWindow), m_window(pWindow) { ; @@ -56,17 +58,7 @@ void CHyprDropShadowDecoration::damageEntire() { applyOffset(shadowBox); - static auto PSHADOWIGNOREWINDOW = CConfigValue("decoration:shadow:ignore_window"); - const auto ROUNDING = PWINDOW->rounding(); - const auto ROUNDINGSIZE = ROUNDING - M_SQRT1_2 * ROUNDING + 1; - - CRegion shadowRegion(shadowBox); - if (*PSHADOWIGNOREWINDOW) { - CBox surfaceBox = PWINDOW->getWindowMainSurfaceBox(); - applyOffset(surfaceBox); - surfaceBox.expand(-ROUNDINGSIZE); - shadowRegion.subtract(CRegion(surfaceBox)); - } + CRegion shadowRegion(shadowBox); for (auto const& m : g_pCompositor->m_monitors) { if (!g_pHyprRenderer->shouldRenderWindow(PWINDOW, m)) { @@ -95,37 +87,45 @@ void CHyprDropShadowDecoration::draw(PHLMONITOR pMonitor, float const& a) { g_pHyprRenderer->m_renderPass.add(makeUnique(data)); } -void CHyprDropShadowDecoration::render(PHLMONITOR pMonitor, float const& a) { +bool CHyprDropShadowDecoration::canRender(PHLMONITOR pMonitor) { + static auto PSHADOWS = CConfigValue("decoration:shadow:enabled"); + if (*PSHADOWS != 1) + return false; // disabled + const auto PWINDOW = m_window.lock(); if (!validMapped(PWINDOW)) - return; + return false; if (PWINDOW->m_realShadowColor->value() == CHyprColor(0, 0, 0, 0)) - return; // don't draw invisible shadows + return false; // don't draw invisible shadows if (!PWINDOW->m_ruleApplicator->decorate().valueOrDefault()) - return; + return false; if (PWINDOW->m_ruleApplicator->noShadow().valueOrDefault()) - return; + return false; - static auto PSHADOWS = CConfigValue("decoration:shadow:enabled"); - static auto PSHADOWSIZE = CConfigValue("decoration:shadow:range"); - static auto PSHADOWIGNOREWINDOW = CConfigValue("decoration:shadow:ignore_window"); - static auto PSHADOWSCALE = CConfigValue("decoration:shadow:scale"); - static auto PSHADOWOFFSET = CConfigValue("decoration:shadow:offset"); + return true; +} - if (*PSHADOWS != 1) - return; // disabled +SShadowRenderData CHyprDropShadowDecoration::getRenderData(PHLMONITOR pMonitor, float const& a) { + if (!canRender(pMonitor)) + return {}; - const auto BORDERSIZE = PWINDOW->getRealBorderSize(); - const auto ROUNDINGBASE = PWINDOW->rounding(); - const auto ROUNDINGPOWER = PWINDOW->roundingPower(); - const auto CORRECTIONOFFSET = (BORDERSIZE * (M_SQRT2 - 1) * std::max(2.0 - ROUNDINGPOWER, 0.0)); - const auto ROUNDING = ROUNDINGBASE > 0 ? (ROUNDINGBASE + BORDERSIZE) - CORRECTIONOFFSET : 0; - const auto PWORKSPACE = PWINDOW->m_workspace; - const auto WORKSPACEOFFSET = PWORKSPACE && !PWINDOW->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D(); + const auto PWINDOW = m_window.lock(); + + static auto PSHADOWSIZE = CConfigValue("decoration:shadow:range"); + static auto PSHADOWSCALE = CConfigValue("decoration:shadow:scale"); + static auto PSHADOWOFFSET = CConfigValue("decoration:shadow:offset"); + + const auto BORDERSIZE = PWINDOW->getRealBorderSize(); + const auto ROUNDINGBASE = PWINDOW->rounding(); + const auto ROUNDINGPOWER = PWINDOW->roundingPower(); + const auto CORRECTIONOFFSET = (BORDERSIZE * (M_SQRT2 - 1) * std::max(2.0 - ROUNDINGPOWER, 0.0)); + const auto ROUNDING = ROUNDINGBASE > 0 ? (ROUNDINGBASE + BORDERSIZE) - CORRECTIONOFFSET : 0; + const auto PWORKSPACE = PWINDOW->m_workspace; + const auto WORKSPACEOFFSET = PWORKSPACE && !PWINDOW->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D(); // draw the shadow CBox fullBox = m_lastWindowBoxWithDecos; @@ -142,89 +142,57 @@ void CHyprDropShadowDecoration::render(PHLMONITOR pMonitor, float const& a) { updateWindow(PWINDOW); m_lastWindowPos += WORKSPACEOFFSET; - m_extents = {{m_lastWindowPos.x - fullBox.x - pMonitor->m_position.x + 2, m_lastWindowPos.y - fullBox.y - pMonitor->m_position.y + 2}, - {fullBox.x + fullBox.width + pMonitor->m_position.x - m_lastWindowPos.x - m_lastWindowSize.x + 2, - fullBox.y + fullBox.height + pMonitor->m_position.y - m_lastWindowPos.y - m_lastWindowSize.y + 2}}; + m_extents = { + .topLeft = + { + m_lastWindowPos.x - fullBox.x - pMonitor->m_position.x + 2, + m_lastWindowPos.y - fullBox.y - pMonitor->m_position.y + 2, + }, + .bottomRight = + { + fullBox.x + fullBox.width + pMonitor->m_position.x - m_lastWindowPos.x - m_lastWindowSize.x + 2, + fullBox.y + fullBox.height + pMonitor->m_position.y - m_lastWindowPos.y - m_lastWindowSize.y + 2, + }, + }; fullBox.translate(PWINDOW->m_floatingOffset); if (fullBox.width < 1 || fullBox.height < 1) - return; // don't draw invisible shadows + return {}; // don't draw invisible shadows - g_pHyprOpenGL->scissor(nullptr); - g_pHyprOpenGL->m_renderData.currentWindow = m_window; - - // we'll take the liberty of using this as it should not be used rn - CFramebuffer& alphaFB = g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorFB; - CFramebuffer& alphaSwapFB = g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorSwapFB; - auto* LASTFB = g_pHyprOpenGL->m_renderData.currentFB; + g_pHyprRenderer->m_renderData.currentWindow = m_window; fullBox.scale(pMonitor->m_scale).round(); - if (*PSHADOWIGNOREWINDOW) { - CBox windowBox = m_lastWindowBox; - CBox withDecos = m_lastWindowBoxWithDecos; - - // get window box - windowBox.translate(-pMonitor->m_position + WORKSPACEOFFSET); - withDecos.translate(-pMonitor->m_position + WORKSPACEOFFSET); - - windowBox.translate(PWINDOW->m_floatingOffset); - withDecos.translate(PWINDOW->m_floatingOffset); - - auto scaledExtentss = withDecos.extentsFrom(windowBox); - scaledExtentss = scaledExtentss * pMonitor->m_scale; - scaledExtentss = scaledExtentss.round(); - - // add extents - windowBox.scale(pMonitor->m_scale).round().addExtents(scaledExtentss); - - if (windowBox.width < 1 || windowBox.height < 1) - return; // prevent assert failed - - CRegion saveDamage = g_pHyprOpenGL->m_renderData.damage; - - g_pHyprOpenGL->m_renderData.damage = fullBox; - 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(); - - // build the matte - // 10-bit formats have dogshit alpha channels, so we have to use the matte to its fullest. - // first, clear region of interest with black (fully transparent) - g_pHyprOpenGL->renderRect(fullBox, CHyprColor(0, 0, 0, 1), {.round = 0}); - - // render white shadow with the alpha of the shadow color (otherwise we clear with alpha later and shit it to 2 bit) - drawShadowInternal(fullBox, ROUNDING * pMonitor->m_scale, ROUNDINGPOWER, *PSHADOWSIZE * pMonitor->m_scale, CHyprColor(1, 1, 1, PWINDOW->m_realShadowColor->value().a), a); - - // render black window box ("clip") - 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(); - - // 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}); - - LASTFB->bind(); - - CBox monbox = {0, 0, pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y}; - - g_pHyprOpenGL->pushMonitorTransformEnabled(true); - g_pHyprOpenGL->setRenderModifEnabled(false); - g_pHyprOpenGL->renderTextureMatte(alphaSwapFB.getTexture(), monbox, alphaFB); - g_pHyprOpenGL->setRenderModifEnabled(true); - g_pHyprOpenGL->popMonitorTransformEnabled(); - - g_pHyprOpenGL->m_renderData.damage = saveDamage; - } else - drawShadowInternal(fullBox, ROUNDING * pMonitor->m_scale, ROUNDINGPOWER, *PSHADOWSIZE * pMonitor->m_scale, PWINDOW->m_realShadowColor->value(), a); + return { + .valid = true, + .fullBox = fullBox, + .rounding = ROUNDING, + .roundingPower = ROUNDINGPOWER, + .size = *PSHADOWSIZE, + }; +} +void CHyprDropShadowDecoration::reposition() { if (m_extents != m_reportedExtents) g_pDecorationPositioner->repositionDeco(this); - g_pHyprOpenGL->m_renderData.currentWindow.reset(); + g_pHyprRenderer->m_renderData.currentWindow.reset(); +} + +// TODO remove +void CHyprDropShadowDecoration::render(PHLMONITOR pMonitor, float const& a) { + auto data = getRenderData(pMonitor, a); + if (!data.valid) + return; + + const auto PWINDOW = m_window.lock(); + + g_pHyprRenderer->disableScissor(); + + drawShadowInternal(data.fullBox, data.rounding * pMonitor->m_scale, data.roundingPower, data.size * pMonitor->m_scale, PWINDOW->m_realShadowColor->value(), a); + + reposition(); } eDecorationLayer CHyprDropShadowDecoration::getDecorationLayer() { @@ -237,12 +205,17 @@ void CHyprDropShadowDecoration::drawShadowInternal(const CBox& box, int round, f if (box.w < 1 || box.h < 1) return; - g_pHyprOpenGL->blend(true); + g_pHyprRenderer->blend(true); color.a *= a; if (*PSHADOWSHARP) - g_pHyprOpenGL->renderRect(box, color, {.round = round, .roundingPower = roundingPower}); + g_pHyprRenderer->draw(CRectPassElement::SRectData{ + .box = box, + .color = color, + .round = round, + .roundingPower = roundingPower, + }); else - g_pHyprOpenGL->renderRoundedShadow(box, round, roundingPower, range, color, 1.F); + g_pHyprRenderer->drawShadow(box, round, roundingPower, range, color, 1.F); } diff --git a/src/render/decorations/CHyprDropShadowDecoration.hpp b/src/render/decorations/CHyprDropShadowDecoration.hpp index 22887a522..6cc7f4994 100644 --- a/src/render/decorations/CHyprDropShadowDecoration.hpp +++ b/src/render/decorations/CHyprDropShadowDecoration.hpp @@ -2,6 +2,14 @@ #include "IHyprWindowDecoration.hpp" +struct SShadowRenderData { + bool valid = false; + CBox fullBox; + float rounding = 0; + float roundingPower = 0; + int size = 0; +}; + class CHyprDropShadowDecoration : public IHyprWindowDecoration { public: CHyprDropShadowDecoration(PHLWINDOW); @@ -25,7 +33,12 @@ class CHyprDropShadowDecoration : public IHyprWindowDecoration { virtual std::string getDisplayName(); - void render(PHLMONITOR, float const& a); + bool canRender(PHLMONITOR); + SShadowRenderData getRenderData(PHLMONITOR, float const& a); + void reposition(); + + // TODO remove + void render(PHLMONITOR, float const& a); private: SBoxExtents m_extents; diff --git a/src/render/decorations/CHyprGroupBarDecoration.cpp b/src/render/decorations/CHyprGroupBarDecoration.cpp index 93a17341d..b8f6d814f 100644 --- a/src/render/decorations/CHyprGroupBarDecoration.cpp +++ b/src/render/decorations/CHyprGroupBarDecoration.cpp @@ -2,19 +2,23 @@ #include "../../Compositor.hpp" #include "../../config/ConfigValue.hpp" #include "../../desktop/state/FocusState.hpp" -#include "managers/LayoutManager.hpp" +#include "../../desktop/view/Group.hpp" #include #include #include "../pass/TexPassElement.hpp" #include "../pass/RectPassElement.hpp" #include "../Renderer.hpp" #include "../../managers/input/InputManager.hpp" +#include "../../layout/LayoutManager.hpp" +#include "../../layout/supplementary/DragController.hpp" + +using namespace Render; // shared things to conserve VRAM -static SP m_tGradientActive = makeShared(); -static SP m_tGradientInactive = makeShared(); -static SP m_tGradientLockedActive = makeShared(); -static SP m_tGradientLockedInactive = makeShared(); +static SP m_tGradientActive; +static SP m_tGradientInactive; +static SP m_tGradientLockedActive; +static SP m_tGradientLockedInactive; constexpr int BAR_TEXT_PAD = 2; @@ -22,7 +26,7 @@ CHyprGroupBarDecoration::CHyprGroupBarDecoration(PHLWINDOW pWindow) : IHyprWindo static auto PGRADIENTS = CConfigValue("group:groupbar:enabled"); static auto PENABLED = CConfigValue("group:groupbar:gradients"); - if (m_tGradientActive->m_texID == 0 && *PENABLED && *PGRADIENTS) + if (*PENABLED && *PGRADIENTS) refreshGroupBarGradients(); } @@ -65,19 +69,14 @@ eDecorationType CHyprGroupBarDecoration::getDecorationType() { // void CHyprGroupBarDecoration::updateWindow(PHLWINDOW pWindow) { - if (m_window->m_groupData.pNextWindow.expired()) { + if (!m_window->m_group) { m_window->removeWindowDeco(this); return; } m_dwGroupMembers.clear(); - PHLWINDOW head = pWindow->getGroupHead(); - m_dwGroupMembers.emplace_back(head); - - PHLWINDOW curr = head->m_groupData.pNextWindow.lock(); - while (curr != head) { - m_dwGroupMembers.emplace_back(curr); - curr = curr->m_groupData.pNextWindow.lock(); + for (const auto& w : m_window->m_group->windows()) { + m_dwGroupMembers.emplace_back(w); } damageEntire(); @@ -127,11 +126,12 @@ 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()); - auto* const GROUPCOLACTIVELOCKED = sc((PGROUPCOLACTIVELOCKED.ptr())->getData()); - auto* const GROUPCOLINACTIVELOCKED = sc((PGROUPCOLINACTIVELOCKED.ptr())->getData()); + auto* const GROUPCOLACTIVE = sc((PGROUPCOLACTIVE.ptr())->getData()); + auto* const GROUPCOLINACTIVE = sc((PGROUPCOLINACTIVE.ptr())->getData()); + auto* const GROUPCOLACTIVELOCKED = sc((PGROUPCOLACTIVELOCKED.ptr())->getData()); + auto* const GROUPCOLINACTIVELOCKED = sc((PGROUPCOLINACTIVELOCKED.ptr())->getData()); const auto ASSIGNEDBOX = assignedBoxGlobal(); @@ -157,7 +157,7 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { rect.scale(pMonitor->m_scale).round(); - const bool GROUPLOCKED = m_window->getGroupHead()->m_groupData.locked || g_pKeybindManager->m_groupsLocked; + const bool GROUPLOCKED = m_window->m_group->locked() || g_pKeybindManager->m_groupsLocked; const auto* const PCOLACTIVE = GROUPLOCKED ? GROUPCOLACTIVELOCKED : GROUPCOLACTIVE; const auto* const PCOLINACTIVE = GROUPLOCKED ? GROUPCOLINACTIVELOCKED : GROUPCOLINACTIVE; @@ -172,7 +172,7 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { if (*PROUNDING) { rectdata.round = *PROUNDING; rectdata.roundingPower = *PROUNDINGPOWER; - if (*PROUNDONLYEDGES) { + if (*PROUNDONLYEDGES && barsToDraw > 1) { rectdata.round = 0; const double offset = *PROUNDING * 2; if (i == 0) { @@ -198,15 +198,16 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { if (*PGRADIENTS) { const auto GRADIENTTEX = (m_dwGroupMembers[WINDOWINDEX] == Desktop::focusState()->window() ? (GROUPLOCKED ? m_tGradientLockedActive : m_tGradientActive) : (GROUPLOCKED ? m_tGradientLockedInactive : m_tGradientInactive)); - if (GRADIENTTEX->m_texID) { + if (GRADIENTTEX && GRADIENTTEX->ok()) { CTexPassElement::SRenderData data; data.tex = GRADIENTTEX; data.blur = blur; data.box = rect; + data.a = a; if (*PGRADIENTROUNDING) { data.round = *PGRADIENTROUNDING; data.roundingPower = *PGRADIENTROUNDINGPOWER; - if (*PGRADIENTROUNDINGONLYEDGES) { + if (*PGRADIENTROUNDINGONLYEDGES && barsToDraw > 1) { data.round = 0; const double offset = *PGRADIENTROUNDING * 2; if (i == 0) { @@ -228,13 +229,14 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { CTitleTex* pTitleTex = textureFromTitle(m_dwGroupMembers[WINDOWINDEX]->m_title); if (!pTitleTex) - pTitleTex = m_titleTexs.titleTexs - .emplace_back(makeUnique(m_dwGroupMembers[WINDOWINDEX].lock(), - Vector2D{m_barWidth * pMonitor->m_scale, (*PTITLEFONTSIZE + 2L * BAR_TEXT_PAD) * pMonitor->m_scale}, - pMonitor->m_scale)) - .get(); + pTitleTex = + m_titleTexs.titleTexs + .emplace_back(makeUnique( + m_dwGroupMembers[WINDOWINDEX].lock(), + Vector2D{(m_barWidth - (*PTEXTPADDING * 2)) * pMonitor->m_scale, (*PTITLEFONTSIZE + 2L * BAR_TEXT_PAD) * pMonitor->m_scale}, pMonitor->m_scale)) + .get(); - SP titleTex; + SP titleTex; if (m_dwGroupMembers[WINDOWINDEX] == Desktop::focusState()->window()) titleTex = GROUPLOCKED ? pTitleTex->m_texLockedActive : pTitleTex->m_texActive; else @@ -243,7 +245,7 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { rect.y += std::ceil(((rect.height - titleTex->m_size.y) / 2.0) - (*PTEXTOFFSET * pMonitor->m_scale)); rect.height = titleTex->m_size.y; rect.width = titleTex->m_size.x; - rect.x += std::round(((m_barWidth * pMonitor->m_scale) / 2.0) - (titleTex->m_size.x / 2.0)); + rect.x += std::round((((m_barWidth + *PTEXTPADDING) * pMonitor->m_scale) / 2.0) - ((titleTex->m_size.x + *PTEXTPADDING) / 2.0)); rect.round(); CTexPassElement::SRenderData data; @@ -289,8 +291,8 @@ CTitleTex::CTitleTex(PHLWINDOW pWindow, const Vector2D& bufferSize, const float static auto PTITLEFONTWEIGHTACTIVE = CConfigValue("group:groupbar:font_weight_active"); static auto PTITLEFONTWEIGHTINACTIVE = CConfigValue("group:groupbar:font_weight_inactive"); - const auto FONTWEIGHTACTIVE = sc((PTITLEFONTWEIGHTACTIVE.ptr())->getData()); - const auto FONTWEIGHTINACTIVE = sc((PTITLEFONTWEIGHTINACTIVE.ptr())->getData()); + const auto FONTWEIGHTACTIVE = sc((PTITLEFONTWEIGHTACTIVE.ptr())->getData()); + const auto FONTWEIGHTINACTIVE = sc((PTITLEFONTWEIGHTINACTIVE.ptr())->getData()); const CHyprColor COLORACTIVE = CHyprColor(*PTEXTCOLORACTIVE); const CHyprColor COLORINACTIVE = *PTEXTCOLORINACTIVE == -1 ? COLORACTIVE : CHyprColor(*PTEXTCOLORINACTIVE); @@ -299,7 +301,7 @@ CTitleTex::CTitleTex(PHLWINDOW pWindow, const Vector2D& bufferSize, const float const auto FONTFAMILY = *PTITLEFONTFAMILY != STRVAL_EMPTY ? *PTITLEFONTFAMILY : *FALLBACKFONT; -#define RENDER_TEXT(color, weight) g_pHyprOpenGL->renderText(pWindow->m_title, (color), *PTITLEFONTSIZE* monitorScale, false, FONTFAMILY, bufferSize.x - 2, (weight)); +#define RENDER_TEXT(color, weight) g_pHyprRenderer->renderText(pWindow->m_title, (color), *PTITLEFONTSIZE* monitorScale, false, FONTFAMILY, bufferSize.x - 2, (weight)); m_texActive = RENDER_TEXT(COLORACTIVE, FONTWEIGHTACTIVE->m_value); m_texInactive = RENDER_TEXT(COLORINACTIVE, FONTWEIGHTINACTIVE->m_value); m_texLockedActive = RENDER_TEXT(COLORLOCKEDACTIVE, FONTWEIGHTACTIVE->m_value); @@ -307,10 +309,10 @@ CTitleTex::CTitleTex(PHLWINDOW pWindow, const Vector2D& bufferSize, const float #undef RENDER_TEXT } -static void renderGradientTo(SP tex, CGradientValueData* grad) { +static SP renderGradient(Config::CGradientValueData* grad) { if (!Desktop::focusState()->monitor()) - return; + return nullptr; const Vector2D& bufferSize = Desktop::focusState()->monitor()->m_pixelSize; @@ -339,19 +341,13 @@ static void renderGradientTo(SP tex, CGradientValueData* grad) { cairo_surface_flush(CAIROSURFACE); // copy the data to an OpenGL texture we have - const auto DATA = cairo_image_surface_get_data(CAIROSURFACE); - tex->allocate(); - tex->bind(); - tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); - tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); - tex->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); - tex->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bufferSize.x, bufferSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA); + auto tex = g_pHyprRenderer->createTexture(CAIROSURFACE); // delete cairo cairo_destroy(CAIRO); cairo_surface_destroy(CAIROSURFACE); + + return tex; } void refreshGroupBarGradients() { @@ -362,34 +358,32 @@ void refreshGroupBarGradients() { static auto PGROUPCOLINACTIVE = CConfigValue("group:groupbar:col.inactive"); static auto PGROUPCOLACTIVELOCKED = CConfigValue("group:groupbar:col.locked_active"); static auto PGROUPCOLINACTIVELOCKED = CConfigValue("group:groupbar:col.locked_inactive"); - auto* const GROUPCOLACTIVE = sc((PGROUPCOLACTIVE.ptr())->getData()); - auto* const GROUPCOLINACTIVE = sc((PGROUPCOLINACTIVE.ptr())->getData()); - auto* const GROUPCOLACTIVELOCKED = sc((PGROUPCOLACTIVELOCKED.ptr())->getData()); - auto* const GROUPCOLINACTIVELOCKED = sc((PGROUPCOLINACTIVELOCKED.ptr())->getData()); + auto* const GROUPCOLACTIVE = sc((PGROUPCOLACTIVE.ptr())->getData()); + auto* const GROUPCOLINACTIVE = sc((PGROUPCOLINACTIVE.ptr())->getData()); + auto* const GROUPCOLACTIVELOCKED = sc((PGROUPCOLACTIVELOCKED.ptr())->getData()); + auto* const GROUPCOLINACTIVELOCKED = sc((PGROUPCOLINACTIVELOCKED.ptr())->getData()); - g_pHyprRenderer->makeEGLCurrent(); - - if (m_tGradientActive->m_texID != 0) { - m_tGradientActive->destroyTexture(); - m_tGradientInactive->destroyTexture(); - m_tGradientLockedActive->destroyTexture(); - m_tGradientLockedInactive->destroyTexture(); + if (m_tGradientActive && m_tGradientActive->ok()) { + m_tGradientActive.reset(); + m_tGradientInactive.reset(); + m_tGradientLockedActive.reset(); + m_tGradientLockedInactive.reset(); } if (!*PENABLED || !*PGRADIENTS) return; - renderGradientTo(m_tGradientActive, GROUPCOLACTIVE); - renderGradientTo(m_tGradientInactive, GROUPCOLINACTIVE); - renderGradientTo(m_tGradientLockedActive, GROUPCOLACTIVELOCKED); - renderGradientTo(m_tGradientLockedInactive, GROUPCOLINACTIVELOCKED); + m_tGradientActive = renderGradient(GROUPCOLACTIVE); + m_tGradientInactive = renderGradient(GROUPCOLINACTIVE); + m_tGradientLockedActive = renderGradient(GROUPCOLACTIVELOCKED); + m_tGradientLockedInactive = renderGradient(GROUPCOLINACTIVELOCKED); } bool CHyprGroupBarDecoration::onBeginWindowDragOnDeco(const Vector2D& pos) { static auto PSTACKED = CConfigValue("group:groupbar:stacked"); static auto POUTERGAP = CConfigValue("group:groupbar:gaps_out"); static auto PINNERGAP = CConfigValue("group:groupbar:gaps_in"); - if (m_window.lock() == m_window->m_groupData.pNextWindow.lock()) + if (m_window->m_group->size() == 1) return false; const float BARRELATIVEX = pos.x - assignedBoxGlobal().x; @@ -402,95 +396,33 @@ bool CHyprGroupBarDecoration::onBeginWindowDragOnDeco(const Vector2D& pos) { if (*PSTACKED && (BARRELATIVEY - (m_barHeight + *POUTERGAP) * WINDOWINDEX < *POUTERGAP)) return false; - PHLWINDOW pWindow = m_window->getGroupWindowByIndex(WINDOWINDEX); + PHLWINDOW pWindow = m_window->m_group->fromIndex(WINDOWINDEX); - // hack - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow); - if (!pWindow->m_isFloating) { - const bool GROUPSLOCKEDPREV = g_pKeybindManager->m_groupsLocked; - g_pKeybindManager->m_groupsLocked = true; - g_pLayoutManager->getCurrentLayout()->onWindowCreated(pWindow); - g_pKeybindManager->m_groupsLocked = GROUPSLOCKEDPREV; - } + const auto& GROUP = m_window->m_group; - g_pInputManager->m_currentlyDraggedWindow = pWindow; + // remove the window from the group + GROUP->remove(pWindow); + + // start a move drag on it + g_layoutManager->dragController()->dragBegin(pWindow->layoutTarget(), MBIND_MOVE); if (!g_pCompositor->isWindowActive(pWindow)) - Desktop::focusState()->rawWindowFocus(pWindow); + Desktop::focusState()->rawWindowFocus(pWindow, Desktop::FOCUS_REASON_CLICK); return true; } bool CHyprGroupBarDecoration::onEndWindowDragOnDeco(const Vector2D& pos, PHLWINDOW pDraggedWindow) { - static auto PSTACKED = CConfigValue("group:groupbar:stacked"); static auto PDRAGINTOGROUP = CConfigValue("group:drag_into_group"); static auto PMERGEFLOATEDINTOTILEDONGROUPBAR = CConfigValue("group:merge_floated_into_tiled_on_groupbar"); static auto PMERGEGROUPSONGROUPBAR = CConfigValue("group:merge_groups_on_groupbar"); - static auto POUTERGAP = CConfigValue("group:groupbar:gaps_out"); - static auto PINNERGAP = CConfigValue("group:groupbar:gaps_in"); - const bool FLOATEDINTOTILED = !m_window->m_isFloating && !pDraggedWindow->m_draggingTiled; + const bool FLOATEDINTOTILED = !m_window->m_isFloating && !g_layoutManager->dragController()->draggingTiled(); - g_pInputManager->m_wasDraggingWindow = false; - - if (!pDraggedWindow->canBeGroupedInto(m_window.lock()) || (*PDRAGINTOGROUP != 1 && *PDRAGINTOGROUP != 2) || (FLOATEDINTOTILED && !*PMERGEFLOATEDINTOTILEDONGROUPBAR) || - (!*PMERGEGROUPSONGROUPBAR && pDraggedWindow->m_groupData.pNextWindow.lock() && m_window->m_groupData.pNextWindow.lock())) { - g_pInputManager->m_wasDraggingWindow = true; + if (!pDraggedWindow->canBeGroupedInto(m_window->m_group) || (*PDRAGINTOGROUP != 1 && *PDRAGINTOGROUP != 2) || (FLOATEDINTOTILED && !*PMERGEFLOATEDINTOTILEDONGROUPBAR) || + (!*PMERGEGROUPSONGROUPBAR && pDraggedWindow->m_group)) return false; - } - const float BARRELATIVE = *PSTACKED ? pos.y - assignedBoxGlobal().y - (m_barHeight + *POUTERGAP) / 2 : pos.x - assignedBoxGlobal().x - m_barWidth / 2; - const float BARSIZE = *PSTACKED ? m_barHeight + *POUTERGAP : m_barWidth + *PINNERGAP; - const int WINDOWINDEX = BARRELATIVE < 0 ? -1 : BARRELATIVE / BARSIZE; - - PHLWINDOW pWindowInsertAfter = m_window->getGroupWindowByIndex(WINDOWINDEX); - PHLWINDOW pWindowInsertEnd = pWindowInsertAfter->m_groupData.pNextWindow.lock(); - PHLWINDOW pDraggedHead = pDraggedWindow->m_groupData.pNextWindow.lock() ? pDraggedWindow->getGroupHead() : pDraggedWindow; - - if (!pDraggedWindow->m_groupData.pNextWindow.expired()) { - - // stores group data - std::vector members; - PHLWINDOW curr = pDraggedHead; - const bool WASLOCKED = pDraggedHead->m_groupData.locked; - do { - members.push_back(curr); - curr = curr->m_groupData.pNextWindow.lock(); - } while (curr != members[0]); - - // removes all windows - for (const PHLWINDOW& w : members) { - w->m_groupData.pNextWindow.reset(); - w->m_groupData.head = false; - w->m_groupData.locked = false; - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(w); - } - - // restores the group - for (auto it = members.begin(); it != members.end(); ++it) { - (*it)->m_isFloating = pWindowInsertAfter->m_isFloating; // match the floating state of group members - *(*it)->m_realSize = pWindowInsertAfter->m_realSize->goal(); // match the size of group members - *(*it)->m_realPosition = pWindowInsertAfter->m_realPosition->goal(); // match the position of group members - if (std::next(it) != members.end()) - (*it)->m_groupData.pNextWindow = *std::next(it); - else - (*it)->m_groupData.pNextWindow = members[0]; - } - members[0]->m_groupData.head = true; - members[0]->m_groupData.locked = WASLOCKED; - } else - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pDraggedWindow); - - pDraggedWindow->m_isFloating = pWindowInsertAfter->m_isFloating; // match the floating state of the window - - pWindowInsertAfter->insertWindowToGroup(pDraggedWindow); - - if (WINDOWINDEX == -1) - std::swap(pDraggedHead->m_groupData.head, pWindowInsertEnd->m_groupData.head); - - m_window->setGroupCurrent(pDraggedWindow); - pDraggedWindow->applyGroupRules(); - pDraggedWindow->updateWindowDecos(); - g_pLayoutManager->getCurrentLayout()->recalculateWindow(pDraggedWindow); + m_window->m_group->add(pDraggedWindow); if (!pDraggedWindow->getDecorationByType(DECORATION_GROUPBAR)) pDraggedWindow->addWindowDeco(makeUnique(pDraggedWindow)); @@ -517,7 +449,7 @@ bool CHyprGroupBarDecoration::onMouseButtonOnDeco(const Vector2D& pos, const IPo if (e.state == WL_POINTER_BUTTON_STATE_PRESSED) pressedCursorPos = pos; else if (e.state == WL_POINTER_BUTTON_STATE_RELEASED && pressedCursorPos == pos) - g_pXWaylandManager->sendCloseWindow(m_window->getGroupWindowByIndex(WINDOWINDEX)); + g_pXWaylandManager->sendCloseWindow(m_window->m_group->fromIndex(WINDOWINDEX)); return true; } @@ -530,17 +462,17 @@ bool CHyprGroupBarDecoration::onMouseButtonOnDeco(const Vector2D& pos, const IPo const auto STACKPAD = *PSTACKED && (BARRELATIVEY - (m_barHeight + *POUTERGAP) * WINDOWINDEX < *POUTERGAP); if (TABPAD || STACKPAD) { if (!g_pCompositor->isWindowActive(m_window.lock())) - Desktop::focusState()->rawWindowFocus(m_window.lock()); + Desktop::focusState()->rawWindowFocus(m_window.lock(), Desktop::FOCUS_REASON_CLICK); return true; } - PHLWINDOW pWindow = m_window->getGroupWindowByIndex(WINDOWINDEX); + PHLWINDOW pWindow = m_window->m_group->fromIndex(WINDOWINDEX); if (pWindow != m_window) - pWindow->setGroupCurrent(pWindow); + pWindow->m_group->setCurrent(pWindow); if (!g_pCompositor->isWindowActive(pWindow) && *PFOLLOWMOUSE != 3) - Desktop::focusState()->rawWindowFocus(pWindow); + Desktop::focusState()->rawWindowFocus(pWindow, Desktop::FOCUS_REASON_CLICK); if (pWindow->m_isFloating) g_pCompositor->changeWindowZOrder(pWindow, true); @@ -551,13 +483,13 @@ bool CHyprGroupBarDecoration::onMouseButtonOnDeco(const Vector2D& pos, const IPo bool CHyprGroupBarDecoration::onScrollOnDeco(const Vector2D& pos, const IPointer::SAxisEvent e) { static auto PGROUPBARSCROLLING = CConfigValue("group:groupbar:scrolling"); - if (!*PGROUPBARSCROLLING || m_window->m_groupData.pNextWindow.expired()) + if (!*PGROUPBARSCROLLING || !m_window->m_group) return false; if (e.delta > 0) - m_window->setGroupCurrent(m_window->m_groupData.pNextWindow.lock()); + m_window->m_group->moveCurrent(true); else - m_window->setGroupCurrent(m_window->getGroupPrevious()); + m_window->m_group->moveCurrent(false); return true; } diff --git a/src/render/decorations/CHyprGroupBarDecoration.hpp b/src/render/decorations/CHyprGroupBarDecoration.hpp index 3e5d3c2d6..f6581c8d6 100644 --- a/src/render/decorations/CHyprGroupBarDecoration.hpp +++ b/src/render/decorations/CHyprGroupBarDecoration.hpp @@ -12,13 +12,13 @@ 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; - std::string m_content; + SP m_texActive; + SP m_texInactive; + SP m_texLockedActive; + SP m_texLockedInactive; + std::string m_content; - PHLWINDOWREF m_windowOwner; + PHLWINDOWREF m_windowOwner; }; void refreshGroupBarGradients(); diff --git a/src/render/decorations/CHyprInnerGlowDecoration.cpp b/src/render/decorations/CHyprInnerGlowDecoration.cpp new file mode 100644 index 000000000..687a77b1f --- /dev/null +++ b/src/render/decorations/CHyprInnerGlowDecoration.cpp @@ -0,0 +1,105 @@ +#include "CHyprInnerGlowDecoration.hpp" + +#include "../../Compositor.hpp" +#include "../pass/InnerGlowPassElement.hpp" +#include "../Renderer.hpp" +#include "../OpenGL.hpp" + +CHyprInnerGlowDecoration::CHyprInnerGlowDecoration(PHLWINDOW pWindow) : IHyprWindowDecoration(pWindow), m_window(pWindow) { + ; +} + +eDecorationType CHyprInnerGlowDecoration::getDecorationType() { + return DECORATION_INNER_GLOW; +} + +SDecorationPositioningInfo CHyprInnerGlowDecoration::getPositioningInfo() { + SDecorationPositioningInfo info; + info.policy = DECORATION_POSITION_ABSOLUTE; + info.edges = DECORATION_EDGE_BOTTOM | DECORATION_EDGE_LEFT | DECORATION_EDGE_RIGHT | DECORATION_EDGE_TOP; + return info; +} + +void CHyprInnerGlowDecoration::onPositioningReply(const SDecorationPositioningReply& reply) { + updateWindow(m_window.lock()); +} + +uint64_t CHyprInnerGlowDecoration::getDecorationFlags() { + return DECORATION_NON_SOLID; +} + +std::string CHyprInnerGlowDecoration::getDisplayName() { + return "Inner Glow"; +} + +void CHyprInnerGlowDecoration::damageEntire() { + const auto PWINDOW = m_window.lock(); + if (!validMapped(PWINDOW)) + return; + + CBox windowBox = PWINDOW->getWindowMainSurfaceBox(); + + const auto PWORKSPACE = PWINDOW->m_workspace; + if (PWORKSPACE && PWORKSPACE->m_renderOffset->isBeingAnimated() && !PWINDOW->m_pinned) + windowBox.translate(PWORKSPACE->m_renderOffset->value()); + windowBox.translate(PWINDOW->m_floatingOffset); + + g_pHyprRenderer->damageRegion(CRegion(windowBox)); +} + +void CHyprInnerGlowDecoration::updateWindow(PHLWINDOW pWindow) { + const auto PWINDOW = m_window.lock(); + m_lastWindowPos = PWINDOW->m_realPosition->value(); + m_lastWindowSize = PWINDOW->m_realSize->value(); +} + +void CHyprInnerGlowDecoration::draw(PHLMONITOR pMonitor, float const& a) { + CInnerGlowPassElement::SInnerGlowData data; + data.deco = this; + data.a = a; + g_pHyprRenderer->m_renderPass.add(makeUnique(data)); +} + +void CHyprInnerGlowDecoration::render(PHLMONITOR pMonitor, float const& a) { + + static auto PGLOW = CConfigValue("decoration:glow:enabled"); + static auto PGLOWRANGE = CConfigValue("decoration:glow:range"); + static auto PGLOWPOWER = CConfigValue("decoration:glow:render_power"); + + if (!*PGLOW) + return; + + const auto PWINDOW = m_window.lock(); + + if (!validMapped(PWINDOW)) + return; + + const auto ROUNDING = PWINDOW->rounding() > 0 ? PWINDOW->rounding() - 1 : PWINDOW->rounding(); + const auto ROUNDINGPOWER = PWINDOW->roundingPower(); + const auto PWORKSPACE = PWINDOW->m_workspace; + const auto WORKSPACEOFF = PWORKSPACE && !PWINDOW->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D(); + + CBox windowBox = {m_lastWindowPos.x, m_lastWindowPos.y, m_lastWindowSize.x, m_lastWindowSize.y}; + windowBox.translate(-pMonitor->m_position + WORKSPACEOFF + PWINDOW->m_floatingOffset); + windowBox.scale(pMonitor->m_scale).round(); + + if (windowBox.width < 1 || windowBox.height < 1) + return; + + const int GLOWSIZE = *PGLOWRANGE; + const float GLOWPOWER = *PGLOWPOWER; + const auto GLOWCOLOR = m_window->m_realGlowColor->value(); + + g_pHyprRenderer->m_renderData.currentWindow = m_window; + + g_pHyprRenderer->blend(true); + + // FIXME use g_pHyprRenderer API + Render::GL::g_pHyprOpenGL->renderInnerGlow(windowBox, ROUNDING * pMonitor->m_scale, ROUNDINGPOWER, GLOWSIZE * pMonitor->m_scale, GLOWCOLOR, GLOWPOWER, a); + + g_pHyprRenderer->m_renderData.currentWindow.reset(); +} + +eDecorationLayer CHyprInnerGlowDecoration::getDecorationLayer() { + return DECORATION_LAYER_OVER; +} diff --git a/src/render/decorations/CHyprInnerGlowDecoration.hpp b/src/render/decorations/CHyprInnerGlowDecoration.hpp new file mode 100644 index 000000000..a2e4d79a5 --- /dev/null +++ b/src/render/decorations/CHyprInnerGlowDecoration.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "IHyprWindowDecoration.hpp" + +class CHyprInnerGlowDecoration : public IHyprWindowDecoration { + public: + CHyprInnerGlowDecoration(PHLWINDOW); + virtual ~CHyprInnerGlowDecoration() = default; + + virtual SDecorationPositioningInfo getPositioningInfo(); + + virtual void onPositioningReply(const SDecorationPositioningReply& reply); + + virtual void draw(PHLMONITOR, float const& a); + + virtual eDecorationType getDecorationType(); + + virtual void updateWindow(PHLWINDOW); + + virtual void damageEntire(); + + virtual eDecorationLayer getDecorationLayer(); + + virtual uint64_t getDecorationFlags(); + + virtual std::string getDisplayName(); + + void render(PHLMONITOR, float const& a); + + private: + PHLWINDOWREF m_window; + + Vector2D m_lastWindowPos; + Vector2D m_lastWindowSize; +}; diff --git a/src/render/decorations/DecorationPositioner.cpp b/src/render/decorations/DecorationPositioner.cpp index 1082812f9..9754e3e13 100644 --- a/src/render/decorations/DecorationPositioner.cpp +++ b/src/render/decorations/DecorationPositioner.cpp @@ -1,23 +1,16 @@ #include "DecorationPositioner.hpp" -#include "../../desktop/Window.hpp" -#include "../../managers/HookSystemManager.hpp" -#include "../../managers/LayoutManager.hpp" +#include "../../desktop/view/Window.hpp" +#include "../../layout/target/Target.hpp" +#include "../../event/EventBus.hpp" CDecorationPositioner::CDecorationPositioner() { - static auto P = g_pHookSystem->hookDynamic("closeWindow", [this](void* call, SCallbackInfo& info, std::any data) { - auto PWINDOW = std::any_cast(data); - this->onWindowUnmap(PWINDOW); - }); - - static auto P2 = g_pHookSystem->hookDynamic("openWindow", [this](void* call, SCallbackInfo& info, std::any data) { - auto PWINDOW = std::any_cast(data); - this->onWindowMap(PWINDOW); - }); + static auto P = Event::bus()->m_events.window.close.listen([this](PHLWINDOW window) { onWindowUnmap(window); }); + static auto P2 = Event::bus()->m_events.window.open.listen([this](PHLWINDOW window) { onWindowMap(window); }); } Vector2D CDecorationPositioner::getEdgeDefinedPoint(uint32_t edges, PHLWINDOWREF pWindow) { if (!pWindow) { - Debug::log(ERR, "getEdgeDefinedPoint: invalid pWindow"); + Log::logger->log(Log::ERR, "getEdgeDefinedPoint: invalid pWindow"); return {}; } @@ -29,7 +22,7 @@ Vector2D CDecorationPositioner::getEdgeDefinedPoint(uint32_t edges, PHLWINDOWREF const int EDGESNO = TOP + BOTTOM + LEFT + RIGHT; if (EDGESNO == 0 || EDGESNO == 3 || EDGESNO > 4) { - Debug::log(ERR, "getEdgeDefinedPoint: invalid number of edges"); + Log::logger->log(Log::ERR, "getEdgeDefinedPoint: invalid number of edges"); return {}; } @@ -57,18 +50,19 @@ Vector2D CDecorationPositioner::getEdgeDefinedPoint(uint32_t edges, PHLWINDOWREF if (BOTTOM && LEFT) return wb.pos() + Vector2D{0.0, wb.size().y}; } - Debug::log(ERR, "getEdgeDefinedPoint: invalid configuration of edges"); + Log::logger->log(Log::ERR, "getEdgeDefinedPoint: invalid configuration of edges"); return {}; } void CDecorationPositioner::uncacheDecoration(IHyprWindowDecoration* deco) { - std::erase_if(m_windowPositioningDatas, [&](const auto& data) { return !data->pWindow.lock() || data->pDecoration == deco; }); - - const auto WIT = std::ranges::find_if(m_windowDatas, [&](const auto& other) { return other.first.lock() == deco->m_window.lock(); }); + const auto WIT = m_windowDatas.find(deco->m_window); if (WIT == m_windowDatas.end()) return; - WIT->second.needsRecalc = true; + std::erase_if(WIT->second.positioningDatas, [&](const auto& data) { return data->pDecoration == deco; }); + WIT->second.needsRecalc = true; + WIT->second.needsDamageExtents = true; + m_needsSanitize = true; } void CDecorationPositioner::repositionDeco(IHyprWindowDecoration* deco) { @@ -77,12 +71,17 @@ void CDecorationPositioner::repositionDeco(IHyprWindowDecoration* deco) { } CDecorationPositioner::SWindowPositioningData* CDecorationPositioner::getDataFor(IHyprWindowDecoration* pDecoration, PHLWINDOW pWindow) { - auto it = std::ranges::find_if(m_windowPositioningDatas, [&](const auto& el) { return el->pDecoration == pDecoration; }); + const auto WIT = m_windowDatas.find(pWindow); + if (WIT == m_windowDatas.end()) + return nullptr; - if (it != m_windowPositioningDatas.end()) + auto& datas = WIT->second.positioningDatas; + auto it = std::ranges::find_if(datas, [&](const auto& el) { return el->pDecoration == pDecoration; }); + + if (it != datas.end()) return it->get(); - const auto DATA = m_windowPositioningDatas.emplace_back(makeUnique(pWindow, pDecoration)).get(); + const auto DATA = datas.emplace_back(makeUnique(pWindow, pDecoration)).get(); DATA->positioningInfo = pDecoration->getPositioningInfo(); @@ -90,37 +89,44 @@ CDecorationPositioner::SWindowPositioningData* CDecorationPositioner::getDataFor } void CDecorationPositioner::sanitizeDatas() { + m_needsSanitize = false; std::erase_if(m_windowDatas, [](const auto& other) { return !valid(other.first); }); - std::erase_if(m_windowPositioningDatas, [](const auto& other) { - if (!validMapped(other->pWindow)) - return true; - if (std::ranges::find_if(other->pWindow->m_windowDecorations, [&](const auto& el) { return el.get() == other->pDecoration; }) == other->pWindow->m_windowDecorations.end()) - return true; - return false; - }); + for (auto& [window, wd] : m_windowDatas) { + std::erase_if(wd.positioningDatas, [&](const auto& data) { + return std::ranges::find_if(window->m_windowDecorations, [&](const auto& el) { return el.get() == data->pDecoration; }) == window->m_windowDecorations.end(); + }); + } } void CDecorationPositioner::forceRecalcFor(PHLWINDOW pWindow) { - const auto WIT = std::ranges::find_if(m_windowDatas, [&](const auto& other) { return other.first.lock() == pWindow; }); + const auto WIT = m_windowDatas.find(pWindow); if (WIT == m_windowDatas.end()) return; - const auto WINDOWDATA = &WIT->second; - - WINDOWDATA->needsRecalc = true; + WIT->second.needsRecalc = true; } void CDecorationPositioner::onWindowUpdate(PHLWINDOW pWindow) { if (!validMapped(pWindow)) return; - const auto WIT = std::ranges::find_if(m_windowDatas, [&](const auto& other) { return other.first.lock() == pWindow; }); + const auto WIT = m_windowDatas.find(pWindow); if (WIT == m_windowDatas.end()) return; - const auto WINDOWDATA = &WIT->second; + auto* const WINDOWDATA = &WIT->second; - sanitizeDatas(); + // fast path: if size unchanged and no recalc needed, skip the expensive work below. + // needsReposition is only true for newly-added decoration entries, so if the deco count + // matches what we've cached, no new decos were added and we can skip the all_of scan too. + if (WINDOWDATA->lastWindowSize == pWindow->m_realSize->value() && !WINDOWDATA->needsRecalc) { + const auto expectedDecos = pWindow->m_windowDecorations.size(); + if (WINDOWDATA->positioningDatas.size() == expectedDecos && std::ranges::all_of(WINDOWDATA->positioningDatas, [](const auto& data) { return !data->needsReposition; })) + return; + } + + if (m_needsSanitize) + sanitizeDatas(); // std::vector datas; @@ -131,13 +137,6 @@ void CDecorationPositioner::onWindowUpdate(PHLWINDOW pWindow) { datas.push_back(getDataFor(wd.get(), pWindow)); } - if (WINDOWDATA->lastWindowSize == pWindow->m_realSize->value() /* position not changed */ - && std::ranges::all_of(m_windowPositioningDatas, [pWindow](const auto& data) { return pWindow != data->pWindow.lock() || !data->needsReposition; }) - /* all window datas are either not for this window or don't need a reposition */ - && !WINDOWDATA->needsRecalc /* window doesn't need recalc */ - ) - return; - for (auto const& wd : datas) { wd->positioningInfo = wd->pDecoration->getPositioningInfo(); } @@ -214,29 +213,23 @@ void CDecorationPositioner::onWindowUpdate(PHLWINDOW pWindow) { continue; } - auto desiredSize = 0; - if (LEFT) - desiredSize = wd->positioningInfo.desiredExtents.topLeft.x; - else if (RIGHT) - desiredSize = wd->positioningInfo.desiredExtents.bottomRight.x; - else if (TOP) - desiredSize = wd->positioningInfo.desiredExtents.topLeft.y; - else - desiredSize = wd->positioningInfo.desiredExtents.bottomRight.y; + const auto desiredExtents = wd->positioningInfo.desiredExtents; const auto EDGEPOINT = getEdgeDefinedPoint(wd->positioningInfo.edges, pWindow); Vector2D pos, size; if (EDGESNO == 4) { - pos = wb.pos() - EDGEPOINT - Vector2D{stickyOffsetXL + desiredSize, stickyOffsetYT + desiredSize}; - size = wb.size() + Vector2D{stickyOffsetXL + stickyOffsetXR + desiredSize * 2, stickyOffsetYB + stickyOffsetYT + desiredSize * 2}; + stickyOffsetXL += desiredExtents.topLeft.x; + stickyOffsetXR += desiredExtents.bottomRight.x; + stickyOffsetYT += desiredExtents.topLeft.y; + stickyOffsetYB += desiredExtents.bottomRight.y; - stickyOffsetXL += desiredSize; - stickyOffsetXR += desiredSize; - stickyOffsetYT += desiredSize; - stickyOffsetYB += desiredSize; + pos = wb.pos() - EDGEPOINT - Vector2D{stickyOffsetXL, stickyOffsetYT}; + size = wb.size() + Vector2D{stickyOffsetXL + stickyOffsetXR, stickyOffsetYB + stickyOffsetYT}; } else if (LEFT) { + const auto desiredSize = desiredExtents.topLeft.x; + pos = wb.pos() - EDGEPOINT - Vector2D{stickyOffsetXL, -stickyOffsetYT}; pos.x -= desiredSize; size = {sc(desiredSize), wb.size().y + stickyOffsetYB + stickyOffsetYT}; @@ -244,12 +237,16 @@ void CDecorationPositioner::onWindowUpdate(PHLWINDOW pWindow) { if (SOLID) stickyOffsetXL += desiredSize; } else if (RIGHT) { + const auto desiredSize = desiredExtents.bottomRight.x; + pos = wb.pos() + Vector2D{wb.size().x, 0.0} - EDGEPOINT + Vector2D{stickyOffsetXR, -stickyOffsetYT}; size = {sc(desiredSize), wb.size().y + stickyOffsetYB + stickyOffsetYT}; if (SOLID) stickyOffsetXR += desiredSize; } else if (TOP) { + const auto desiredSize = desiredExtents.topLeft.y; + pos = wb.pos() - EDGEPOINT - Vector2D{stickyOffsetXL, stickyOffsetYT}; pos.y -= desiredSize; size = {wb.size().x + stickyOffsetXL + stickyOffsetXR, sc(desiredSize)}; @@ -257,6 +254,8 @@ void CDecorationPositioner::onWindowUpdate(PHLWINDOW pWindow) { if (SOLID) stickyOffsetYT += desiredSize; } else { + const auto desiredSize = desiredExtents.bottomRight.y; + pos = wb.pos() + Vector2D{0.0, wb.size().y} - EDGEPOINT - Vector2D{stickyOffsetXL, stickyOffsetYB}; size = {wb.size().x + stickyOffsetXL + stickyOffsetXR, sc(desiredSize)}; @@ -278,12 +277,14 @@ void CDecorationPositioner::onWindowUpdate(PHLWINDOW pWindow) { if (WINDOWDATA->extents != SBoxExtents{{stickyOffsetXL + reservedXL, stickyOffsetYT + reservedYT}, {stickyOffsetXR + reservedXR, stickyOffsetYB + reservedYB}}) { WINDOWDATA->extents = {{stickyOffsetXL + reservedXL, stickyOffsetYT + reservedYT}, {stickyOffsetXR + reservedXR, stickyOffsetYB + reservedYB}}; - g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow); + pWindow->layoutTarget()->recalc(); } + + // update cached decoration extents for getWindowDecorationExtents + WINDOWDATA->needsDamageExtents = true; } void CDecorationPositioner::onWindowUnmap(PHLWINDOW pWindow) { - std::erase_if(m_windowPositioningDatas, [&](const auto& data) { return data->pWindow.lock() == pWindow; }); m_windowDatas.erase(pWindow); } @@ -292,22 +293,22 @@ void CDecorationPositioner::onWindowMap(PHLWINDOW pWindow) { } SBoxExtents CDecorationPositioner::getWindowDecorationReserved(PHLWINDOWREF pWindow) { - try { - const auto E = m_windowDatas.at(pWindow); - return E.reserved; - } catch (std::out_of_range& e) { return {}; } + const auto WIT = m_windowDatas.find(pWindow); + if (WIT == m_windowDatas.end()) + return {}; + return WIT->second.reserved; } -SBoxExtents CDecorationPositioner::getWindowDecorationExtents(PHLWINDOWREF pWindow, bool inputOnly) { +SBoxExtents CDecorationPositioner::computeWindowDecorationExtents(PHLWINDOWREF pWindow, bool inputOnly) { CBox const mainSurfaceBox = pWindow->getWindowMainSurfaceBox(); CBox accum = mainSurfaceBox; - for (auto const& data : m_windowPositioningDatas) { - if (!data->pDecoration || (inputOnly && !(data->pDecoration->getDecorationFlags() & DECORATION_ALLOWS_MOUSE_INPUT))) - continue; + const auto WIT = m_windowDatas.find(pWindow); + if (WIT == m_windowDatas.end()) + return accum.extentsFrom(mainSurfaceBox); - auto const window = data->pWindow; - if (!window || window != pWindow) + for (auto const& data : WIT->second.positioningDatas) { + if (!data->pDecoration || (inputOnly && !(data->pDecoration->getDecorationFlags() & DECORATION_ALLOWS_MOUSE_INPUT))) continue; CBox decoBox; @@ -347,20 +348,36 @@ SBoxExtents CDecorationPositioner::getWindowDecorationExtents(PHLWINDOWREF pWind return accum.extentsFrom(mainSurfaceBox); } +SBoxExtents CDecorationPositioner::getWindowDecorationExtents(PHLWINDOWREF pWindow, bool inputOnly) { + const auto WIT = m_windowDatas.find(pWindow); + if (WIT == m_windowDatas.end()) + return computeWindowDecorationExtents(pWindow, inputOnly); + + auto& wd = WIT->second; + if (wd.needsDamageExtents) { + wd.decorationExtents = computeWindowDecorationExtents(pWindow, false); + wd.decorationInputExtents = computeWindowDecorationExtents(pWindow, true); + wd.needsDamageExtents = false; + } + + return inputOnly ? wd.decorationInputExtents : wd.decorationExtents; +} + CBox CDecorationPositioner::getBoxWithIncludedDecos(PHLWINDOW pWindow) { - CBox accum = pWindow->getWindowMainSurfaceBox(); + CBox accum = pWindow->getWindowMainSurfaceBox(); - for (auto const& data : m_windowPositioningDatas) { - if (data->pWindow.lock() != pWindow) - continue; + const auto WIT = m_windowDatas.find(pWindow); + if (WIT == m_windowDatas.end()) + return accum; + for (auto const& data : WIT->second.positioningDatas) { if (!(data->pDecoration->getDecorationFlags() & DECORATION_PART_OF_MAIN_WINDOW)) continue; CBox decoBox; if (data->positioningInfo.policy == DECORATION_POSITION_ABSOLUTE) { - decoBox = data->pWindow->getWindowMainSurfaceBox(); + decoBox = pWindow->getWindowMainSurfaceBox(); decoBox.addExtents(data->positioningInfo.desiredExtents); } else { decoBox = data->lastReply.assignedGeometry; @@ -388,8 +405,10 @@ CBox CDecorationPositioner::getBoxWithIncludedDecos(PHLWINDOW pWindow) { CBox CDecorationPositioner::getWindowDecorationBox(IHyprWindowDecoration* deco) { auto const window = deco->m_window.lock(); const auto DATA = getDataFor(deco, window); + if (!DATA) + return {}; - CBox box = DATA->lastReply.assignedGeometry; + CBox box = DATA->lastReply.assignedGeometry; box.translate(getEdgeDefinedPoint(DATA->positioningInfo.edges, window)); return box; } diff --git a/src/render/decorations/DecorationPositioner.hpp b/src/render/decorations/DecorationPositioner.hpp index 8048c7ade..11ca12fd1 100644 --- a/src/render/decorations/DecorationPositioner.hpp +++ b/src/render/decorations/DecorationPositioner.hpp @@ -6,7 +6,6 @@ #include "../../helpers/math/Math.hpp" #include "../../desktop/DesktopTypes.hpp" -class CWindow; class IHyprWindowDecoration; enum eDecorationPositioningPolicy : uint8_t { @@ -81,19 +80,24 @@ class CDecorationPositioner { }; struct SWindowData { - Vector2D lastWindowSize = {}; - SBoxExtents reserved = {}; - SBoxExtents extents = {}; - bool needsRecalc = false; + Vector2D lastWindowSize = {}; + SBoxExtents reserved = {}; + SBoxExtents extents = {}; + SBoxExtents decorationExtents = {}; + SBoxExtents decorationInputExtents = {}; + bool needsRecalc = false; + bool needsDamageExtents = true; + std::vector> positioningDatas; }; - std::map m_windowDatas; - std::vector> m_windowPositioningDatas; + std::map m_windowDatas; + bool m_needsSanitize = false; - SWindowPositioningData* getDataFor(IHyprWindowDecoration* pDecoration, PHLWINDOW pWindow); - void onWindowUnmap(PHLWINDOW pWindow); - void onWindowMap(PHLWINDOW pWindow); - void sanitizeDatas(); + SWindowPositioningData* getDataFor(IHyprWindowDecoration* pDecoration, PHLWINDOW pWindow); + SBoxExtents computeWindowDecorationExtents(PHLWINDOWREF pWindow, bool inputOnly); + void onWindowUnmap(PHLWINDOW pWindow); + void onWindowMap(PHLWINDOW pWindow); + void sanitizeDatas(); }; inline UP g_pDecorationPositioner; diff --git a/src/render/decorations/IHyprWindowDecoration.cpp b/src/render/decorations/IHyprWindowDecoration.cpp index 0dd118671..267820985 100644 --- a/src/render/decorations/IHyprWindowDecoration.cpp +++ b/src/render/decorations/IHyprWindowDecoration.cpp @@ -1,7 +1,5 @@ #include "IHyprWindowDecoration.hpp" -class CWindow; - IHyprWindowDecoration::IHyprWindowDecoration(PHLWINDOW pWindow) : m_window(pWindow) { ; } diff --git a/src/render/decorations/IHyprWindowDecoration.hpp b/src/render/decorations/IHyprWindowDecoration.hpp index 26bfcb455..5f77ea42b 100644 --- a/src/render/decorations/IHyprWindowDecoration.hpp +++ b/src/render/decorations/IHyprWindowDecoration.hpp @@ -9,6 +9,7 @@ enum eDecorationType : int8_t { DECORATION_NONE = -1, DECORATION_GROUPBAR, DECORATION_SHADOW, + DECORATION_INNER_GLOW, DECORATION_BORDER, DECORATION_CUSTOM }; @@ -26,7 +27,6 @@ enum eDecorationFlags : uint8_t { DECORATION_NON_SOLID = 1 << 2, /* this decoration is not solid. Other decorations should draw on top of it. Example: shadow */ }; -class CWindow; class CMonitor; class CDecorationPositioner; diff --git a/src/render/gl/GLElementRenderer.cpp b/src/render/gl/GLElementRenderer.cpp new file mode 100644 index 000000000..dbf62cb8f --- /dev/null +++ b/src/render/gl/GLElementRenderer.cpp @@ -0,0 +1,140 @@ +#include "GLElementRenderer.hpp" +#include "../Renderer.hpp" +#include "../decorations/CHyprDropShadowDecoration.hpp" +#include "../OpenGL.hpp" +#include "../decorations/CHyprInnerGlowDecoration.hpp" +#include + +using namespace Render::GL; + +void CGLElementRenderer::draw(WP element, const CRegion& damage) { + const auto m_data = element->m_data; + if (m_data.hasGrad2) + g_pHyprOpenGL->renderBorder( + m_data.box, m_data.grad1, m_data.grad2, m_data.lerp, + {.round = m_data.round, .roundingPower = m_data.roundingPower, .borderSize = m_data.borderSize, .a = m_data.a, .outerRound = m_data.outerRound}); + else + g_pHyprOpenGL->renderBorder( + m_data.box, m_data.grad1, + {.round = m_data.round, .roundingPower = m_data.roundingPower, .borderSize = m_data.borderSize, .a = m_data.a, .outerRound = m_data.outerRound}); +}; + +void CGLElementRenderer::draw(WP element, const CRegion& damage) { + const auto& color = element->m_data.color; + RASSERT(g_pHyprRenderer->m_renderData.pMonitor, "Tried to render without begin()!"); + + TRACY_GPU_ZONE("RenderClear"); + const std::array c = {sc(color.r), sc(color.g), sc(color.b), sc(color.a)}; + + if (!g_pHyprRenderer->m_renderData.damage.empty()) { + g_pHyprRenderer->m_renderData.damage.forEachRect([&c](const auto& RECT) { + g_pHyprOpenGL->scissor(&RECT, g_pHyprRenderer->m_renderData.transformDamage); + glClearBufferfv(GL_COLOR, 0, c.data()); + }); + + g_pHyprOpenGL->scissor(nullptr); + } else + glClearBufferfv(GL_COLOR, 0, c.data()); +}; + +void CGLElementRenderer::draw(WP element, const CRegion& damage) { + Log::logger->log(Log::ERR, "Deprecated CFramebufferElement. Use g_pHyprRenderer->m_renderData and CTexPassElement instead"); + // const auto m_data = element->m_data; + // SP fb = nullptr; + + // if (m_data.main) { + // switch (m_data.framebufferID) { + // case FB_MONITOR_RENDER_MAIN: fb = g_pHyprRenderer->m_renderData.mainFB; break; + // case FB_MONITOR_RENDER_CURRENT: fb = g_pHyprRenderer->m_renderData.currentFB; break; + // case FB_MONITOR_RENDER_OUT: fb = g_pHyprRenderer->m_renderData.outFB; break; + // default: fb = nullptr; + // } + + // if (!fb) { + // Log::logger->log(Log::ERR, "BUG THIS: CFramebufferElement::draw: main but null"); + // return; + // } + + // } else { + // switch (m_data.framebufferID) { + // case FB_MONITOR_RENDER_EXTRA_OFFLOAD: fb = g_pHyprRenderer->m_renderData.pMonitor->m_offloadFB; break; + // case FB_MONITOR_RENDER_EXTRA_MIRROR: fb = g_pHyprRenderer->m_renderData.pMonitor->m_mirrorFB; break; + // case FB_MONITOR_RENDER_EXTRA_MIRROR_SWAP: fb = g_pHyprRenderer->m_renderData.pMonitor->m_mirrorSwapFB; break; + // case FB_MONITOR_RENDER_EXTRA_OFF_MAIN: fb = g_pHyprRenderer->m_renderData.pMonitor->m_offMainFB; break; + // case FB_MONITOR_RENDER_EXTRA_MONITOR_MIRROR: fb = g_pHyprRenderer->m_renderData.pMonitor->m_monitorMirrorFB; break; + // case FB_MONITOR_RENDER_EXTRA_BLUR: fb = g_pHyprRenderer->m_renderData.pMonitor->m_blurFB; break; + // default: fb = nullptr; + // } + + // if (!fb) { + // Log::logger->log(Log::ERR, "BUG THIS: CFramebufferElement::draw: not main but null"); + // return; + // } + // } + + // g_pHyprRenderer->bindFB(fb); +}; + +void CGLElementRenderer::draw(WP element, const CRegion& damage) { + auto dmg = damage; + g_pHyprRenderer->preBlurForCurrentMonitor(&dmg); +}; + +void CGLElementRenderer::draw(WP element, const CRegion& damage) { + const auto m_data = element->m_data; + + if (m_data.color.a == 1.F || !m_data.blur) + g_pHyprOpenGL->renderRect(m_data.box, m_data.color, {.damage = &damage, .round = m_data.round, .roundingPower = m_data.roundingPower}); + else + g_pHyprOpenGL->renderRect(m_data.box, m_data.color, + {.round = m_data.round, .roundingPower = m_data.roundingPower, .blur = true, .blurA = m_data.blurA, .xray = m_data.xray}); +}; + +void CGLElementRenderer::draw(WP element, const CRegion& damage) { + const auto m_data = element->m_data; + m_data.deco->render(g_pHyprRenderer->m_renderData.pMonitor.lock(), m_data.a); +}; + +void CGLElementRenderer::draw(WP element, const CRegion& damage) { + const auto m_data = element->m_data; + m_data.deco->render(g_pHyprRenderer->m_renderData.pMonitor.lock(), m_data.a); +}; + +void CGLElementRenderer::draw(WP element, const CRegion& damage) { + const auto m_data = element->m_data; + + g_pHyprOpenGL->renderTexture( // + m_data.tex, m_data.box, + { + // blur settings for m_data.blur == true + .blur = m_data.blur, + .blurA = m_data.blurA, + .overallA = m_data.overallA, + .blockBlurOptimization = m_data.blockBlurOptimization.value_or(false), + .blurredBG = m_data.blurredBG, + + // common settings + .damage = m_data.damage.empty() ? &damage : &m_data.damage, + .surface = m_data.surface, + .a = m_data.a, + .round = m_data.round, + .roundingPower = m_data.roundingPower, + .discardActive = m_data.discardActive, + .allowCustomUV = m_data.allowCustomUV, + .cmBackToSRGB = m_data.cmBackToSRGB, + .cmBackToSRGBSource = m_data.cmBackToSRGBSource, + .discardMode = m_data.ignoreAlpha.has_value() ? sc(DISCARD_ALPHA) : m_data.discardMode, + .discardOpacity = m_data.ignoreAlpha.has_value() ? *m_data.ignoreAlpha : m_data.discardOpacity, + .clipRegion = m_data.clipRegion, + .currentLS = m_data.currentLS, + + .primarySurfaceUVTopLeft = g_pHyprRenderer->m_renderData.primarySurfaceUVTopLeft, + .primarySurfaceUVBottomRight = g_pHyprRenderer->m_renderData.primarySurfaceUVBottomRight, + }); +}; + +void CGLElementRenderer::draw(WP element, const CRegion& damage) { + const auto m_data = element->m_data; + + g_pHyprOpenGL->renderTextureMatte(m_data.tex, m_data.box, m_data.fb); +}; \ No newline at end of file diff --git a/src/render/gl/GLElementRenderer.hpp b/src/render/gl/GLElementRenderer.hpp new file mode 100644 index 000000000..675714fed --- /dev/null +++ b/src/render/gl/GLElementRenderer.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "../ElementRenderer.hpp" + +namespace Render::GL { + class CGLElementRenderer : public Render::IElementRenderer { + public: + CGLElementRenderer() = default; + ~CGLElementRenderer() = default; + + private: + void draw(WP element, const Hyprutils::Math::CRegion& damage) override; + void draw(WP element, const CRegion& damage) override; + void draw(WP element, const CRegion& damage) override; + void draw(WP element, const CRegion& damage) override; + void draw(WP element, const CRegion& damage) override; + void draw(WP element, const CRegion& damage) override; + void draw(WP element, const CRegion& damage) override; + void draw(WP element, const CRegion& damage) override; + void draw(WP element, const CRegion& damage) override; + }; +} diff --git a/src/render/gl/GLFramebuffer.cpp b/src/render/gl/GLFramebuffer.cpp new file mode 100644 index 000000000..59c0c4e35 --- /dev/null +++ b/src/render/gl/GLFramebuffer.cpp @@ -0,0 +1,178 @@ +#include "GLFramebuffer.hpp" +#include "../OpenGL.hpp" +#include "../Renderer.hpp" +#include "macros.hpp" +#include "../Framebuffer.hpp" +#include + +using namespace Hyprgraphics::Egl; +using namespace Render::GL; + +CGLFramebuffer::CGLFramebuffer() : IFramebuffer() {} +CGLFramebuffer::CGLFramebuffer(const std::string& name) : IFramebuffer(name) {} + +bool CGLFramebuffer::internalAlloc(int w, int h, uint32_t drmFormat) { + g_pHyprOpenGL->makeEGLCurrent(); + + if (!m_tex) { + m_tex = g_pHyprRenderer->createTexture(); + m_tex->allocate({w, h}, drmFormat); + 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); + } + + if (!m_fbAllocated) { + glGenFramebuffers(1, &m_fb); + m_fbAllocated = true; + } + + const auto format = 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_mirrorTex) { + const auto format = getPixelFormatFromDRM(m_mirrorTex->m_drmFormat); + m_mirrorTex->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_ATTACHMENT1, GL_TEXTURE_2D, m_mirrorTex->m_texID, 0); + } else + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, 0, 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 {}", m_name, 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) { + const auto& size = g_pHyprRenderer->m_renderData.pMonitor ? g_pHyprRenderer->m_renderData.pMonitor->m_pixelSize : m_size; + g_pHyprOpenGL->setViewport(0, 0, size.x, size.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); + if (m_mirrorTex) + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, 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 = getPixelFormatFromDRM(shm.format); + if (!PFORMAT) { + LOGM(Log::ERR, "Can't copy: failed to find a pixel format"); + return false; + } + + g_pHyprOpenGL->makeEGLCurrent(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, getFBID()); + bind(); + + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + uint32_t packStride = 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()) { + if (PFORMAT->swizzle == SWIZZLE_RGBA) + glFormat = GL_RGBA; + else if (PFORMAT->swizzle == SWIZZLE_BGRA) + glFormat = GL_BGRA_EXT; + else { + LOGM(Log::ERR, "Copied frame via shm might be broken or color flipped"); + glFormat = GL_RGBA; + } + } + } + + // This could be optimized by using a pixel buffer object to make this async, + // but really clients should just use a dma buffer anyways. + if (packStride == sc(shm.stride)) { + glReadPixels(offsetX, offsetY, width > 0 ? width : m_size.x, height > 0 ? height : m_size.y, glFormat, PFORMAT->glType, pixelData); + } else { + const auto h = height > 0 ? height : m_size.y; + for (size_t i = 0; i < h; ++i) { + uint32_t y = i; + glReadPixels(offsetX, offsetY + y, width > 0 ? width : m_size.x, 1, glFormat, PFORMAT->glType, pixelData + i * shm.stride); + } + } + + unbind(); + glPixelStorei(GL_PACK_ALIGNMENT, 4); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + return true; +} + +CGLFramebuffer::~CGLFramebuffer() { + release(); +} + +GLuint CGLFramebuffer::getFBID() { + return m_fbAllocated ? m_fb : 0; +} + +void CGLFramebuffer::invalidate(const std::vector& attachments) { + if (!isAllocated()) + return; + + glInvalidateFramebuffer(GL_FRAMEBUFFER, attachments.size(), attachments.data()); +} diff --git a/src/render/gl/GLFramebuffer.hpp b/src/render/gl/GLFramebuffer.hpp new file mode 100644 index 000000000..1be5d0e02 --- /dev/null +++ b/src/render/gl/GLFramebuffer.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "../../defines.hpp" +#include "../Texture.hpp" +#include "../Framebuffer.hpp" +#include + +namespace Render::GL { + 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, DRMFormat format = DRM_FORMAT_ARGB8888) override; + + private: + GLuint m_fb = -1; + + friend class CGLRenderbuffer; + }; +} diff --git a/src/render/gl/GLRenderbuffer.cpp b/src/render/gl/GLRenderbuffer.cpp new file mode 100644 index 000000000..208957e5f --- /dev/null +++ b/src/render/gl/GLRenderbuffer.cpp @@ -0,0 +1,73 @@ +#include "GLRenderbuffer.hpp" +#include "../Renderer.hpp" +#include "../OpenGL.hpp" +#include "../../Compositor.hpp" +#include "../Framebuffer.hpp" +#include "GLFramebuffer.hpp" +#include "../Renderbuffer.hpp" +#include +#include +#include + +#include + +using namespace Render::GL; + +CGLRenderbuffer::~CGLRenderbuffer() { + if (!g_pCompositor || g_pCompositor->m_isShuttingDown || !g_pHyprRenderer) + return; + + g_pHyprOpenGL->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_pHyprOpenGL->makeEGLCurrent(); + g_pHyprRenderer->bindFB(m_framebuffer); +} + +void CGLRenderbuffer::unbind() { + GLFB(m_framebuffer)->unbind(); +} diff --git a/src/render/gl/GLRenderbuffer.hpp b/src/render/gl/GLRenderbuffer.hpp new file mode 100644 index 000000000..137a8e0d5 --- /dev/null +++ b/src/render/gl/GLRenderbuffer.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "../../helpers/memory/Memory.hpp" +#include "../Renderbuffer.hpp" +#include + +class CMonitor; + +namespace Render::GL { + class CGLRenderbuffer : public IRenderbuffer { + public: + CGLRenderbuffer(SP buffer, uint32_t format); + ~CGLRenderbuffer(); + + void bind() override; + void unbind() override; + + private: + void* m_image = nullptr; + GLuint m_rbo = 0; + }; +} diff --git a/src/render/gl/GLTexture.cpp b/src/render/gl/GLTexture.cpp new file mode 100644 index 000000000..3216290e6 --- /dev/null +++ b/src/render/gl/GLTexture.cpp @@ -0,0 +1,227 @@ +#include "GLTexture.hpp" +#include "../Renderer.hpp" +#include "../../Compositor.hpp" +#include "../../helpers/Format.hpp" +#include "../Texture.hpp" +#include +#include + +using namespace Hyprgraphics::Egl; +using namespace Render::GL; + +CGLTexture::CGLTexture(bool opaque) { + m_opaque = opaque; +} + +CGLTexture::~CGLTexture() { + if (!g_pCompositor || g_pCompositor->m_isShuttingDown || !g_pHyprRenderer) + return; + + g_pHyprOpenGL->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_pHyprOpenGL->makeEGLCurrent(); + + const auto format = 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 = isDrmFormatOpaque(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 = isDrmFormatOpaque(attrs.format) ? TEXTURE_RGBX : TEXTURE_RGBA; + //} + + allocate(attrs.size, attrs.format); + 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_pHyprOpenGL->makeEGLCurrent(); + + const auto format = getPixelFormatFromDRM(drmFormat); + ASSERT(format); + + bind(); + + if (format->swizzle.has_value()) + swizzle(format->swizzle.value()); + + bool alignmentChanged = false; + if (format->bytesPerBlock != 4) { + const GLint alignment = (stride % 4 == 0) ? 4 : 1; + GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, alignment)); + alignmentChanged = true; + } + + GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / format->bytesPerBlock)); + + damage.copy().intersect(CBox{{}, m_size}).forEachRect([&format, &pixels](const auto& rect) { + GLCALL(glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, rect.x1)); + GLCALL(glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, rect.y1)); + + int width = rect.x2 - rect.x1; + int height = rect.y2 - rect.y1; + GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x1, rect.y1, width, height, format->glFormat, format->glType, pixels)); + }); + + if (alignmentChanged) + GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 4)); + + GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0)); + GLCALL(glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0)); + GLCALL(glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0)); + + unbind(); + + if (m_keepDataCopy) { + m_dataCopy.resize(stride * m_size.y); + memcpy(m_dataCopy.data(), pixels, stride * m_size.y); + } +} + +void CGLTexture::allocate(const Vector2D& size, uint32_t drmFormat) { + if (!m_texID) + GLCALL(glGenTextures(1, &m_texID)); + m_size = size; + m_drmFormat = drmFormat; +} + +void CGLTexture::bind() { + GLCALL(glBindTexture(m_target, m_texID)); +} + +void CGLTexture::unbind() { + GLCALL(glBindTexture(m_target, 0)); +} + +bool CGLTexture::ok() { + return m_texID > 0; +} + +bool CGLTexture::isDMA() { + return m_eglImage; +} + +constexpr std::optional CGLTexture::getCacheStateIndex(GLenum pname) { + switch (pname) { + case GL_TEXTURE_WRAP_S: return TEXTURE_PAR_WRAP_S; + case GL_TEXTURE_WRAP_T: return TEXTURE_PAR_WRAP_T; + case GL_TEXTURE_MAG_FILTER: return TEXTURE_PAR_MAG_FILTER; + case GL_TEXTURE_MIN_FILTER: return TEXTURE_PAR_MIN_FILTER; + case GL_TEXTURE_SWIZZLE_R: return TEXTURE_PAR_SWIZZLE_R; + case GL_TEXTURE_SWIZZLE_B: return TEXTURE_PAR_SWIZZLE_B; + default: return std::nullopt; + } +} + +void CGLTexture::setTexParameter(GLenum pname, GLint param) { + const auto cacheIndex = getCacheStateIndex(pname); + + if (!cacheIndex) { + GLCALL(glTexParameteri(m_target, pname, param)); + return; + } + + const auto idx = cacheIndex.value(); + + if (m_cachedStates[idx] == param) + return; + + m_cachedStates[idx] = param; + GLCALL(glTexParameteri(m_target, pname, param)); +} + +void CGLTexture::swizzle(const std::array& colors) { + setTexParameter(GL_TEXTURE_SWIZZLE_R, colors.at(0)); + setTexParameter(GL_TEXTURE_SWIZZLE_G, colors.at(1)); + setTexParameter(GL_TEXTURE_SWIZZLE_B, colors.at(2)); + setTexParameter(GL_TEXTURE_SWIZZLE_A, colors.at(3)); +} diff --git a/src/render/gl/GLTexture.hpp b/src/render/gl/GLTexture.hpp new file mode 100644 index 000000000..07b51d9a4 --- /dev/null +++ b/src/render/gl/GLTexture.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include "../Texture.hpp" +#include +#include + +namespace Render::GL { + + 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/BorderPassElement.cpp b/src/render/pass/BorderPassElement.cpp index 13063290b..ef24224b2 100644 --- a/src/render/pass/BorderPassElement.cpp +++ b/src/render/pass/BorderPassElement.cpp @@ -1,21 +1,9 @@ #include "BorderPassElement.hpp" -#include "../OpenGL.hpp" CBorderPassElement::CBorderPassElement(const CBorderPassElement::SBorderData& data_) : m_data(data_) { ; } -void CBorderPassElement::draw(const CRegion& damage) { - if (m_data.hasGrad2) - g_pHyprOpenGL->renderBorder( - m_data.box, m_data.grad1, m_data.grad2, m_data.lerp, - {.round = m_data.round, .roundingPower = m_data.roundingPower, .borderSize = m_data.borderSize, .a = m_data.a, .outerRound = m_data.outerRound}); - else - g_pHyprOpenGL->renderBorder( - m_data.box, m_data.grad1, - {.round = m_data.round, .roundingPower = m_data.roundingPower, .borderSize = m_data.borderSize, .a = m_data.a, .outerRound = m_data.outerRound}); -} - bool CBorderPassElement::needsLiveBlur() { return false; } diff --git a/src/render/pass/BorderPassElement.hpp b/src/render/pass/BorderPassElement.hpp index 238b9ed5b..80e9a9efb 100644 --- a/src/render/pass/BorderPassElement.hpp +++ b/src/render/pass/BorderPassElement.hpp @@ -1,24 +1,22 @@ #pragma once #include "PassElement.hpp" -#include "../../config/ConfigDataValues.hpp" - -class CGradientValueData; +#include "../../config/shared/complex/ComplexDataTypes.hpp" class CBorderPassElement : public IPassElement { public: struct SBorderData { - CBox box; - CGradientValueData grad1, grad2; - bool hasGrad2 = false; - float lerp = 0.F, a = 1.F; - int round = 0, borderSize = 1, outerRound = -1; - float roundingPower = 2.F; + CBox box; + Config::CGradientValueData grad1, grad2; + bool hasGrad2 = false; + float lerp = 0.F, a = 1.F; + int round = 0, borderSize = 1, outerRound = -1; + float roundingPower = 2.F; + PHLWINDOWREF window; }; CBorderPassElement(const SBorderData& data_); virtual ~CBorderPassElement() = default; - virtual void draw(const CRegion& damage); virtual bool needsLiveBlur(); virtual bool needsPrecomputeBlur(); @@ -26,6 +24,9 @@ class CBorderPassElement : public IPassElement { return "CBorderPassElement"; } - private: + virtual ePassElementType type() { + return EK_BORDER; + }; + SBorderData m_data; }; diff --git a/src/render/pass/ClearPassElement.cpp b/src/render/pass/ClearPassElement.cpp index 256b857fb..850807233 100644 --- a/src/render/pass/ClearPassElement.cpp +++ b/src/render/pass/ClearPassElement.cpp @@ -1,14 +1,9 @@ #include "ClearPassElement.hpp" -#include "../OpenGL.hpp" CClearPassElement::CClearPassElement(const CClearPassElement::SClearData& data_) : m_data(data_) { ; } -void CClearPassElement::draw(const CRegion& damage) { - g_pHyprOpenGL->clear(m_data.color); -} - bool CClearPassElement::needsLiveBlur() { return false; } diff --git a/src/render/pass/ClearPassElement.hpp b/src/render/pass/ClearPassElement.hpp index 067b70942..112af442a 100644 --- a/src/render/pass/ClearPassElement.hpp +++ b/src/render/pass/ClearPassElement.hpp @@ -10,7 +10,6 @@ class CClearPassElement : public IPassElement { CClearPassElement(const SClearData& data); virtual ~CClearPassElement() = default; - virtual void draw(const CRegion& damage); virtual bool needsLiveBlur(); virtual bool needsPrecomputeBlur(); virtual std::optional boundingBox(); @@ -20,6 +19,9 @@ class CClearPassElement : public IPassElement { return "CClearPassElement"; } - private: + virtual ePassElementType type() { + return EK_CLEAR; + }; + SClearData m_data; }; diff --git a/src/render/pass/FramebufferElement.cpp b/src/render/pass/FramebufferElement.cpp index 7cfa8b4b7..047ce1e86 100644 --- a/src/render/pass/FramebufferElement.cpp +++ b/src/render/pass/FramebufferElement.cpp @@ -1,44 +1,9 @@ #include "FramebufferElement.hpp" -#include "../OpenGL.hpp" CFramebufferElement::CFramebufferElement(const CFramebufferElement::SFramebufferElementData& data_) : m_data(data_) { ; } -void CFramebufferElement::draw(const CRegion& damage) { - CFramebuffer* fb = nullptr; - - if (m_data.main) { - switch (m_data.framebufferID) { - case FB_MONITOR_RENDER_MAIN: fb = g_pHyprOpenGL->m_renderData.mainFB; break; - case FB_MONITOR_RENDER_CURRENT: fb = g_pHyprOpenGL->m_renderData.currentFB; break; - case FB_MONITOR_RENDER_OUT: fb = g_pHyprOpenGL->m_renderData.outFB; break; - } - - if (!fb) { - Debug::log(ERR, "BUG THIS: CFramebufferElement::draw: main but null"); - return; - } - - } 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; - } - - if (!fb) { - Debug::log(ERR, "BUG THIS: CFramebufferElement::draw: not main but null"); - return; - } - } - - fb->bind(); -} - bool CFramebufferElement::needsLiveBlur() { return false; } diff --git a/src/render/pass/FramebufferElement.hpp b/src/render/pass/FramebufferElement.hpp index 515c3380b..f2e1cc74d 100644 --- a/src/render/pass/FramebufferElement.hpp +++ b/src/render/pass/FramebufferElement.hpp @@ -11,7 +11,6 @@ class CFramebufferElement : public IPassElement { CFramebufferElement(const SFramebufferElementData& data_); virtual ~CFramebufferElement() = default; - virtual void draw(const CRegion& damage); virtual bool needsLiveBlur(); virtual bool needsPrecomputeBlur(); virtual bool undiscardable(); @@ -20,6 +19,9 @@ class CFramebufferElement : public IPassElement { return "CFramebufferElement"; } - private: + virtual ePassElementType type() { + return EK_FRAMEBUFFER; + }; + SFramebufferElementData m_data; }; \ No newline at end of file diff --git a/src/render/pass/InnerGlowPassElement.cpp b/src/render/pass/InnerGlowPassElement.cpp new file mode 100644 index 000000000..04ccebd71 --- /dev/null +++ b/src/render/pass/InnerGlowPassElement.cpp @@ -0,0 +1,13 @@ +#include "InnerGlowPassElement.hpp" + +CInnerGlowPassElement::CInnerGlowPassElement(const CInnerGlowPassElement::SInnerGlowData& data_) : m_data(data_) { + ; +} + +bool CInnerGlowPassElement::needsLiveBlur() { + return false; +} + +bool CInnerGlowPassElement::needsPrecomputeBlur() { + return false; +} diff --git a/src/render/pass/InnerGlowPassElement.hpp b/src/render/pass/InnerGlowPassElement.hpp new file mode 100644 index 000000000..62db77918 --- /dev/null +++ b/src/render/pass/InnerGlowPassElement.hpp @@ -0,0 +1,28 @@ +#pragma once +#include "PassElement.hpp" + +class CHyprInnerGlowDecoration; + +class CInnerGlowPassElement : public IPassElement { + public: + struct SInnerGlowData { + CHyprInnerGlowDecoration* deco = nullptr; + float a = 1.F; + }; + + CInnerGlowPassElement(const SInnerGlowData& data_); + virtual ~CInnerGlowPassElement() = default; + + virtual bool needsLiveBlur(); + virtual bool needsPrecomputeBlur(); + + virtual const char* passName() { + return "CInnerGlowPassElement"; + } + + virtual ePassElementType type() { + return EK_INNER_GLOW; + }; + + SInnerGlowData m_data; +}; diff --git a/src/render/pass/Pass.cpp b/src/render/pass/Pass.cpp index 8970e2af4..e4ef6f965 100644 --- a/src/render/pass/Pass.cpp +++ b/src/render/pass/Pass.cpp @@ -4,12 +4,16 @@ #include #include "../../Compositor.hpp" #include "../../config/ConfigValue.hpp" -#include "../../desktop/WLSurface.hpp" +#include "../../desktop/view/WLSurface.hpp" #include "../../managers/SeatManager.hpp" #include "../../managers/eventLoop/EventLoopManager.hpp" #include "../../render/Renderer.hpp" #include "../../desktop/state/FocusState.hpp" #include "../../protocols/core/Compositor.hpp" +#include "RectPassElement.hpp" +#include "macros.hpp" + +using namespace Render; bool CRenderPass::empty() const { return false; @@ -23,15 +27,13 @@ void CRenderPass::add(UP&& el) { m_passElements.emplace_back(makeUnique(CRegion{}, std::move(el))); } -void CRenderPass::simplify() { +void CRenderPass::simplify(bool willBlur, const CRegion& liveBlurRegion) { + const auto pMonitor = g_pHyprRenderer->m_renderData.pMonitor; static auto PDEBUGPASS = CConfigValue("debug:pass"); // TODO: use precompute blur for instances where there is nothing in between - // if there is live blur, we need to NOT occlude any area where it will be influenced - const auto WILLBLUR = std::ranges::any_of(m_passElements, [](const auto& el) { return el->element->needsLiveBlur(); }); - - CRegion newDamage = m_damage.copy().intersect(CBox{{}, g_pHyprOpenGL->m_renderData.pMonitor->m_transformedSize}); + CRegion newDamage = m_damage.copy().intersect(CBox{{}, pMonitor->m_transformedSize}); for (auto& el : m_passElements | std::views::reverse) { if (newDamage.empty() && !el->element->undiscardable()) { @@ -44,7 +46,7 @@ void CRenderPass::simplify() { if (!bb1 || newDamage.empty()) continue; - auto bb = bb1->scale(g_pHyprOpenGL->m_renderData.pMonitor->m_scale); + auto bb = bb1->scale(pMonitor->m_scale); // drop if empty if (CRegion copy = newDamage.copy(); copy.intersect(bb).empty()) { @@ -55,30 +57,20 @@ void CRenderPass::simplify() { auto opaque = el->element->opaqueRegion(); if (!opaque.empty()) { - opaque.scale(g_pHyprOpenGL->m_renderData.pMonitor->m_scale); + // scale and rounding is very particular so we have to use CBoxes scale and round functions + if (opaque.getRects().size() == 1) + opaque = opaque.getExtents().scale(pMonitor->m_scale).round(); + else { + CRegion scaledRegion; + opaque.forEachRect([&scaledRegion, pMonitor](const auto& RECT) { + scaledRegion.add(CBox(RECT.x1, RECT.y1, RECT.x2 - RECT.x1, RECT.y2 - RECT.y1).scale(pMonitor->m_scale).round()); + }); + opaque = scaledRegion; + } // if this intersects the liveBlur region, allow live blur to operate correctly. // do not occlude a border near it. - if (WILLBLUR) { - CRegion liveBlurRegion; - for (auto& el2 : m_passElements) { - // if we reach self, no problem, we can break. - // if the blur is above us, we don't care, it will work fine. - if (el2 == el) - break; - - if (!el2->element->needsLiveBlur()) - continue; - - const auto BB = el2->element->boundingBox(); - RASSERT(BB, "No bounding box for an element with live blur is illegal"); - - liveBlurRegion.add(*BB); - } - - // expand the region: this area needs to be proper to blur it right. - liveBlurRegion.scale(g_pHyprOpenGL->m_renderData.pMonitor->m_scale).expand(oneBlurRadius() * 2.F); - + if (willBlur) { if (auto infringement = opaque.copy().intersect(liveBlurRegion); !infringement.empty()) { // eh, this is not the correct solution, but it will do... // TODO: is this *easily* fixable? @@ -93,13 +85,13 @@ void CRenderPass::simplify() { if (*PDEBUGPASS) { for (auto& el2 : m_passElements) { - if (!el2->element->needsLiveBlur()) + if (!el2->element->needsLiveBlurCached) continue; const auto BB = el2->element->boundingBox(); RASSERT(BB, "No bounding box for an element with live blur is illegal"); - m_totalLiveBlurRegion.add(BB->copy().scale(g_pHyprOpenGL->m_renderData.pMonitor->m_scale)); + m_totalLiveBlurRegion.add(BB->copy().scale(pMonitor->m_scale)); } } } @@ -109,9 +101,29 @@ void CRenderPass::clear() { } CRegion CRenderPass::render(const CRegion& damage_) { + const auto pMonitor = g_pHyprRenderer->m_renderData.pMonitor; static auto PDEBUGPASS = CConfigValue("debug:pass"); - const auto WILLBLUR = std::ranges::any_of(m_passElements, [](const auto& el) { return el->element->needsLiveBlur(); }); + // single pass: cache blur results and gather aggregate info + bool willBlur = false, willDisableSimplification = false, willPrecomputeBlur = false; + CRegion blurRegion; + for (auto& el : m_passElements) { + el->element->needsLiveBlurCached = el->element->needsLiveBlur(); + el->element->needsPrecomputeBlurCached = el->element->needsPrecomputeBlur(); + + if (el->element->needsLiveBlurCached) { + willBlur = true; + const auto BB = el->element->boundingBox(); + RASSERT(BB, "No bounding box for an element with live blur is illegal"); + blurRegion.add(*BB); + } + + if (el->element->needsPrecomputeBlurCached) + willPrecomputeBlur = true; + + if (el->element->disableSimplification()) + willDisableSimplification = true; + } m_damage = *PDEBUGPASS ? CRegion{CBox{{}, {INT32_MAX, INT32_MAX}}} : damage_.copy(); if (*PDEBUGPASS) { @@ -120,8 +132,8 @@ CRegion CRenderPass::render(const CRegion& damage_) { } if (m_damage.empty()) { - g_pHyprOpenGL->m_renderData.damage = m_damage; - g_pHyprOpenGL->m_renderData.finalDamage = m_damage; + g_pHyprRenderer->m_renderData.damage = m_damage; + g_pHyprRenderer->m_renderData.finalDamage = m_damage; return m_damage; } @@ -129,29 +141,22 @@ CRegion CRenderPass::render(const CRegion& damage_) { m_debugData = {false}; else if (*PDEBUGPASS && !m_debugData.present) { m_debugData.present = true; - m_debugData.keyboardFocusText = g_pHyprOpenGL->renderText("keyboard", Colors::WHITE, 12); - m_debugData.pointerFocusText = g_pHyprOpenGL->renderText("pointer", Colors::WHITE, 12); - m_debugData.lastWindowText = g_pHyprOpenGL->renderText("lastWindow", Colors::WHITE, 12); + m_debugData.keyboardFocusText = g_pHyprRenderer->renderText("keyboard", Colors::WHITE, 12); + m_debugData.pointerFocusText = g_pHyprRenderer->renderText("pointer", Colors::WHITE, 12); + m_debugData.lastWindowText = g_pHyprRenderer->renderText("lastWindow", Colors::WHITE, 12); } - if (WILLBLUR && !*PDEBUGPASS) { - // combine blur regions into one that will be expanded - CRegion blurRegion; - for (auto& el : m_passElements) { - if (!el->element->needsLiveBlur()) - continue; + // precompute the expanded live blur region for simplify() to use + CRegion liveBlurRegion; + if (willBlur && !*PDEBUGPASS) { + blurRegion.scale(pMonitor->m_scale); - const auto BB = el->element->boundingBox(); - RASSERT(BB, "No bounding box for an element with live blur is illegal"); - - blurRegion.add(*BB); - } - - blurRegion.scale(g_pHyprOpenGL->m_renderData.pMonitor->m_scale); + // save a copy for simplify's occlusion test before we mutate for damage expansion + liveBlurRegion = blurRegion.copy().expand(oneBlurRadius() * 2.F); blurRegion.intersect(m_damage).expand(oneBlurRadius()); - g_pHyprOpenGL->m_renderData.finalDamage = blurRegion.copy().add(m_damage); + g_pHyprRenderer->m_renderData.finalDamage = blurRegion.copy().add(m_damage); // FIXME: why does this break on * 1.F ? // used to work when we expand all the damage... I think? Well, before pass. @@ -160,16 +165,17 @@ CRegion CRenderPass::render(const CRegion& damage_) { m_damage = blurRegion.copy().add(m_damage); } else - g_pHyprOpenGL->m_renderData.finalDamage = m_damage; + g_pHyprRenderer->m_renderData.finalDamage = m_damage; - if (std::ranges::any_of(m_passElements, [](const auto& el) { return el->element->disableSimplification(); })) { + if (g_pHyprRenderer->m_renderData.noSimplify || willDisableSimplification) { for (auto& el : m_passElements) { el->elementDamage = m_damage; } } else - simplify(); + simplify(willBlur, liveBlurRegion); - g_pHyprOpenGL->m_renderData.pCurrentMonData->blurFBShouldRender = std::ranges::any_of(m_passElements, [](const auto& el) { return el->element->needsPrecomputeBlur(); }); + if (g_pHyprRenderer->m_renderData.pMonitor) + g_pHyprRenderer->m_renderData.pMonitor->m_blurFBShouldRender = willPrecomputeBlur; if (m_passElements.empty()) return {}; @@ -180,8 +186,8 @@ CRegion CRenderPass::render(const CRegion& damage_) { continue; } - g_pHyprOpenGL->m_renderData.damage = el->elementDamage; - el->element->draw(el->elementDamage); + g_pHyprRenderer->m_renderData.damage = el->elementDamage; + g_pHyprRenderer->draw(el->element, el->elementDamage); } if (*PDEBUGPASS) { @@ -193,25 +199,27 @@ CRegion CRenderPass::render(const CRegion& damage_) { }); } - g_pHyprOpenGL->m_renderData.damage = m_damage; + g_pHyprRenderer->m_renderData.damage = m_damage; return m_damage; } void CRenderPass::renderDebugData() { - CBox box = {{}, g_pHyprOpenGL->m_renderData.pMonitor->m_transformedSize}; + const auto pMonitor = g_pHyprRenderer->m_renderData.pMonitor; + CBox box = {{}, pMonitor->m_transformedSize}; for (const auto& rg : m_occludedRegions) { - g_pHyprOpenGL->renderRect(box, Colors::RED.modifyA(0.1F), {.damage = &rg}); + g_pHyprRenderer->draw(CRectPassElement::SRectData{.box = box, .color = Colors::RED.modifyA(0.1F)}, rg); } - g_pHyprOpenGL->renderRect(box, Colors::GREEN.modifyA(0.1F), {.damage = &m_totalLiveBlurRegion}); + + g_pHyprRenderer->draw(CRectPassElement::SRectData{.box = box, .color = Colors::GREEN.modifyA(0.1F)}, m_totalLiveBlurRegion); std::unordered_map offsets; // render focus stuff - auto renderHLSurface = [&offsets](SP texture, SP surface, const CHyprColor& color) { + auto renderHLSurface = [&offsets, pMonitor, this](SP texture, SP surface, const CHyprColor& color) { if (!surface || !texture) return; - auto hlSurface = CWLSurface::fromResource(surface); + auto hlSurface = Desktop::View::CWLSurface::fromResource(surface); if (!hlSurface) return; @@ -220,14 +228,12 @@ void CRenderPass::renderDebugData() { if (!bb.has_value()) return; - CBox box = bb->copy().translate(-g_pHyprOpenGL->m_renderData.pMonitor->m_position).scale(g_pHyprOpenGL->m_renderData.pMonitor->m_scale); + CBox box = bb->copy().translate(-pMonitor->m_position).scale(pMonitor->m_scale); - if (box.intersection(CBox{{}, g_pHyprOpenGL->m_renderData.pMonitor->m_size}).empty()) + if (box.intersection(CBox{{}, pMonitor->m_size}).empty()) return; - static const auto FULL_REGION = CRegion{0, 0, INT32_MAX, INT32_MAX}; - - g_pHyprOpenGL->renderRect(box, color, {.damage = &FULL_REGION}); + g_pHyprRenderer->draw(CRectPassElement::SRectData{.box = box, .color = color}, m_damage); if (offsets.contains(surface.get())) box.translate(Vector2D{0.F, offsets[surface.get()]}); @@ -235,8 +241,16 @@ void CRenderPass::renderDebugData() { offsets[surface.get()] = 0; box = {box.pos(), texture->m_size}; - g_pHyprOpenGL->renderRect(box, CHyprColor{0.F, 0.F, 0.F, 0.2F}, {.damage = &FULL_REGION, .round = std::min(5.0, box.size().y)}); - g_pHyprOpenGL->renderTexture(texture, box, {}); + + g_pHyprRenderer->draw( + CRectPassElement::SRectData{ + .box = box, + .color = color, + .round = std::min(5.0, box.size().y), + }, + m_damage); + + g_pHyprRenderer->draw(CTexPassElement::SRenderData{.tex = texture, .box = box}, m_damage); offsets[surface.get()] += texture->m_size.y; }; @@ -244,51 +258,53 @@ void CRenderPass::renderDebugData() { renderHLSurface(m_debugData.keyboardFocusText, g_pSeatManager->m_state.keyboardFocus.lock(), Colors::PURPLE.modifyA(0.1F)); renderHLSurface(m_debugData.pointerFocusText, g_pSeatManager->m_state.pointerFocus.lock(), Colors::ORANGE.modifyA(0.1F)); if (Desktop::focusState()->window()) - renderHLSurface(m_debugData.lastWindowText, Desktop::focusState()->window()->m_wlSurface->resource(), Colors::LIGHT_BLUE.modifyA(0.1F)); + renderHLSurface(m_debugData.lastWindowText, Desktop::focusState()->window()->wlSurface()->resource(), Colors::LIGHT_BLUE.modifyA(0.1F)); if (g_pSeatManager->m_state.pointerFocus) { if (g_pSeatManager->m_state.pointerFocus->m_current.input.intersect(CBox{{}, g_pSeatManager->m_state.pointerFocus->m_current.size}).getExtents().size() != g_pSeatManager->m_state.pointerFocus->m_current.size) { - auto hlSurface = CWLSurface::fromResource(g_pSeatManager->m_state.pointerFocus.lock()); + auto hlSurface = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.pointerFocus.lock()); if (hlSurface) { auto BOX = hlSurface->getSurfaceBoxGlobal(); if (BOX) { - auto region = g_pSeatManager->m_state.pointerFocus->m_current.input.copy() - .scale(g_pHyprOpenGL->m_renderData.pMonitor->m_scale) - .translate(BOX->pos() - g_pHyprOpenGL->m_renderData.pMonitor->m_position); - g_pHyprOpenGL->renderRect(box, CHyprColor{0.8F, 0.8F, 0.2F, 0.4F}, {.damage = ®ion}); + g_pHyprRenderer->draw(CRectPassElement::SRectData{.box = box, .color = CHyprColor{0.8F, 0.8F, 0.2F, 0.4F}}, m_damage); } } } } const auto DISCARDED_ELEMENTS = std::ranges::count_if(m_passElements, [](const auto& e) { return e->discard; }); - auto tex = g_pHyprOpenGL->renderText(std::format("occlusion layers: {}\npass elements: {} ({} discarded)\nviewport: {:X0}", m_occludedRegions.size(), m_passElements.size(), - DISCARDED_ELEMENTS, g_pHyprOpenGL->m_renderData.pMonitor->m_pixelSize), - Colors::WHITE, 12); + auto tex = g_pHyprRenderer->renderText(std::format("occlusion layers: {}\npass elements: {} ({} discarded)\nviewport: {:X0}", m_occludedRegions.size(), m_passElements.size(), + DISCARDED_ELEMENTS, pMonitor->m_pixelSize), + Colors::WHITE, 12); - if (tex) { - box = CBox{{0.F, g_pHyprOpenGL->m_renderData.pMonitor->m_size.y - tex->m_size.y}, tex->m_size}.scale(g_pHyprOpenGL->m_renderData.pMonitor->m_scale); - g_pHyprOpenGL->renderTexture(tex, box, {}); - } + if (tex) + g_pHyprRenderer->draw( + CTexPassElement::SRenderData{ + .tex = tex, + .box = CBox{{0.F, pMonitor->m_size.y - tex->m_size.y}, tex->m_size}.scale(pMonitor->m_scale), + }, + m_damage); std::string passStructure; auto yn = [](const bool val) -> const char* { return val ? "yes" : "no"; }; auto tick = [](const bool val) -> const char* { return val ? "✔" : "✖"; }; for (const auto& el : m_passElements | std::views::reverse) { passStructure += std::format("{} {} (bb: {} op: {}, pb: {}, lb: {})\n", tick(!el->discard), el->element->passName(), yn(el->element->boundingBox().has_value()), - yn(!el->element->opaqueRegion().empty()), yn(el->element->needsPrecomputeBlur()), yn(el->element->needsLiveBlur())); + yn(!el->element->opaqueRegion().empty()), yn(el->element->needsPrecomputeBlurCached), yn(el->element->needsLiveBlurCached)); } if (!passStructure.empty()) passStructure.pop_back(); - tex = g_pHyprOpenGL->renderText(passStructure, Colors::WHITE, 12); - if (tex) { - box = CBox{{g_pHyprOpenGL->m_renderData.pMonitor->m_size.x - tex->m_size.x, g_pHyprOpenGL->m_renderData.pMonitor->m_size.y - tex->m_size.y}, tex->m_size}.scale( - g_pHyprOpenGL->m_renderData.pMonitor->m_scale); - g_pHyprOpenGL->renderTexture(tex, box, {}); - } + tex = g_pHyprRenderer->renderText(passStructure, Colors::WHITE, 12); + if (tex) + g_pHyprRenderer->draw( + CTexPassElement::SRenderData{ + .tex = tex, + .box = CBox{{pMonitor->m_size.x - tex->m_size.x, pMonitor->m_size.y - tex->m_size.y}, tex->m_size}.scale(pMonitor->m_scale), + }, + m_damage); } float CRenderPass::oneBlurRadius() { diff --git a/src/render/pass/Pass.hpp b/src/render/pass/Pass.hpp index 435b53015..898dd5988 100644 --- a/src/render/pass/Pass.hpp +++ b/src/render/pass/Pass.hpp @@ -4,40 +4,43 @@ #include "PassElement.hpp" class CGradientValueData; -class CTexture; -class CRenderPass { - public: - bool empty() const; - bool single() const; +namespace Render { + class ITexture; - void add(UP&& elem); - void clear(); - void removeAllOfType(const std::string& type); + class CRenderPass { + public: + bool empty() const; + bool single() const; - CRegion render(const CRegion& damage_); + void add(UP&& elem); + void clear(); + void removeAllOfType(const std::string& type); - private: - CRegion m_damage; - std::vector m_occludedRegions; - CRegion m_totalLiveBlurRegion; + CRegion render(const CRegion& damage_); - struct SPassElementData { - CRegion elementDamage; - UP element; - bool discard = false; + private: + CRegion m_damage; + std::vector m_occludedRegions; + CRegion m_totalLiveBlurRegion; + + struct SPassElementData { + CRegion elementDamage; + UP element; + bool discard = false; + }; + + std::vector> m_passElements; + + void simplify(bool willBlur, const CRegion& liveBlurRegion); + float oneBlurRadius(); + void renderDebugData(); + + struct { + bool present = false; + SP keyboardFocusText, pointerFocusText, lastWindowText; + } m_debugData; + + friend class CHyprOpenGLImpl; }; - - std::vector> m_passElements; - - void simplify(); - float oneBlurRadius(); - void renderDebugData(); - - struct { - bool present = false; - SP keyboardFocusText, pointerFocusText, lastWindowText; - } m_debugData; - - friend class CHyprOpenGLImpl; -}; +} diff --git a/src/render/pass/PassElement.cpp b/src/render/pass/PassElement.cpp index 3ae52ef5a..ae24680a3 100644 --- a/src/render/pass/PassElement.cpp +++ b/src/render/pass/PassElement.cpp @@ -19,3 +19,7 @@ void IPassElement::discard() { bool IPassElement::undiscardable() { return false; } + +std::vector> IPassElement::draw() { + return {}; +} diff --git a/src/render/pass/PassElement.hpp b/src/render/pass/PassElement.hpp index a006ce9e0..a3715b2fa 100644 --- a/src/render/pass/PassElement.hpp +++ b/src/render/pass/PassElement.hpp @@ -1,19 +1,41 @@ #pragma once #include "../../defines.hpp" -#include +#include + +enum ePassElementType : uint8_t { + EK_UNKNOWN = 0, + EK_BORDER, + EK_CLEAR, + EK_FRAMEBUFFER, + EK_PRE_BLUR, + EK_RECT, + EK_HINTS, + EK_SHADOW, + EK_SURFACE, + EK_TEXTURE, + EK_TEXTURE_MATTE, + EK_INNER_GLOW, + EK_CUSTOM, +}; class IPassElement { public: virtual ~IPassElement() = default; - virtual void draw(const CRegion& damage) = 0; - virtual bool needsLiveBlur() = 0; - virtual bool needsPrecomputeBlur() = 0; - virtual const char* passName() = 0; + virtual std::vector> draw(); + // + virtual bool needsLiveBlur() = 0; + virtual bool needsPrecomputeBlur() = 0; + virtual const char* passName() = 0; + virtual ePassElementType type() = 0; virtual void discard(); virtual bool undiscardable(); virtual std::optional boundingBox(); // in monitor-local logical coordinates virtual CRegion opaqueRegion(); // in monitor-local logical coordinates virtual bool disableSimplification(); + + // cached results, computed once per frame in CRenderPass::render() + bool needsLiveBlurCached = false; + bool needsPrecomputeBlurCached = false; }; diff --git a/src/render/pass/PreBlurElement.cpp b/src/render/pass/PreBlurElement.cpp index 6f2822326..9c054b930 100644 --- a/src/render/pass/PreBlurElement.cpp +++ b/src/render/pass/PreBlurElement.cpp @@ -1,12 +1,7 @@ #include "PreBlurElement.hpp" -#include "../OpenGL.hpp" CPreBlurElement::CPreBlurElement() = default; -void CPreBlurElement::draw(const CRegion& damage) { - g_pHyprOpenGL->preBlurForCurrentMonitor(); -} - bool CPreBlurElement::needsLiveBlur() { return false; } diff --git a/src/render/pass/PreBlurElement.hpp b/src/render/pass/PreBlurElement.hpp index 80474a298..fa8a875c5 100644 --- a/src/render/pass/PreBlurElement.hpp +++ b/src/render/pass/PreBlurElement.hpp @@ -6,7 +6,6 @@ class CPreBlurElement : public IPassElement { CPreBlurElement(); virtual ~CPreBlurElement() = default; - virtual void draw(const CRegion& damage); virtual bool needsLiveBlur(); virtual bool needsPrecomputeBlur(); virtual bool disableSimplification(); @@ -15,4 +14,8 @@ class CPreBlurElement : public IPassElement { virtual const char* passName() { return "CPreBlurElement"; } + + virtual ePassElementType type() { + return EK_PRE_BLUR; + }; }; \ No newline at end of file diff --git a/src/render/pass/RectPassElement.cpp b/src/render/pass/RectPassElement.cpp index 6c60741ef..042dfd2a4 100644 --- a/src/render/pass/RectPassElement.cpp +++ b/src/render/pass/RectPassElement.cpp @@ -1,26 +1,10 @@ #include "RectPassElement.hpp" -#include "../OpenGL.hpp" +#include "../Renderer.hpp" CRectPassElement::CRectPassElement(const CRectPassElement::SRectData& data_) : m_data(data_) { ; } -void CRectPassElement::draw(const CRegion& damage) { - if (m_data.box.w <= 0 || m_data.box.h <= 0) - return; - - if (!m_data.clipBox.empty()) - g_pHyprOpenGL->m_renderData.clipBox = m_data.clipBox; - - if (m_data.color.a == 1.F || !m_data.blur) - g_pHyprOpenGL->renderRect(m_data.box, m_data.color, {.damage = &damage, .round = m_data.round, .roundingPower = m_data.roundingPower}); - else - g_pHyprOpenGL->renderRect(m_data.box, m_data.color, - {.round = m_data.round, .roundingPower = m_data.roundingPower, .blur = true, .blurA = m_data.blurA, .xray = m_data.xray}); - - g_pHyprOpenGL->m_renderData.clipBox = {}; -} - bool CRectPassElement::needsLiveBlur() { return m_data.color.a < 1.F && !m_data.xray && m_data.blur; } @@ -30,7 +14,7 @@ bool CRectPassElement::needsPrecomputeBlur() { } std::optional CRectPassElement::boundingBox() { - return m_data.box.copy().scale(1.F / g_pHyprOpenGL->m_renderData.pMonitor->m_scale).round(); + return m_data.box.copy().scale(1.F / g_pHyprRenderer->m_renderData.pMonitor->m_scale).round(); } CRegion CRectPassElement::opaqueRegion() { diff --git a/src/render/pass/RectPassElement.hpp b/src/render/pass/RectPassElement.hpp index c83d52ec9..a61ab1558 100644 --- a/src/render/pass/RectPassElement.hpp +++ b/src/render/pass/RectPassElement.hpp @@ -1,5 +1,6 @@ #pragma once #include "PassElement.hpp" +#include class CRectPassElement : public IPassElement { public: @@ -11,12 +12,17 @@ class CRectPassElement : public IPassElement { bool blur = false, xray = false; float blurA = 1.F; CBox clipBox; + + // internal + CBox modifiedBox; + float TOPLEFT[2]; + float FULLSIZE[2]; + CRegion drawRegion; }; CRectPassElement(const SRectData& data); virtual ~CRectPassElement() = default; - virtual void draw(const CRegion& damage); virtual bool needsLiveBlur(); virtual bool needsPrecomputeBlur(); virtual std::optional boundingBox(); @@ -26,6 +32,9 @@ class CRectPassElement : public IPassElement { return "CRectPassElement"; } - private: + virtual ePassElementType type() { + return EK_RECT; + }; + SRectData m_data; }; diff --git a/src/render/pass/RendererHintsPassElement.cpp b/src/render/pass/RendererHintsPassElement.cpp index 2ad682040..6e81d9c53 100644 --- a/src/render/pass/RendererHintsPassElement.cpp +++ b/src/render/pass/RendererHintsPassElement.cpp @@ -1,15 +1,9 @@ #include "RendererHintsPassElement.hpp" -#include "../OpenGL.hpp" CRendererHintsPassElement::CRendererHintsPassElement(const CRendererHintsPassElement::SData& data_) : m_data(data_) { ; } -void CRendererHintsPassElement::draw(const CRegion& damage) { - if (m_data.renderModif.has_value()) - g_pHyprOpenGL->m_renderData.renderModif = *m_data.renderModif; -} - bool CRendererHintsPassElement::needsLiveBlur() { return false; } diff --git a/src/render/pass/RendererHintsPassElement.hpp b/src/render/pass/RendererHintsPassElement.hpp index d56a0cd60..0a24ff6de 100644 --- a/src/render/pass/RendererHintsPassElement.hpp +++ b/src/render/pass/RendererHintsPassElement.hpp @@ -1,18 +1,16 @@ #pragma once #include "PassElement.hpp" -#include -#include "../OpenGL.hpp" +#include "../types.hpp" class CRendererHintsPassElement : public IPassElement { public: struct SData { - std::optional renderModif; + std::optional renderModif; }; CRendererHintsPassElement(const SData& data); virtual ~CRendererHintsPassElement() = default; - virtual void draw(const CRegion& damage); virtual bool needsLiveBlur(); virtual bool needsPrecomputeBlur(); virtual bool undiscardable(); @@ -21,6 +19,9 @@ class CRendererHintsPassElement : public IPassElement { return "CRendererHintsPassElement"; } - private: + virtual ePassElementType type() { + return EK_HINTS; + }; + SData m_data; }; \ No newline at end of file diff --git a/src/render/pass/ShadowPassElement.cpp b/src/render/pass/ShadowPassElement.cpp index 45ad18c8f..da5601a36 100644 --- a/src/render/pass/ShadowPassElement.cpp +++ b/src/render/pass/ShadowPassElement.cpp @@ -1,15 +1,9 @@ #include "ShadowPassElement.hpp" -#include "../OpenGL.hpp" -#include "../decorations/CHyprDropShadowDecoration.hpp" CShadowPassElement::CShadowPassElement(const CShadowPassElement::SShadowData& data_) : m_data(data_) { ; } -void CShadowPassElement::draw(const CRegion& damage) { - m_data.deco->render(g_pHyprOpenGL->m_renderData.pMonitor.lock(), m_data.a); -} - bool CShadowPassElement::needsLiveBlur() { return false; } diff --git a/src/render/pass/ShadowPassElement.hpp b/src/render/pass/ShadowPassElement.hpp index 028ac88c7..ee1761029 100644 --- a/src/render/pass/ShadowPassElement.hpp +++ b/src/render/pass/ShadowPassElement.hpp @@ -13,7 +13,6 @@ class CShadowPassElement : public IPassElement { CShadowPassElement(const SShadowData& data_); virtual ~CShadowPassElement() = default; - virtual void draw(const CRegion& damage); virtual bool needsLiveBlur(); virtual bool needsPrecomputeBlur(); @@ -21,6 +20,9 @@ class CShadowPassElement : public IPassElement { return "CShadowPassElement"; } - private: + virtual ePassElementType type() { + return EK_SHADOW; + }; + SShadowData m_data; }; diff --git a/src/render/pass/SurfacePassElement.cpp b/src/render/pass/SurfacePassElement.cpp index 36b9e5f9e..eef3c11bf 100644 --- a/src/render/pass/SurfacePassElement.cpp +++ b/src/render/pass/SurfacePassElement.cpp @@ -1,10 +1,11 @@ #include "SurfacePassElement.hpp" #include "../OpenGL.hpp" -#include "../../desktop/WLSurface.hpp" -#include "../../desktop/Window.hpp" +#include "../../desktop/view/WLSurface.hpp" +#include "../../desktop/view/Window.hpp" #include "../../protocols/core/Compositor.hpp" #include "../../protocols/DRMSyncobj.hpp" #include "../../managers/input/InputManager.hpp" +#include "../../layout/LayoutManager.hpp" #include "../Renderer.hpp" #include @@ -16,162 +17,21 @@ CSurfacePassElement::CSurfacePassElement(const CSurfacePassElement::SRenderData& ; } -void CSurfacePassElement::draw(const CRegion& damage) { - g_pHyprOpenGL->m_renderData.currentWindow = m_data.pWindow; - g_pHyprOpenGL->m_renderData.surface = m_data.surface; - g_pHyprOpenGL->m_renderData.currentLS = m_data.pLS; - g_pHyprOpenGL->m_renderData.clipBox = m_data.clipBox; - g_pHyprOpenGL->m_renderData.discardMode = m_data.discardMode; - g_pHyprOpenGL->m_renderData.discardOpacity = m_data.discardOpacity; - g_pHyprOpenGL->m_renderData.useNearestNeighbor = m_data.useNearestNeighbor; - g_pHyprOpenGL->pushMonitorTransformEnabled(m_data.flipEndFrame); - - CScopeGuard x = {[]() { - g_pHyprOpenGL->m_renderData.primarySurfaceUVTopLeft = Vector2D(-1, -1); - g_pHyprOpenGL->m_renderData.primarySurfaceUVBottomRight = Vector2D(-1, -1); - g_pHyprOpenGL->m_renderData.useNearestNeighbor = false; - g_pHyprOpenGL->m_renderData.clipBox = {}; - g_pHyprOpenGL->m_renderData.clipRegion = {}; - g_pHyprOpenGL->m_renderData.discardMode = 0; - g_pHyprOpenGL->m_renderData.discardOpacity = 0; - g_pHyprOpenGL->m_renderData.useNearestNeighbor = false; - g_pHyprOpenGL->popMonitorTransformEnabled(); - g_pHyprOpenGL->m_renderData.currentWindow.reset(); - g_pHyprOpenGL->m_renderData.surface.reset(); - g_pHyprOpenGL->m_renderData.currentLS.reset(); - }}; - - if (!m_data.texture) - return; - - const auto& TEXTURE = m_data.texture; - - // this is bad, probably has been logged elsewhere. Means the texture failed - // uploading to the GPU. - if (!TEXTURE->m_texID) - return; - - const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_pInputManager->m_currentlyDraggedWindow && g_pInputManager->m_dragMode == MBIND_RESIZE; - TRACY_GPU_ZONE("RenderSurface"); - - auto PSURFACE = CWLSurface::fromResource(m_data.surface); - - const float ALPHA = m_data.alpha * m_data.fadeAlpha * (PSURFACE ? PSURFACE->m_alphaModifier : 1.F); - const float OVERALL_ALPHA = PSURFACE ? PSURFACE->m_overallOpacity : 1.F; - const bool BLUR = m_data.blur && (!TEXTURE->m_opaque || ALPHA < 1.F || OVERALL_ALPHA < 1.F); - - auto windowBox = getTexBox(); - - const auto PROJSIZEUNSCALED = windowBox.size(); - - windowBox.scale(m_data.pMonitor->m_scale); - windowBox.round(); - - if (windowBox.width <= 1 || windowBox.height <= 1) { - discard(); - return; - } - - const bool MISALIGNEDFSV1 = std::floor(m_data.pMonitor->m_scale) != m_data.pMonitor->m_scale /* Fractional */ && m_data.surface->m_current.scale == 1 /* fs protocol */ && - windowBox.size() != m_data.surface->m_current.bufferSize /* misaligned */ && DELTALESSTHAN(windowBox.width, m_data.surface->m_current.bufferSize.x, 3) && - DELTALESSTHAN(windowBox.height, m_data.surface->m_current.bufferSize.y, 3) /* off by one-or-two */ && - (!m_data.pWindow || (!m_data.pWindow->m_realSize->isBeingAnimated() && !INTERACTIVERESIZEINPROGRESS)) /* not window or not animated/resizing */ && - (!m_data.pLS || (!m_data.pLS->m_realSize->isBeingAnimated())); /* not LS or not animated */ - - g_pHyprRenderer->calculateUVForSurface(m_data.pWindow, m_data.surface, m_data.pMonitor->m_self.lock(), m_data.mainSurface, windowBox.size(), PROJSIZEUNSCALED, MISALIGNEDFSV1); - - auto cancelRender = false; - g_pHyprOpenGL->m_renderData.clipRegion = visibleRegion(cancelRender); - if (cancelRender) - return; - - // check for fractional scale surfaces misaligning the buffer size - // in those cases it's better to just force nearest neighbor - // as long as the window is not animated. During those it'd look weird. - // UV will fixup it as well - if (MISALIGNEDFSV1) - g_pHyprOpenGL->m_renderData.useNearestNeighbor = true; - - float rounding = m_data.rounding; - float roundingPower = m_data.roundingPower; - - rounding -= 1; // to fix a border issue - - if (m_data.dontRound) { - rounding = 0; - roundingPower = 2.0f; - } - - const bool WINDOWOPAQUE = m_data.pWindow && m_data.pWindow->m_wlSurface->resource() == m_data.surface ? m_data.pWindow->opaque() : false; - const bool CANDISABLEBLEND = ALPHA >= 1.f && OVERALL_ALPHA >= 1.f && rounding == 0 && WINDOWOPAQUE; - - if (CANDISABLEBLEND) - g_pHyprOpenGL->blend(false); - else - g_pHyprOpenGL->blend(true); - - // FIXME: This is wrong and will bug the blur out as shit if the first surface - // is a subsurface that does NOT cover the entire frame. In such cases, we probably should fall back - // to what we do for misaligned surfaces (blur the entire thing and then render shit without blur) - if (m_data.surfaceCounter == 0 && !m_data.popup) { - if (BLUR) - g_pHyprOpenGL->renderTexture(TEXTURE, windowBox, - { - .surface = m_data.surface, - .a = ALPHA, - .blur = true, - .blurA = m_data.fadeAlpha, - .overallA = OVERALL_ALPHA, - .round = rounding, - .roundingPower = roundingPower, - .allowCustomUV = true, - .blockBlurOptimization = m_data.blockBlurOptimization, - }); - else - g_pHyprOpenGL->renderTexture(TEXTURE, windowBox, - {.a = ALPHA * OVERALL_ALPHA, .round = rounding, .roundingPower = roundingPower, .discardActive = false, .allowCustomUV = true}); - } else { - if (BLUR && m_data.popup) - g_pHyprOpenGL->renderTexture(TEXTURE, windowBox, - { - .surface = m_data.surface, - .a = ALPHA, - .blur = true, - .blurA = m_data.fadeAlpha, - .overallA = OVERALL_ALPHA, - .round = rounding, - .roundingPower = roundingPower, - .allowCustomUV = true, - .blockBlurOptimization = true, - }); - else - g_pHyprOpenGL->renderTexture(TEXTURE, windowBox, - {.a = ALPHA * OVERALL_ALPHA, .round = rounding, .roundingPower = roundingPower, .discardActive = false, .allowCustomUV = true}); - } - - if (!g_pHyprRenderer->m_bBlockSurfaceFeedback) - m_data.surface->presentFeedback(m_data.when, m_data.pMonitor->m_self.lock()); - - // add async (dmabuf) buffers to usedBuffers so we can handle release later - // sync (shm) buffers will be released in commitState, so no need to track them here - if (m_data.surface->m_current.buffer && !m_data.surface->m_current.buffer->isSynchronous()) - g_pHyprRenderer->m_usedAsyncBuffers.emplace_back(m_data.surface->m_current.buffer); - - g_pHyprOpenGL->blend(true); -} - CBox CSurfacePassElement::getTexBox() { + if (m_texBoxCached) + return m_cachedTexBox; + const double outputX = -m_data.pMonitor->m_position.x, outputY = -m_data.pMonitor->m_position.y; - const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_pInputManager->m_currentlyDraggedWindow && g_pInputManager->m_dragMode == MBIND_RESIZE; - auto PSURFACE = CWLSurface::fromResource(m_data.surface); + const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_layoutManager->dragController()->target() && g_layoutManager->dragController()->mode() == MBIND_RESIZE; + auto PSURFACE = Desktop::View::CWLSurface::fromResource(m_data.surface); CBox windowBox; if (m_data.surface && m_data.mainSurface) { windowBox = {sc(outputX) + m_data.pos.x + m_data.localPos.x, sc(outputY) + m_data.pos.y + m_data.localPos.y, m_data.w, m_data.h}; // however, if surface buffer w / h < box, we need to adjust them - const auto PWINDOW = PSURFACE ? PSURFACE->getWindow() : nullptr; + const auto PWINDOW = PSURFACE ? Desktop::View::CWindow::fromView(PSURFACE->view()) : nullptr; // center the surface if it's smaller than the viewport we assign it if (PSURFACE && !PSURFACE->m_fillIgnoreSmall && PSURFACE->small() /* guarantees PWINDOW */) { @@ -212,11 +72,14 @@ CBox CSurfacePassElement::getTexBox() { windowBox.height = m_data.h - m_data.localPos.y; } - return windowBox; + m_cachedTexBox = windowBox; + m_texBoxCached = true; + + return m_cachedTexBox; } bool CSurfacePassElement::needsLiveBlur() { - auto PSURFACE = CWLSurface::fromResource(m_data.surface); + auto PSURFACE = Desktop::View::CWLSurface::fromResource(m_data.surface); const float ALPHA = m_data.alpha * m_data.fadeAlpha * (PSURFACE ? PSURFACE->m_alphaModifier * PSURFACE->m_overallOpacity : 1.F); const bool BLUR = m_data.blur && (!m_data.texture || !m_data.texture->m_opaque || ALPHA < 1.F); @@ -227,13 +90,13 @@ bool CSurfacePassElement::needsLiveBlur() { if (m_data.popup) return BLUR; - const bool NEWOPTIM = g_pHyprOpenGL->shouldUseNewBlurOptimizations(m_data.pLS, m_data.pWindow); + const bool NEWOPTIM = g_pHyprRenderer->shouldUseNewBlurOptimizations(m_data.pLS, m_data.pWindow); return BLUR && !NEWOPTIM; } bool CSurfacePassElement::needsPrecomputeBlur() { - auto PSURFACE = CWLSurface::fromResource(m_data.surface); + auto PSURFACE = Desktop::View::CWLSurface::fromResource(m_data.surface); const float ALPHA = m_data.alpha * m_data.fadeAlpha * (PSURFACE ? PSURFACE->m_alphaModifier * PSURFACE->m_overallOpacity : 1.F); const bool BLUR = m_data.blur && (!m_data.texture || !m_data.texture->m_opaque || ALPHA < 1.F); @@ -244,7 +107,7 @@ bool CSurfacePassElement::needsPrecomputeBlur() { if (m_data.popup) return false; - const bool NEWOPTIM = g_pHyprOpenGL->shouldUseNewBlurOptimizations(m_data.pLS, m_data.pWindow); + const bool NEWOPTIM = g_pHyprRenderer->shouldUseNewBlurOptimizations(m_data.pLS, m_data.pWindow); return BLUR && NEWOPTIM; } @@ -254,7 +117,7 @@ std::optional CSurfacePassElement::boundingBox() { } CRegion CSurfacePassElement::opaqueRegion() { - auto PSURFACE = CWLSurface::fromResource(m_data.surface); + auto PSURFACE = Desktop::View::CWLSurface::fromResource(m_data.surface); const float ALPHA = m_data.alpha * m_data.fadeAlpha * (PSURFACE ? PSURFACE->m_alphaModifier * PSURFACE->m_overallOpacity : 1.F); @@ -272,7 +135,7 @@ CRegion CSurfacePassElement::opaqueRegion() { } CRegion CSurfacePassElement::visibleRegion(bool& cancel) { - auto PSURFACE = CWLSurface::fromResource(m_data.surface); + auto PSURFACE = Desktop::View::CWLSurface::fromResource(m_data.surface); if (!PSURFACE) return {}; @@ -292,8 +155,8 @@ CRegion CSurfacePassElement::visibleRegion(bool& cancel) { // deal with any rounding errors that might come from scaling visibleRegion.expand(1); - auto uvTL = g_pHyprOpenGL->m_renderData.primarySurfaceUVTopLeft; - auto uvBR = g_pHyprOpenGL->m_renderData.primarySurfaceUVBottomRight; + auto uvTL = g_pHyprRenderer->m_renderData.primarySurfaceUVTopLeft; + auto uvBR = g_pHyprRenderer->m_renderData.primarySurfaceUVBottomRight; if (uvTL == Vector2D(-1, -1)) uvTL = Vector2D(0, 0); @@ -315,7 +178,7 @@ CRegion CSurfacePassElement::visibleRegion(bool& cancel) { void CSurfacePassElement::discard() { if (!g_pHyprRenderer->m_bBlockSurfaceFeedback) { - Debug::log(TRACE, "discard for invisible surface"); + Log::logger->log(Log::TRACE, "discard for invisible surface"); m_data.surface->presentFeedback(m_data.when, m_data.pMonitor->m_self.lock(), true); } } diff --git a/src/render/pass/SurfacePassElement.hpp b/src/render/pass/SurfacePassElement.hpp index f4dbb45a4..26e148906 100644 --- a/src/render/pass/SurfacePassElement.hpp +++ b/src/render/pass/SurfacePassElement.hpp @@ -1,10 +1,13 @@ #pragma once #include "PassElement.hpp" +#include "TexPassElement.hpp" #include #include "../../helpers/time/Time.hpp" class CWLSurfaceResource; -class CTexture; +namespace Render { + class ITexture; +} class CSyncTimeline; class CSurfacePassElement : public IPassElement { @@ -16,7 +19,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; @@ -41,7 +44,7 @@ class CSurfacePassElement : public IPassElement { CBox clipBox = {}; // scaled coordinates - uint32_t discardMode = 0; + uint32_t discardMode = DISCARD_OPAQUE; float discardOpacity = 0.f; bool useNearestNeighbor = false; @@ -52,7 +55,6 @@ class CSurfacePassElement : public IPassElement { CSurfacePassElement(const SRenderData& data); virtual ~CSurfacePassElement() = default; - virtual void draw(const CRegion& damage); virtual bool needsLiveBlur(); virtual bool needsPrecomputeBlur(); virtual std::optional boundingBox(); @@ -64,8 +66,15 @@ class CSurfacePassElement : public IPassElement { return "CSurfacePassElement"; } - private: + virtual ePassElementType type() { + return EK_SURFACE; + }; + SRenderData m_data; CBox getTexBox(); + + private: + bool m_texBoxCached = false; + CBox m_cachedTexBox = {}; }; diff --git a/src/render/pass/TexPassElement.cpp b/src/render/pass/TexPassElement.cpp index 39853a642..94b4cf762 100644 --- a/src/render/pass/TexPassElement.cpp +++ b/src/render/pass/TexPassElement.cpp @@ -1,8 +1,5 @@ #include "TexPassElement.hpp" -#include "../OpenGL.hpp" - -#include -using namespace Hyprutils::Utils; +#include "../Renderer.hpp" CTexPassElement::CTexPassElement(const SRenderData& data) : m_data(data) { ; @@ -12,47 +9,6 @@ CTexPassElement::CTexPassElement(CTexPassElement::SRenderData&& data) : m_data(s ; } -void CTexPassElement::draw(const CRegion& damage) { - g_pHyprOpenGL->pushMonitorTransformEnabled(m_data.flipEndFrame); - - CScopeGuard x = {[this]() { - // - g_pHyprOpenGL->popMonitorTransformEnabled(); - g_pHyprOpenGL->m_renderData.clipBox = {}; - if (m_data.replaceProjection) - g_pHyprOpenGL->m_renderData.monitorProjection = g_pHyprOpenGL->m_renderData.pMonitor->m_projMatrix; - if (m_data.ignoreAlpha.has_value()) - g_pHyprOpenGL->m_renderData.discardMode = 0; - }}; - - if (!m_data.clipBox.empty()) - g_pHyprOpenGL->m_renderData.clipBox = m_data.clipBox; - - if (m_data.replaceProjection) - g_pHyprOpenGL->m_renderData.monitorProjection = *m_data.replaceProjection; - - if (m_data.ignoreAlpha.has_value()) { - g_pHyprOpenGL->m_renderData.discardMode = DISCARD_ALPHA; - g_pHyprOpenGL->m_renderData.discardOpacity = *m_data.ignoreAlpha; - } - - if (m_data.blur) { - g_pHyprOpenGL->renderTexture(m_data.tex, m_data.box, - { - .a = m_data.a, - .blur = true, - .blurA = m_data.blurA, - .overallA = 1.F, - .round = m_data.round, - .roundingPower = m_data.roundingPower, - .blockBlurOptimization = m_data.blockBlurOptimization.value_or(false), - }); - } else { - g_pHyprOpenGL->renderTexture(m_data.tex, m_data.box, - {.damage = m_data.damage.empty() ? &damage : &m_data.damage, .a = m_data.a, .round = m_data.round, .roundingPower = m_data.roundingPower}); - } -} - bool CTexPassElement::needsLiveBlur() { return false; // TODO? } @@ -62,7 +18,7 @@ bool CTexPassElement::needsPrecomputeBlur() { } std::optional CTexPassElement::boundingBox() { - return m_data.box.copy().scale(1.F / g_pHyprOpenGL->m_renderData.pMonitor->m_scale).round(); + return m_data.box.copy().scale(1.F / g_pHyprRenderer->m_renderData.pMonitor->m_scale).round(); } CRegion CTexPassElement::opaqueRegion() { diff --git a/src/render/pass/TexPassElement.hpp b/src/render/pass/TexPassElement.hpp index a922843dd..6c0cfab11 100644 --- a/src/render/pass/TexPassElement.hpp +++ b/src/render/pass/TexPassElement.hpp @@ -3,32 +3,53 @@ #include class CWLSurfaceResource; -class CTexture; +namespace Render { + class ITexture; +} class CSyncTimeline; +enum eDiscardMode : uint8_t { + DISCARD_OPAQUE = 1, + DISCARD_ALPHA = 1 << 1 +}; + class CTexPassElement : public IPassElement { public: struct SRenderData { - SP tex; - CBox box; - float a = 1.F; - float blurA = 1.F; - CRegion damage; - int round = 0; - float roundingPower = 2.0f; - bool flipEndFrame = false; - std::optional replaceProjection; - CBox clipBox; - bool blur = false; - std::optional ignoreAlpha; - std::optional blockBlurOptimization; + SP tex; + CBox box; + float a = 1.F; + float blurA = 1.F; + float overallA = 1.F; + CRegion damage; + int round = 0; + float roundingPower = 2.0f; + bool flipEndFrame = false; + bool useMirrorProjection = false; + CBox clipBox; + bool blur = false; + std::optional ignoreAlpha; + std::optional blockBlurOptimization; + bool cmBackToSRGB = false; + SP cmBackToSRGBSource; + + bool discardActive = false; + bool allowCustomUV = false; + SP surface = nullptr; + + uint32_t discardMode = DISCARD_OPAQUE; + float discardOpacity = 0.f; + + CRegion clipRegion; + PHLLSREF currentLS; + + SP blurredBG; }; CTexPassElement(const SRenderData& data); CTexPassElement(SRenderData&& data); virtual ~CTexPassElement() = default; - virtual void draw(const CRegion& damage); virtual bool needsLiveBlur(); virtual bool needsPrecomputeBlur(); virtual std::optional boundingBox(); @@ -39,6 +60,9 @@ class CTexPassElement : public IPassElement { return "CTexPassElement"; } - private: + virtual ePassElementType type() { + return EK_TEXTURE; + }; + SRenderData m_data; }; diff --git a/src/render/pass/TextureMatteElement.cpp b/src/render/pass/TextureMatteElement.cpp index aeeeabc60..f1f0ae179 100644 --- a/src/render/pass/TextureMatteElement.cpp +++ b/src/render/pass/TextureMatteElement.cpp @@ -1,21 +1,9 @@ #include "TextureMatteElement.hpp" -#include "../OpenGL.hpp" CTextureMatteElement::CTextureMatteElement(const CTextureMatteElement::STextureMatteData& data_) : m_data(data_) { ; } -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->setRenderModifEnabled(true); - g_pHyprOpenGL->popMonitorTransformEnabled(); - } else - g_pHyprOpenGL->renderTextureMatte(m_data.tex, m_data.box, *m_data.fb); -} - bool CTextureMatteElement::needsLiveBlur() { return false; } diff --git a/src/render/pass/TextureMatteElement.hpp b/src/render/pass/TextureMatteElement.hpp index 57d0e1e34..03526a57f 100644 --- a/src/render/pass/TextureMatteElement.hpp +++ b/src/render/pass/TextureMatteElement.hpp @@ -2,21 +2,22 @@ #include "PassElement.hpp" #include "../Framebuffer.hpp" -class CTexture; +namespace Render { + class ITexture; +} class CTextureMatteElement : public IPassElement { public: struct STextureMatteData { - CBox box; - SP tex; - SP fb; - bool disableTransformAndModify = false; + CBox box; + SP tex; + SP fb; + bool disableTransformAndModify = false; }; CTextureMatteElement(const STextureMatteData& data_); virtual ~CTextureMatteElement() = default; - virtual void draw(const CRegion& damage); virtual bool needsLiveBlur(); virtual bool needsPrecomputeBlur(); @@ -24,6 +25,9 @@ class CTextureMatteElement : public IPassElement { return "CTextureMatteElement"; } - private: + virtual ePassElementType type() { + return EK_TEXTURE_MATTE; + }; + STextureMatteData m_data; }; \ No newline at end of file diff --git a/src/render/shaders/glsl/CM.frag b/src/render/shaders/glsl/CM.frag deleted file mode 100644 index 031fe7f32..000000000 --- a/src/render/shaders/glsl/CM.frag +++ /dev/null @@ -1,54 +0,0 @@ -#version 300 es -//#extension GL_OES_EGL_image_external : require -#extension GL_ARB_shading_language_include : enable - -precision highp float; -in vec2 v_texcoord; -uniform sampler2D tex; -//uniform samplerExternalOES texture0; - -uniform int texType; // eTextureType: 0 - rgba, 1 - rgbx, 2 - ext -// uniform int skipCM; -uniform int sourceTF; // eTransferFunction -uniform int targetTF; // eTransferFunction -uniform mat4x2 targetPrimaries; - -uniform float alpha; - -uniform int discardOpaque; -uniform int discardAlpha; -uniform float discardAlphaValue; - -uniform int applyTint; -uniform vec3 tint; - -#include "rounding.glsl" -#include "CM.glsl" - -layout(location = 0) out vec4 fragColor; -void main() { - vec4 pixColor; - if (texType == 1) - pixColor = vec4(texture(tex, v_texcoord).rgb, 1.0); -// else if (texType == 2) -// pixColor = texture(texture0, v_texcoord); - else // assume rgba - pixColor = texture(tex, v_texcoord); - - if (discardOpaque == 1 && pixColor[3] * alpha == 1.0) - discard; - - if (discardAlpha == 1 && pixColor[3] <= discardAlphaValue) - discard; - - // this shader shouldn't be used when skipCM == 1 - pixColor = doColorManagement(pixColor, sourceTF, targetTF, targetPrimaries); - - if (applyTint == 1) - pixColor = vec4(pixColor.rgb * tint.rgb, pixColor[3]); - - if (radius > 0.0) - pixColor = rounding(pixColor); - - fragColor = pixColor * alpha; -} diff --git a/src/render/shaders/glsl/CM.glsl b/src/render/shaders/glsl/CM.glsl index 0e79aab01..323a3008f 100644 --- a/src/render/shaders/glsl/CM.glsl +++ b/src/render/shaders/glsl/CM.glsl @@ -1,424 +1,27 @@ -uniform vec2 srcTFRange; -uniform vec2 dstTFRange; +#ifndef ALLOW_INCLUDES +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable +#endif +#include "cm_helpers.glsl" +uniform vec2 srcTFRange; +uniform vec2 dstTFRange; + +uniform float 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 dstMaxLuminance; uniform float dstRefLuminance; -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 srcScale = maxLuminance / dstRefLuminance; - float dstScale = dstMaxLuminance / dstRefLuminance; - - float minScale = min(srcScale, 1.5); - float dimming = 1.0 / clamp(minScale / dstScale, 1.0, minScale); - float refLuminance = dstRefLuminance * dimming; - - float low = min(luminance * dimming, refLuminance); - float highlight = clamp((luminance / dstRefLuminance - 1.0) / (srcScale - 1.0), 0.0, 1.0); - float high = log(highlight * (M_E - 1.0) + 1.0) * (dstMaxLuminance - refLuminance); - luminance = low + high; - - E = pow(clamp(ICtCp[0], 0.0, 1.0), PQ_M1); - ICtCp[0] = pow( - (PQ_C1 + PQ_C2 * E) / (1.0 + PQ_C3 * E), - PQ_M2 - ) / HDR_MAX_LUMINANCE; - return vec4(fromLMS * toLinear(vec4(ICtCpPQInv * ICtCp, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb * HDR_MAX_LUMINANCE, 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; -} +#endif diff --git a/src/render/shaders/glsl/blur1.frag b/src/render/shaders/glsl/blur1.frag index 796fb42db..044df3cc4 100644 --- a/src/render/shaders/glsl/blur1.frag +++ b/src/render/shaders/glsl/blur1.frag @@ -1,143 +1,21 @@ #version 300 es -precision highp float; -uniform sampler2D tex; +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable -uniform float radius; -uniform vec2 halfpixel; -uniform int passes; -uniform float vibrancy; -uniform float vibrancy_darkness; +precision highp float; +uniform sampler2D tex; -in vec2 v_texcoord; - -// see http://alienryderflex.com/hsp.html -const float Pr = 0.299; -const float Pg = 0.587; -const float Pb = 0.114; - -// Y is "v" ( brightness ). X is "s" ( saturation ) -// see https://www.desmos.com/3d/a88652b9a4 -// Determines if high brightness or high saturation is more important -const float a = 0.93; -const float b = 0.11; -const float c = 0.66; // Determines the smoothness of the transition of unboosted to boosted colors -// - -// http://www.flong.com/archive/texts/code/shapers_circ/ -float doubleCircleSigmoid(float x, float a) { - a = clamp(a, 0.0, 1.0); - - float y = .0; - if (x <= a) { - y = a - sqrt(a * a - x * x); - } else { - y = a + sqrt(pow(1. - a, 2.) - pow(x - 1., 2.)); - } - return y; -} - -vec3 rgb2hsl(vec3 col) { - float red = col.r; - float green = col.g; - float blue = col.b; - - float minc = min(col.r, min(col.g, col.b)); - float maxc = max(col.r, max(col.g, col.b)); - float delta = maxc - minc; - - float lum = (minc + maxc) * 0.5; - float sat = 0.0; - float hue = 0.0; - - if (lum > 0.0 && lum < 1.0) { - float mul = (lum < 0.5) ? (lum) : (1.0 - lum); - sat = delta / (mul * 2.0); - } - - if (delta > 0.0) { - vec3 maxcVec = vec3(maxc); - vec3 masks = vec3(equal(maxcVec, col)) * vec3(notEqual(maxcVec, vec3(green, blue, red))); - vec3 adds = vec3(0.0, 2.0, 4.0) + vec3(green - blue, blue - red, red - green) / delta; - - hue += dot(adds, masks); - hue /= 6.0; - - if (hue < 0.0) - hue += 1.0; - } - - return vec3(hue, sat, lum); -} - -vec3 hsl2rgb(vec3 col) { - const float onethird = 1.0 / 3.0; - const float twothird = 2.0 / 3.0; - const float rcpsixth = 6.0; - - float hue = col.x; - float sat = col.y; - float lum = col.z; - - vec3 xt = vec3(0.0); - - if (hue < onethird) { - xt.r = rcpsixth * (onethird - hue); - xt.g = rcpsixth * hue; - xt.b = 0.0; - } else if (hue < twothird) { - xt.r = 0.0; - xt.g = rcpsixth * (twothird - hue); - xt.b = rcpsixth * (hue - onethird); - } else - xt = vec3(rcpsixth * (hue - twothird), 0.0, rcpsixth * (1.0 - hue)); - - xt = min(xt, 1.0); - - float sat2 = 2.0 * sat; - float satinv = 1.0 - sat; - float luminv = 1.0 - lum; - float lum2m1 = (2.0 * lum) - 1.0; - vec3 ct = (sat2 * xt) + satinv; - - vec3 rgb; - if (lum >= 0.5) - rgb = (luminv * ct) + lum2m1; - else - rgb = lum * ct; - - return rgb; -} +uniform float radius; +uniform vec2 halfpixel; +uniform int passes; +uniform float vibrancy; +uniform float vibrancy_darkness; +in vec2 v_texcoord; layout(location = 0) out vec4 fragColor; + +#include "blur1.glsl" + void main() { - vec2 uv = v_texcoord * 2.0; - - vec4 sum = texture(tex, uv) * 4.0; - sum += texture(tex, uv - halfpixel.xy * radius); - sum += texture(tex, uv + halfpixel.xy * radius); - sum += texture(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius); - sum += texture(tex, uv - vec2(halfpixel.x, -halfpixel.y) * radius); - - vec4 color = sum / 8.0; - - if (vibrancy == 0.0) { - fragColor = color; - } else { - // Invert it so that it correctly maps to the config setting - float vibrancy_darkness1 = 1.0 - vibrancy_darkness; - - // Decrease the RGB components based on their perceived brightness, to prevent visually dark colors from overblowing the rest. - vec3 hsl = rgb2hsl(color.rgb); - // Calculate perceived brightness, as not boost visually dark colors like deep blue as much as equally saturated yellow - float perceivedBrightness = doubleCircleSigmoid(sqrt(color.r * color.r * Pr + color.g * color.g * Pg + color.b * color.b * Pb), 0.8 * vibrancy_darkness1); - - float b1 = b * vibrancy_darkness1; - float boostBase = hsl[1] > 0.0 ? smoothstep(b1 - c * 0.5, b1 + c * 0.5, 1.0 - (pow(1.0 - hsl[1] * cos(a), 2.0) + pow(1.0 - perceivedBrightness * sin(a), 2.0))) : 0.0; - - float saturation = clamp(hsl[1] + (boostBase * vibrancy) / float(passes), 0.0, 1.0); - - vec3 newColor = hsl2rgb(vec3(hsl[0], saturation, hsl[2])); - - fragColor = vec4(newColor, color[3]); - } + fragColor = blur1(v_texcoord, tex, radius, halfpixel, passes, vibrancy, vibrancy_darkness); } diff --git a/src/render/shaders/glsl/blur1.glsl b/src/render/shaders/glsl/blur1.glsl new file mode 100644 index 000000000..86a37d88a --- /dev/null +++ b/src/render/shaders/glsl/blur1.glsl @@ -0,0 +1,134 @@ +// 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; + // Those pixels might go outside the rendered area and grab some gabage. + // That garbage maps to 0.0-1.0 range with UINT8 buffer and doesn't have any significant impact on the end result. + // FP16 garbage maps to -65,504 - 65,504 and defines the end result. Clamp it here to 0.0 - 1.0 to get the same quality outcome as with UINT8. + // Rerendering an undamaged area to get some insignificant color accuracy increase on blur edges isn't worth it. + sum += clamp(texture(tex, uv - halfpixel.xy * radius), 0.0, 1.0); + sum += clamp(texture(tex, uv + halfpixel.xy * radius), 0.0, 1.0); + sum += clamp(texture(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius), 0.0, 1.0); + sum += clamp(texture(tex, uv - vec2(halfpixel.x, -halfpixel.y) * radius), 0.0, 1.0); + + 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 bfe448d5f..62caae561 100644 --- a/src/render/shaders/glsl/blur2.frag +++ b/src/render/shaders/glsl/blur2.frag @@ -1,25 +1,18 @@ #version 300 es -precision highp float; +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable + +precision highp float; uniform sampler2D tex; -uniform float radius; -uniform vec2 halfpixel; +uniform float radius; +uniform vec2 halfpixel; -in vec2 v_texcoord; +in vec2 v_texcoord; layout(location = 0) out vec4 fragColor; +#include "blur2.glsl" + void main() { - vec2 uv = v_texcoord / 2.0; - - vec4 sum = texture(tex, uv + vec2(-halfpixel.x * 2.0, 0.0) * radius); - - sum += texture(tex, uv + vec2(-halfpixel.x, halfpixel.y) * radius) * 2.0; - sum += texture(tex, uv + vec2(0.0, halfpixel.y * 2.0) * radius); - sum += texture(tex, uv + vec2(halfpixel.x, halfpixel.y) * radius) * 2.0; - sum += texture(tex, uv + vec2(halfpixel.x * 2.0, 0.0) * radius); - sum += texture(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius) * 2.0; - sum += texture(tex, uv + vec2(0.0, -halfpixel.y * 2.0) * radius); - sum += texture(tex, uv + vec2(-halfpixel.x, -halfpixel.y) * radius) * 2.0; - - fragColor = sum / 12.0; + fragColor = blur2(v_texcoord, tex, radius, halfpixel); } diff --git a/src/render/shaders/glsl/blur2.glsl b/src/render/shaders/glsl/blur2.glsl new file mode 100644 index 000000000..e73e90e3b --- /dev/null +++ b/src/render/shaders/glsl/blur2.glsl @@ -0,0 +1,15 @@ +vec4 blur2(vec2 v_texcoord, sampler2D tex, float radius, vec2 halfpixel) { + vec2 uv = v_texcoord / 2.0; + + vec4 sum = texture(tex, uv + vec2(-halfpixel.x * 2.0, 0.0) * radius); + + sum += texture(tex, uv + vec2(-halfpixel.x, halfpixel.y) * radius) * 2.0; + sum += texture(tex, uv + vec2(0.0, halfpixel.y * 2.0) * radius); + sum += texture(tex, uv + vec2(halfpixel.x, halfpixel.y) * radius) * 2.0; + sum += texture(tex, uv + vec2(halfpixel.x * 2.0, 0.0) * radius); + sum += texture(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius) * 2.0; + sum += texture(tex, uv + vec2(0.0, -halfpixel.y * 2.0) * radius); + sum += texture(tex, uv + vec2(-halfpixel.x, -halfpixel.y) * radius) * 2.0; + + return sum / 12.0; +} diff --git a/src/render/shaders/glsl/blurFinish.glsl b/src/render/shaders/glsl/blurFinish.glsl new file mode 100644 index 000000000..e7e1f1c62 --- /dev/null +++ b/src/render/shaders/glsl/blurFinish.glsl @@ -0,0 +1,37 @@ +#ifndef ALLOW_INCLUDES +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable +#endif + +#include "defines.h" + +#if USE_CM +#include "cm_helpers.glsl" +#endif + +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 +#if USE_CM + , + int sourceTF, int targetTF, mat3 convertMatrix, vec2 srcTFRange, vec2 dstTFRange +#endif +) { + // noise + float noiseHash = hash(v_texcoord); + float noiseAmount = noiseHash - 0.5; + pixColor.rgb += noiseAmount * noise; + + // brightness + pixColor.rgb *= min(1.0, brightness); + +#if USE_CM + pixColor = doColorManagement(pixColor, sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange); +#endif + + return pixColor; +} diff --git a/src/render/shaders/glsl/blurfinish.frag b/src/render/shaders/glsl/blurfinish.frag index 6ab483378..51eb76849 100644 --- a/src/render/shaders/glsl/blurfinish.frag +++ b/src/render/shaders/glsl/blurfinish.frag @@ -1,32 +1,31 @@ #version 300 es +#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable -precision highp float; -in vec2 v_texcoord; // is in 0-1 +precision highp float; +in vec2 v_texcoord; // is in 0-1 uniform sampler2D tex; -uniform float noise; -uniform float brightness; +uniform float noise; +uniform float brightness; -float hash(vec2 p) { - vec3 p3 = fract(vec3(p.xyx) * 1689.1984); - p3 += dot(p3, p3.yzx + 33.33); - return fract((p3.x + p3.y) * p3.z); -} +#include "defines.h" +#if USE_CM +uniform int sourceTF; // eTransferFunction +uniform int targetTF; // eTransferFunction +#include "CM.glsl" +#endif + +#include "blurFinish.glsl" layout(location = 0) out vec4 fragColor; void main() { vec4 pixColor = texture(tex, v_texcoord); - // noise - float noiseHash = hash(v_texcoord); - float noiseAmount = (mod(noiseHash, 1.0) - 0.5); - pixColor.rgb += noiseAmount * noise; - - // brightness - if (brightness < 1.0) { - pixColor.rgb *= brightness; - } - - fragColor = pixColor; + fragColor = blurFinish(pixColor, v_texcoord, noise, brightness +#if USE_CM + , + sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange +#endif + ); } diff --git a/src/render/shaders/glsl/blurprepare.frag b/src/render/shaders/glsl/blurprepare.frag index 548289c30..e96c54bb7 100644 --- a/src/render/shaders/glsl/blurprepare.frag +++ b/src/render/shaders/glsl/blurprepare.frag @@ -1,48 +1,38 @@ #version 300 es +#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable -precision highp float; -in vec2 v_texcoord; // is in 0-1 +#include "defines.h" + +precision highp float; +in vec2 v_texcoord; // is in 0-1 uniform sampler2D tex; -uniform float contrast; -uniform float brightness; +uniform float contrast; +uniform float brightness; -uniform int skipCM; -uniform int sourceTF; // eTransferFunction -uniform int targetTF; // eTransferFunction +uniform int sourceTF; // eTransferFunction +uniform int targetTF; // eTransferFunction -#include "CM.glsl" +#if USE_CM +uniform vec2 srcTFRange; +uniform vec2 dstTFRange; -float gain(float x, float k) { - float a = 0.5 * pow(2.0 * ((x < 0.5) ? x : 1.0 - x), k); - return (x < 0.5) ? a : 1.0 - a; -} +uniform float srcRefLuminance; +uniform mat3 convertMatrix; + +uniform float sdrBrightnessMultiplier; +#include "cm_helpers.glsl" +#endif + +#include "blurprepare.glsl" layout(location = 0) out vec4 fragColor; void main() { - vec4 pixColor = texture(tex, v_texcoord); - - if (skipCM == 0) { - if (sourceTF == CM_TRANSFER_FUNCTION_ST2084_PQ) { - pixColor.rgb /= sdrBrightnessMultiplier; - } - pixColor.rgb = convertMatrix * toLinearRGB(pixColor.rgb, sourceTF); - pixColor = toNit(pixColor, srcTFRange); - pixColor = fromLinearNit(pixColor, targetTF, dstTFRange); - } - - // contrast - if (contrast != 1.0) { - pixColor.r = gain(pixColor.r, contrast); - pixColor.g = gain(pixColor.g, contrast); - pixColor.b = gain(pixColor.b, contrast); - } - - // brightness - if (brightness > 1.0) { - pixColor.rgb *= brightness; - } - - fragColor = pixColor; + fragColor = fragColor = blurPrepare(texture(tex, v_texcoord), contrast, brightness +#if USE_CM + , + sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange, srcRefLuminance, sdrBrightnessMultiplier +#endif + ); } diff --git a/src/render/shaders/glsl/blurprepare.glsl b/src/render/shaders/glsl/blurprepare.glsl new file mode 100644 index 000000000..e4a0daad2 --- /dev/null +++ b/src/render/shaders/glsl/blurprepare.glsl @@ -0,0 +1,37 @@ +#ifndef ALLOW_INCLUDES +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable +#endif + +#include "defines.h" + +#if USE_CM +#include "cm_helpers.glsl" +#endif + +#include "gain.glsl" + +vec4 blurPrepare(vec4 pixColor, float contrast, float brightness +#if USE_CM + , + int sourceTF, int targetTF, mat3 convertMatrix, vec2 srcTFRange, vec2 dstTFRange, float srcRefLuminance, float sdrBrightnessMultiplier +#endif +) { +#if USE_CM + if (sourceTF == CM_TRANSFER_FUNCTION_ST2084_PQ) { + pixColor.rgb /= sdrBrightnessMultiplier; + } + pixColor.rgb = convertMatrix * toLinearRGB(pixColor.rgb, sourceTF); + pixColor = toNit(pixColor, vec2(srcTFRange[0], srcRefLuminance)); + pixColor = fromLinearNit(pixColor, targetTF, dstTFRange); +#endif + + // contrast + if (contrast != 1.0) + pixColor.rgb = gain(pixColor.rgb, contrast); + + // brightness + pixColor.rgb *= max(1.0, brightness); + + return pixColor; +} diff --git a/src/render/shaders/glsl/border.frag b/src/render/shaders/glsl/border.frag index d93773fd8..9f97348b6 100644 --- a/src/render/shaders/glsl/border.frag +++ b/src/render/shaders/glsl/border.frag @@ -1,182 +1,72 @@ #version 300 es +#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable -precision highp float; -in vec2 v_texcoord; +precision highp float; +in vec2 v_texcoord; -uniform int skipCM; -uniform int sourceTF; // eTransferFunction -uniform int targetTF; // eTransferFunction -uniform mat4x2 targetPrimaries; +uniform int sourceTF; // eTransferFunction +uniform int targetTF; // eTransferFunction +uniform mat3 targetPrimariesXYZ; -uniform vec2 fullSizeUntransformed; +uniform vec2 fullSizeUntransformed; uniform float radiusOuter; uniform float thick; // Gradients are in OkLabA!!!! {l, a, b, alpha} -uniform vec4 gradient[10]; -uniform vec4 gradient2[10]; -uniform int gradientLength; -uniform int gradient2Length; +uniform vec4 gradient[10]; +uniform vec4 gradient2[10]; +uniform int gradientLength; +uniform int gradient2Length; uniform float angle; uniform float angle2; uniform float gradientLerp; uniform float alpha; +uniform float radius; +uniform float roundingPower; +uniform vec2 topLeft; +uniform vec2 fullSize; #include "rounding.glsl" #include "CM.glsl" - -vec4 okLabAToSrgb(vec4 lab) { - float l = pow(lab[0] + lab[1] * 0.3963377774 + lab[2] * 0.2158037573, 3.0); - float m = pow(lab[0] + lab[1] * (-0.1055613458) + lab[2] * (-0.0638541728), 3.0); - float s = pow(lab[0] + lab[1] * (-0.0894841775) + lab[2] * (-1.2914855480), 3.0); - - return vec4(fromLinearRGB( - vec3( - l * 4.0767416621 + m * -3.3077115913 + s * 0.2309699292, - l * (-1.2684380046) + m * 2.6097574011 + s * (-0.3413193965), - l * (-0.0041960863) + m * (-0.7034186147) + s * 1.7076147010 - ), CM_TRANSFER_FUNCTION_SRGB - ), lab[3]); -} - -vec4 getOkColorForCoordArray1(vec2 normalizedCoord) { - if (gradientLength < 2) - return gradient[0]; - - float finalAng = 0.0; - - if (angle > 4.71 /* 270 deg */) { - normalizedCoord[1] = 1.0 - normalizedCoord[1]; - finalAng = 6.28 - angle; - } else if (angle > 3.14 /* 180 deg */) { - normalizedCoord[0] = 1.0 - normalizedCoord[0]; - normalizedCoord[1] = 1.0 - normalizedCoord[1]; - finalAng = angle - 3.14; - } else if (angle > 1.57 /* 90 deg */) { - normalizedCoord[0] = 1.0 - normalizedCoord[0]; - finalAng = 3.14 - angle; - } else { - finalAng = angle; - } - - float sine = sin(finalAng); - - float progress = (normalizedCoord[1] * sine + normalizedCoord[0] * (1.0 - sine)) * float(gradientLength - 1); - int bottom = int(floor(progress)); - int top = bottom + 1; - - return gradient[top] * (progress - float(bottom)) + gradient[bottom] * (float(top) - progress); -} - -vec4 getOkColorForCoordArray2(vec2 normalizedCoord) { - if (gradient2Length < 2) - return gradient2[0]; - - float finalAng = 0.0; - - if (angle2 > 4.71 /* 270 deg */) { - normalizedCoord[1] = 1.0 - normalizedCoord[1]; - finalAng = 6.28 - angle; - } else if (angle2 > 3.14 /* 180 deg */) { - normalizedCoord[0] = 1.0 - normalizedCoord[0]; - normalizedCoord[1] = 1.0 - normalizedCoord[1]; - finalAng = angle - 3.14; - } else if (angle2 > 1.57 /* 90 deg */) { - normalizedCoord[0] = 1.0 - normalizedCoord[0]; - finalAng = 3.14 - angle2; - } else { - finalAng = angle2; - } - - float sine = sin(finalAng); - - float progress = (normalizedCoord[1] * sine + normalizedCoord[0] * (1.0 - sine)) * float(gradient2Length - 1); - int bottom = int(floor(progress)); - int top = bottom + 1; - - return gradient2[top] * (progress - float(bottom)) + gradient2[bottom] * (float(top) - progress); -} - -vec4 getColorForCoord(vec2 normalizedCoord) { - vec4 result1 = getOkColorForCoordArray1(normalizedCoord); - - if (gradient2Length <= 0) - return okLabAToSrgb(result1); - - vec4 result2 = getOkColorForCoordArray2(normalizedCoord); - - return okLabAToSrgb(mix(result1, result2, gradientLerp)); -} +#include "border.glsl" layout(location = 0) out vec4 fragColor; +#if USE_MIRROR +layout(location = 1) out vec4 mirrorColor; +#endif void main() { - highp vec2 pixCoord = vec2(gl_FragCoord); - highp vec2 pixCoordOuter = pixCoord; - highp vec2 originalPixCoord = v_texcoord; - originalPixCoord *= fullSizeUntransformed; - float additionalAlpha = 1.0; - - vec4 pixColor = vec4(1.0, 1.0, 1.0, 1.0); - - bool done = false; - - pixCoord -= topLeft + fullSize * 0.5; - pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0; - pixCoordOuter = pixCoord; - pixCoord -= fullSize * 0.5 - radius; - pixCoordOuter -= fullSize * 0.5 - radiusOuter; - - // center the pixes don't make it top-left - pixCoord += vec2(1.0, 1.0) / fullSize; - pixCoordOuter += vec2(1.0, 1.0) / fullSize; - - if (min(pixCoord.x, pixCoord.y) > 0.0 && radius > 0.0) { - float dist = pow(pow(pixCoord.x,roundingPower)+pow(pixCoord.y,roundingPower),1.0/roundingPower); - float distOuter = pow(pow(pixCoordOuter.x,roundingPower)+pow(pixCoordOuter.y,roundingPower),1.0/roundingPower); - float h = (thick / 2.0); - - if (dist < radius - h) { - // lower - float normalized = smoothstep(0.0, 1.0, (dist - radius + thick + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); - additionalAlpha *= normalized; - done = true; - } else if (min(pixCoordOuter.x, pixCoordOuter.y) > 0.0) { - // higher - float normalized = 1.0 - smoothstep(0.0, 1.0, (distOuter - radiusOuter + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); - additionalAlpha *= normalized; - done = true; - } else if (distOuter < radiusOuter - h) { - additionalAlpha = 1.0; - done = true; - } - } - - // now check for other shit - if (!done) { - // distance to all straight bb borders - float distanceT = originalPixCoord[1]; - float distanceB = fullSizeUntransformed[1] - originalPixCoord[1]; - float distanceL = originalPixCoord[0]; - float distanceR = fullSizeUntransformed[0] - originalPixCoord[0]; - - // get the smallest - float smallest = min(min(distanceT, distanceB), min(distanceL, distanceR)); - - if (smallest > thick) - discard; - } - - if (additionalAlpha == 0.0) - discard; - - pixColor = getColorForCoord(v_texcoord); - pixColor.rgb *= pixColor[3]; - - if (skipCM == 0) - pixColor = doColorManagement(pixColor, sourceTF, targetTF, targetPrimaries); - - pixColor *= alpha * additionalAlpha; - - fragColor = pixColor; +#if USE_MIRROR + vec4[2] pixColors = +#else + fragColor = +#endif + 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 + ); +#if USE_MIRROR + fragColor = pixColors[0]; + mirrorColor = pixColors[1]; +#endif } diff --git a/src/render/shaders/glsl/border.glsl b/src/render/shaders/glsl/border.glsl new file mode 100644 index 000000000..9028ad0d1 --- /dev/null +++ b/src/render/shaders/glsl/border.glsl @@ -0,0 +1,218 @@ +#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)); +} + +#if USE_MIRROR +vec4[2] +#else +vec4 +#endif + 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 +#if USE_MIRROR + vec4[2] pixColors = +#else + pixColor = +#endif + 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_MIRROR + pixColors[0] *= alpha * additionalAlpha; + pixColors[1] *= alpha * additionalAlpha; + return pixColors; +#else + pixColor *= alpha * additionalAlpha; + return pixColor; +#endif +} diff --git a/src/render/shaders/glsl/cm_helpers.glsl b/src/render/shaders/glsl/cm_helpers.glsl new file mode 100644 index 000000000..0d8c68e93 --- /dev/null +++ b/src/render/shaders/glsl/cm_helpers.glsl @@ -0,0 +1,261 @@ +#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) { + color.rgb = (color.rgb - range[0] * color.a) / (range[1] - range[0]); // @gulafaran + color.rgb /= max(color.a, 0.001); + color.rgb = fromLinearRGB(color.rgb, tf); + color.rgb *= color.a; + return color; +} + +#if USE_TONEMAP +#include "tonemap.glsl" +#endif + +#if USE_MIRROR +vec4[2] +#else +vec4 +#endif + 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 +#if USE_MIRROR + // TODO HDR -> SDR tonemap + vec4 mirrorColor = fromLinearNit(pixColor, CM_TRANSFER_FUNCTION_SRGB, + srcTF == CM_TRANSFER_FUNCTION_GAMMA22 || srcTF == CM_TRANSFER_FUNCTION_SRGB ? srcTFRange : vec2(SDR_MIN_LUMINANCE, SDR_MAX_LUMINANCE)); +#endif + pixColor = fromLinearNit(pixColor, dstTF, dstTFRange); +#if USE_SDR_MOD + pixColor = saturate(pixColor, dstxyz, sdrSaturation); + pixColor.rgb *= sdrBrightnessMultiplier; +#endif +#endif + +#if USE_MIRROR + vec4[2] pixColors; + pixColors[0] = pixColor; + pixColors[1] = mirrorColor; + return pixColors; +#else + return pixColor; +#endif +} + +#endif \ No newline at end of file diff --git a/src/render/shaders/glsl/constants.h b/src/render/shaders/glsl/constants.h new file mode 100644 index 000000000..bbab5284b --- /dev/null +++ b/src/render/shaders/glsl/constants.h @@ -0,0 +1,62 @@ +#ifndef CONSTANTS_H +#define CONSTANTS_H +//enum eTransferFunction +#define CM_TRANSFER_FUNCTION_BT1886 1 +#define CM_TRANSFER_FUNCTION_GAMMA22 2 +#define CM_TRANSFER_FUNCTION_GAMMA28 3 +#define CM_TRANSFER_FUNCTION_ST240 4 +#define CM_TRANSFER_FUNCTION_EXT_LINEAR 5 +#define CM_TRANSFER_FUNCTION_LOG_100 6 +#define CM_TRANSFER_FUNCTION_LOG_316 7 +#define CM_TRANSFER_FUNCTION_XVYCC 8 +#define CM_TRANSFER_FUNCTION_SRGB 9 +#define CM_TRANSFER_FUNCTION_EXT_SRGB 10 +#define CM_TRANSFER_FUNCTION_ST2084_PQ 11 +#define CM_TRANSFER_FUNCTION_ST428 12 +#define CM_TRANSFER_FUNCTION_HLG 13 + +// sRGB constants +#define SRGB_POW 2.4 +#define SRGB_CUT 0.0031308 +#define SRGB_SCALE 12.92 +#define SRGB_ALPHA 1.055 + +#define BT1886_POW (1.0 / 0.45) +#define BT1886_CUT 0.018053968510807 +#define BT1886_SCALE 4.5 +#define BT1886_ALPHA (1.0 + 5.5 * BT1886_CUT) + +// See http://car.france3.mars.free.fr/HD/INA-%2026%20jan%2006/SMPTE%20normes%20et%20confs/s240m.pdf +#define ST240_POW (1.0 / 0.45) +#define ST240_CUT 0.0228 +#define ST240_SCALE 4.0 +#define ST240_ALPHA 1.1115 + +#define ST428_POW 2.6 +#define ST428_SCALE (52.37 / 48.0) + +// PQ constants +#define PQ_M1 0.1593017578125 +#define PQ_M2 78.84375 +#define PQ_INV_M1 (1.0 / PQ_M1) +#define PQ_INV_M2 (1.0 / PQ_M2) +#define PQ_C1 0.8359375 +#define PQ_C2 18.8515625 +#define PQ_C3 18.6875 + +// HLG constants +#define HLG_D_CUT (1.0 / 12.0) +#define HLG_E_CUT 0.5 +#define HLG_A 0.17883277 +#define HLG_B 0.28466892 +#define HLG_C 0.55991073 + +#define SDR_MIN_LUMINANCE 0.2 +#define SDR_MAX_LUMINANCE 80.0 +#define HDR_MIN_LUMINANCE 0.005 +#define HDR_MAX_LUMINANCE 10000.0 +#define HLG_MAX_LUMINANCE 1000.0 + +#define M_E 2.718281828459045 + +#endif diff --git a/src/render/shaders/glsl/defines.h b/src/render/shaders/glsl/defines.h new file mode 100644 index 000000000..a04e0250b --- /dev/null +++ b/src/render/shaders/glsl/defines.h @@ -0,0 +1,14 @@ +// DO NOT EDIT. Will be overwritten in runtime +// Values here are only for highlighting and static checking +// 1 assumes that a shader either supports this feature or doesn't use any code supporting it +// 0 assumes that shader uses some code supporting the feature but will never have this feature enabled +#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 0 +#define USE_MIRROR 0 diff --git a/src/render/shaders/glsl/ext.frag b/src/render/shaders/glsl/ext.frag index f540a9f9f..1c614bd3c 100644 --- a/src/render/shaders/glsl/ext.frag +++ b/src/render/shaders/glsl/ext.frag @@ -1,38 +1,43 @@ #version 300 es +#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable #extension GL_OES_EGL_image_external_essl3 : require -precision highp float; -in vec2 v_texcoord; -uniform samplerExternalOES texture0; -uniform float alpha; +precision highp float; +in vec2 v_texcoord; +uniform samplerExternalOES tex; +uniform float alpha; +uniform float radius; +uniform float roundingPower; +uniform vec2 topLeft; +uniform vec2 fullSize; #include "rounding.glsl" -uniform int discardOpaque; -uniform int discardAlpha; -uniform int discardAlphaValue; +uniform int discardOpaque; +uniform int discardAlpha; +uniform int discardAlphaValue; -uniform int applyTint; +uniform int applyTint; uniform vec3 tint; layout(location = 0) out vec4 fragColor; void main() { - vec4 pixColor = texture(texture0, v_texcoord); + vec4 pixColor = texture(tex, v_texcoord); if (discardOpaque == 1 && pixColor[3] * alpha == 1.0) - discard; + discard; if (applyTint == 1) { - pixColor[0] = pixColor[0] * tint[0]; - pixColor[1] = pixColor[1] * tint[1]; - pixColor[2] = pixColor[2] * tint[2]; + pixColor[0] = pixColor[0] * tint[0]; + pixColor[1] = pixColor[1] * tint[1]; + pixColor[2] = pixColor[2] * tint[2]; } if (radius > 0.0) - pixColor = rounding(pixColor); + pixColor = rounding(pixColor, radius, roundingPower, topLeft, fullSize); fragColor = pixColor * alpha; } diff --git a/src/render/shaders/glsl/gain.glsl b/src/render/shaders/glsl/gain.glsl new file mode 100644 index 000000000..f5326d9c8 --- /dev/null +++ b/src/render/shaders/glsl/gain.glsl @@ -0,0 +1,7 @@ +vec3 gain(vec3 src, float k) { + vec3 x = clamp(src, 0.0, 1.0); + 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 e399a8b16..d7259cc4d 100644 --- a/src/render/shaders/glsl/glitch.frag +++ b/src/render/shaders/glsl/glitch.frag @@ -5,7 +5,7 @@ in vec2 v_texcoord; uniform sampler2D tex; uniform float time; // quirk: time is set to 0 at the beginning, should be around 10 when crash. uniform float distort; -uniform vec2 screenSize; +uniform vec2 fullSize; float rand(float co) { return fract(sin(dot(vec2(co, co), vec2(12.9898, 78.233))) * 43758.5453); @@ -31,7 +31,7 @@ void main() { float ABERR_OFFSET = 4.0 * (distort / 5.5) * time; float TEAR_AMOUNT = 9000.0 * (1.0 - (distort / 5.5)); float TEAR_BANDS = 108.0 / 2.0 * (distort / 5.5) * 2.0; - float MELT_AMOUNT = (distort * 8.0) / screenSize.y; + float MELT_AMOUNT = (distort * 8.0) / fullSize.y; float NOISE = abs(mod(noise(v_texcoord) * distort * time * 2.771, 1.0)) * time / 10.0; if (time < 2.0) @@ -44,7 +44,7 @@ void main() { if (time < 3.0) blockOffset = vec2(0,0); - float meltSeed = abs(mod(rand(floor(v_texcoord.x * screenSize.x * 17.719)) * 281.882, 1.0)); + float meltSeed = abs(mod(rand(floor(v_texcoord.x * fullSize.x * 17.719)) * 281.882, 1.0)); if (meltSeed < 0.8) { meltSeed = 0.0; } else { @@ -52,11 +52,11 @@ void main() { } float meltAmount = MELT_AMOUNT * meltSeed; - vec2 pixCoord = vec2(v_texcoord.x + offset + NOISE * 3.0 / screenSize.x + blockOffset.x, v_texcoord.y - meltAmount + 0.02 * NOISE / screenSize.x + NOISE * 3.0 / screenSize.y + blockOffset.y); + vec2 pixCoord = vec2(v_texcoord.x + offset + NOISE * 3.0 / fullSize.x + blockOffset.x, v_texcoord.y - meltAmount + 0.02 * NOISE / fullSize.x + NOISE * 3.0 / fullSize.y + blockOffset.y); vec4 pixColor = texture(tex, pixCoord); - vec4 pixColorLeft = texture(tex, pixCoord + vec2(ABERR_OFFSET / screenSize.x, 0)); - vec4 pixColorRight = texture(tex, pixCoord + vec2(-ABERR_OFFSET / screenSize.x, 0)); + vec4 pixColorLeft = texture(tex, pixCoord + vec2(ABERR_OFFSET / fullSize.x, 0)); + vec4 pixColorRight = texture(tex, pixCoord + vec2(-ABERR_OFFSET / fullSize.x, 0)); pixColor[0] = pixColorLeft[0]; pixColor[2] = pixColorRight[2]; diff --git a/src/render/shaders/glsl/inner_glow.frag b/src/render/shaders/glsl/inner_glow.frag new file mode 100644 index 000000000..2fc405c06 --- /dev/null +++ b/src/render/shaders/glsl/inner_glow.frag @@ -0,0 +1,57 @@ +#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; + +uniform int sourceTF; // eTransferFunction +uniform int targetTF; // eTransferFunction +uniform mat3 targetPrimariesXYZ; + +uniform vec2 topLeft; +uniform vec2 bottomRight; +uniform vec2 fullSize; +uniform float radius; +uniform float roundingPower; +uniform float range; +uniform float shadowPower; + +#if USE_CM +#include "cm_helpers.glsl" +#include "CM.glsl" +#endif + +#include "inner_glow.glsl" + +layout(location = 0) out vec4 fragColor; +void main() { + vec4 pixColor = v_color; + + fragColor = getInnerGlow(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 + ); +} diff --git a/src/render/shaders/glsl/inner_glow.glsl b/src/render/shaders/glsl/inner_glow.glsl new file mode 100644 index 000000000..b0d55b194 --- /dev/null +++ b/src/render/shaders/glsl/inner_glow.glsl @@ -0,0 +1,106 @@ +#ifndef ALLOW_INCLUDES +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable +#endif +#ifndef INNER_GLOW_GLSL +#define INNER_GLOW_GLSL + +#include "cm_helpers.glsl" + +float innerGlowAlpha(float distFromEdge, float range, float glowPower) { + if (distFromEdge >= range) + return 0.0; + + if (distFromEdge <= 0.0) + return 1.0; + + return pow(1.0 - distFromEdge / range, glowPower); +} + +float innerGlowModifiedLength(vec2 a, float roundingPower) { + return pow(pow(abs(a.x), roundingPower) + pow(abs(a.y), roundingPower), 1.0 / roundingPower); +} + +float innerGlowSmin(float a, float b, float k) { + float h = max(k - abs(a - b), 0.0) / k; + return min(a, b) - h * h * h * k * (1.0 / 6.0); +} + +vec4 getInnerGlow(vec4 pixColor, vec2 v_texcoord, float radius, float roundingPower, vec2 topLeft, vec2 fullSize, float range, float glowPower, 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 +) { + vec2 pixCoord = fullSize * v_texcoord; + + // clip to the rounded rectangle shape using actual SDF + vec2 center = fullSize * 0.5; + vec2 p = abs(pixCoord - center); + vec2 q = p - (center - vec2(radius)); + vec2 qc = max(q, vec2(0.0)); + float cornerD = (qc.x > 0.0 || qc.y > 0.0) ? innerGlowModifiedLength(qc, roundingPower) : 0.0; + float sdfDist = cornerD + min(max(q.x, q.y), 0.0) - radius; + + if (sdfDist > 0.0) + discard; + + // smooth-min of edge distances for rounded glow contours + float distT = pixCoord.y; + float distB = fullSize.y - pixCoord.y; + float distL = pixCoord.x; + float distR = fullSize.x - pixCoord.x; + + float k = range; + float distFromEdge = innerGlowSmin(innerGlowSmin(distT, distB, k), innerGlowSmin(distL, distR, k), k); + + pixColor[3] = pixColor[3] * innerGlowAlpha(distFromEdge, range, glowPower); + + if (pixColor[3] == 0.0) + discard; + + // 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 diff --git a/src/render/shaders/glsl/quad.frag b/src/render/shaders/glsl/quad.frag index 5dae493e6..dc1945afa 100644 --- a/src/render/shaders/glsl/quad.frag +++ b/src/render/shaders/glsl/quad.frag @@ -1,17 +1,33 @@ #version 300 es +#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable -precision highp float; -in vec4 v_color; +#include "defines.h" +precision highp float; +in vec4 v_color; + +#if USE_ROUNDING +uniform float radius; +uniform float roundingPower; +uniform vec2 topLeft; +uniform vec2 fullSize; #include "rounding.glsl" +#endif layout(location = 0) out vec4 fragColor; +#if USE_MIRROR +layout(location = 1) out vec4 mirrorColor; +#endif void main() { vec4 pixColor = v_color; - if (radius > 0.0) - pixColor = rounding(pixColor); +#if USE_ROUNDING + pixColor = rounding(pixColor, radius, roundingPower, topLeft, fullSize); +#endif fragColor = pixColor; +#if USE_MIRROR + mirrorColor = fragColor; +#endif } diff --git a/src/render/shaders/glsl/rgba.frag b/src/render/shaders/glsl/rgba.frag deleted file mode 100644 index e4e045004..000000000 --- a/src/render/shaders/glsl/rgba.frag +++ /dev/null @@ -1,39 +0,0 @@ -#version 300 es - -#extension GL_ARB_shading_language_include : enable -precision highp float; -in vec2 v_texcoord; // is in 0-1 -uniform sampler2D tex; -uniform float alpha; - -#include "rounding.glsl" - -uniform int discardOpaque; -uniform int discardAlpha; -uniform float discardAlphaValue; - -uniform int applyTint; -uniform vec3 tint; - -layout(location = 0) out vec4 fragColor; -void main() { - - vec4 pixColor = texture(tex, v_texcoord); - - if (discardOpaque == 1 && pixColor[3] * alpha == 1.0) - discard; - - if (discardAlpha == 1 && pixColor[3] <= discardAlphaValue) - discard; - - if (applyTint == 1) { - pixColor[0] = pixColor[0] * tint[0]; - pixColor[1] = pixColor[1] * tint[1]; - pixColor[2] = pixColor[2] * tint[2]; - } - - if (radius > 0.0) - pixColor = rounding(pixColor); - - fragColor = pixColor * alpha; -} diff --git a/src/render/shaders/glsl/rgbamatte.frag b/src/render/shaders/glsl/rgbamatte.frag index a7213cfe0..72fdc1d68 100644 --- a/src/render/shaders/glsl/rgbamatte.frag +++ b/src/render/shaders/glsl/rgbamatte.frag @@ -1,11 +1,21 @@ #version 300 es +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable -precision highp float; -in vec2 v_texcoord; // is in 0-1 +#include "defines.h" + +precision highp float; +in vec2 v_texcoord; // is in 0-1 uniform sampler2D tex; uniform sampler2D texMatte; layout(location = 0) out vec4 fragColor; +#if USE_MIRROR +layout(location = 1) out vec4 mirrorColor; +#endif void main() { fragColor = texture(tex, v_texcoord) * texture(texMatte, v_texcoord)[0]; // I know it only uses R, but matte should be black/white anyways. +#if USE_MIRROR + mirrorColor = fragColor; +#endif } diff --git a/src/render/shaders/glsl/rgbx.frag b/src/render/shaders/glsl/rgbx.frag deleted file mode 100644 index 84672d767..000000000 --- a/src/render/shaders/glsl/rgbx.frag +++ /dev/null @@ -1,35 +0,0 @@ -#version 300 es -#extension GL_ARB_shading_language_include : enable -precision highp float; -in vec2 v_texcoord; -uniform sampler2D tex; -uniform float alpha; - -#include "rounding.glsl" - -uniform int discardOpaque; -uniform int discardAlpha; -uniform int discardAlphaValue; - -uniform int applyTint; -uniform vec3 tint; - -layout(location = 0) out vec4 fragColor; -void main() { - - if (discardOpaque == 1 && alpha == 1.0) - discard; - - vec4 pixColor = vec4(texture(tex, v_texcoord).rgb, 1.0); - - if (applyTint == 1) { - pixColor[0] = pixColor[0] * tint[0]; - pixColor[1] = pixColor[1] * tint[1]; - pixColor[2] = pixColor[2] * tint[2]; - } - - if (radius > 0.0) - pixColor = rounding(pixColor); - - fragColor = pixColor * alpha; -} diff --git a/src/render/shaders/glsl/rounding.glsl b/src/render/shaders/glsl/rounding.glsl index 472415fd6..64c6bcb98 100644 --- a/src/render/shaders/glsl/rounding.glsl +++ b/src/render/shaders/glsl/rounding.glsl @@ -1,13 +1,14 @@ +#ifndef ROUNDING_GLSL +#define ROUNDING_GLSL // smoothing constant for the edge: more = blurrier, but smoother -#define M_PI 3.1415926535897932384626433832795 +#define M_PI 3.1415926535897932384626433832795 #define SMOOTHING_CONSTANT (M_PI / 5.34665792551) -uniform float radius; -uniform float roundingPower; -uniform vec2 topLeft; -uniform vec2 fullSize; +float distanceWithRounding(vec2 coords, float roundingPower) { + return pow(pow(coords.x, roundingPower) + pow(coords.y, roundingPower), 1.0 / roundingPower); +} -vec4 rounding(vec4 color) { +vec4 rounding(vec4 color, float radius, float roundingPower, vec2 topLeft, vec2 fullSize) { vec2 pixCoord = vec2(gl_FragCoord); pixCoord -= topLeft + fullSize * 0.5; pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0; @@ -15,7 +16,7 @@ vec4 rounding(vec4 color) { pixCoord += vec2(1.0, 1.0) / fullSize; // center the pix don't make it top-left if (pixCoord.x + pixCoord.y > radius) { - float dist = pow(pow(pixCoord.x, roundingPower) + pow(pixCoord.y, roundingPower), 1.0/roundingPower); + float dist = distanceWithRounding(pixCoord, roundingPower); if (dist > radius + SMOOTHING_CONSTANT) discard; @@ -27,3 +28,4 @@ vec4 rounding(vec4 color) { return color; } +#endif \ No newline at end of file diff --git a/src/render/shaders/glsl/shadow.frag b/src/render/shaders/glsl/shadow.frag index b6fdf6ee0..e91e9a140 100644 --- a/src/render/shaders/glsl/shadow.frag +++ b/src/render/shaders/glsl/shadow.frag @@ -1,99 +1,71 @@ #version 300 es +#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable -precision highp float; -in vec4 v_color; -in vec2 v_texcoord; +#include "defines.h" -uniform int skipCM; -uniform int sourceTF; // eTransferFunction -uniform int targetTF; // eTransferFunction -uniform mat4x2 targetPrimaries; +precision highp float; +in vec4 v_color; +in vec2 v_texcoord; -uniform vec2 topLeft; -uniform vec2 bottomRight; -uniform vec2 fullSize; +uniform int sourceTF; // eTransferFunction +uniform int targetTF; // eTransferFunction +uniform mat3 targetPrimariesXYZ; + +uniform vec2 topLeft; +uniform vec2 bottomRight; +uniform vec2 windowTopLeft; +uniform vec2 windowBottomRight; +uniform vec2 fullSize; uniform float radius; uniform float roundingPower; uniform float range; uniform float shadowPower; +uniform float thick; +#if USE_CM +#include "cm_helpers.glsl" #include "CM.glsl" +#endif -float pixAlphaRoundedDistance(float distanceToCorner) { - if (distanceToCorner > radius) { - return 0.0; - } - - if (distanceToCorner > radius - range) { - return pow((range - (distanceToCorner - radius + range)) / range, shadowPower); // i think? - } - - return 1.0; -} - -float modifiedLength(vec2 a) { - return pow(pow(abs(a.x),roundingPower)+pow(abs(a.y),roundingPower),1.0/roundingPower); -} +#include "shadow.glsl" layout(location = 0) out vec4 fragColor; +#if USE_MIRROR +layout(location = 1) out vec4 mirrorColor; +#endif void main() { - - 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 + vec4 pixColor = v_color; +#if USE_MIRROR + vec4[2] pixColors = +#else + fragColor = +#endif + getShadow(pixColor, v_texcoord, radius, roundingPower, topLeft, fullSize, range, shadowPower, bottomRight, windowTopLeft, windowBottomRight, thick +#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 + ); +#if USE_MIRROR + fragColor = pixColors[0]; + mirrorColor = pixColors[1]; +#endif +} diff --git a/src/render/shaders/glsl/shadow.glsl b/src/render/shaders/glsl/shadow.glsl new file mode 100644 index 000000000..fdfa697c6 --- /dev/null +++ b/src/render/shaders/glsl/shadow.glsl @@ -0,0 +1,179 @@ +#ifndef ALLOW_INCLUDES +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable +#endif +#ifndef SHADOW_GLSL +#define SHADOW_GLSL + +#include "cm_helpers.glsl" +#include "rounding.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); +} + +bool pointInRoundedRect(vec2 pixCoord, vec2 tl, vec2 br, float radius, float roundingPower) { + if (pixCoord.x < tl.x || pixCoord.x > br.x || pixCoord.y < tl.y || pixCoord.y > br.y) + return false; + + if (radius <= 0.0) + return true; + + radius = min(radius, min((br.x - tl.x) * 0.5, (br.y - tl.y) * 0.5)); + + vec2 innerTL = tl + vec2(radius, radius); + vec2 innerBR = br - vec2(radius, radius); + + if (pixCoord.x >= innerTL.x && pixCoord.x <= innerBR.x) + return true; + + if (pixCoord.y >= innerTL.y && pixCoord.y <= innerBR.y) + return true; + + vec2 delta = vec2(0.0, 0.0); + delta.x = pixCoord.x < innerTL.x ? innerTL.x - pixCoord.x : pixCoord.x - innerBR.x; + delta.y = pixCoord.y < innerTL.y ? innerTL.y - pixCoord.y : pixCoord.y - innerBR.y; + + return distanceWithRounding(delta, roundingPower) <= radius; +} + +#if USE_MIRROR +vec4[2] +#else +vec4 +#endif + getShadow(vec4 pixColor, vec2 v_texcoord, float borderRadius, float roundingPower, vec2 topLeft, vec2 fullSize, float range, float shadowPower, vec2 bottomRight, + vec2 windowTopLeft, vec2 windowBottomRight, float windowRadius +#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 radius = range + borderRadius; + float originalAlpha = pixColor[3]; + + bool done = false; + + vec2 pixCoord = fullSize * v_texcoord; + + // ok, now we check the distance to a border. + // corners + 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; + } + } + + // edges + 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) { + // between border and max shadow distance + pixColor[3] = pixColor[3] * pow((smallest / range), shadowPower); + } + } + + if (pointInRoundedRect(pixCoord, windowTopLeft, windowBottomRight, windowRadius, roundingPower)) + pixColor[3] = 0.0; + + if (pixColor[3] == 0.0) { + discard; +#if USE_MIRROR + vec4[2] pixColors; + pixColors[0] = pixColor; + pixColors[1] = pixColor; + return pixColors; +#else + return pixColor; +#endif + } + + // premultiply + pixColor.rgb *= pixColor[3]; + +#if USE_CM +#if USE_MIRROR + vec4[2] pixColors = +#else + pixColor = +#endif + 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_MIRROR + return pixColors; +#else + return pixColor; +#endif +} +#endif diff --git a/src/render/shaders/glsl/surface.frag b/src/render/shaders/glsl/surface.frag new file mode 100644 index 000000000..596cd8fba --- /dev/null +++ b/src/render/shaders/glsl/surface.frag @@ -0,0 +1,136 @@ +#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; +#if USE_MIRROR +layout(location = 1) out vec4 mirrorColor; +#endif +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 +#if USE_MIRROR + vec4[2] pixColors = +#else + pixColor = +#endif + 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_MIRROR + pixColor = pixColors[0]; + mirrorColor = pixColors[1]; +#endif + +#if USE_TINT + pixColor.rgb = pixColor.rgb * tint; +#endif + +#if USE_ROUNDING + pixColor = rounding(pixColor, radius, roundingPower, topLeft, fullSize); +#endif + pixColor *= alpha; +#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; +#if USE_MIRROR +#if USE_TINT + mirrorColor.rgb = mirrorColor.rgb * tint; +#endif + +#if USE_ROUNDING + mirrorColor = rounding(mirrorColor, radius, roundingPower, topLeft, fullSize); +#endif + mirrorColor = mirrorColor * alpha; +#if USE_BLUR +#if USE_DISCARD + mirrorColor = mix(mirrorColor, vec4(mix(texture(blurredBG, v_texcoord * uvSize + uvOffset).rgb, mirrorColor.rgb, mirrorColor.a), 1.0), + discardAlpha && (mirrorColor.a <= discardAlphaValue) ? 0.0 : 1.0); +#else + mirrorColor = vec4(mix(texture(blurredBG, v_texcoord * uvSize + uvOffset).rgb, mirrorColor.rgb, mirrorColor.a), 1.0); +#endif +#endif + +#endif +} diff --git a/src/render/shaders/glsl/tonemap.glsl b/src/render/shaders/glsl/tonemap.glsl new file mode 100644 index 000000000..dcce45a67 --- /dev/null +++ b/src/render/shaders/glsl/tonemap.glsl @@ -0,0 +1,70 @@ +#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; + + // kind of works but doesn't use newLum at all + return vec4(fromLMS * toLinear(vec4(ICtCpPQInv * ICtCp, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb * HDR_MAX_LUMINANCE * refScale, color[3]); + // breaks with overriden monitor luminances. might be caused by incorrect imput values + // @gulafaran + // vec3 outRGB = fromLMS * toLinear(vec4(ICtCpPQInv * ICtCp, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb; + // outRGB *= (newLum / max(luminance, 0.0001)); // actually apply the tone mapping + // return vec4(clamp(outRGB * HDR_MAX_LUMINANCE * refScale, 0.0, dstMaxLuminance), color[3]); +} diff --git a/src/render/types.hpp b/src/render/types.hpp new file mode 100644 index 000000000..a33c7b6df --- /dev/null +++ b/src/render/types.hpp @@ -0,0 +1,131 @@ +#pragma once + +#include "Framebuffer.hpp" +#include "../desktop/DesktopTypes.hpp" +#include "../helpers/cm/ColorManagement.hpp" +#include "../protocols/core/Compositor.hpp" +#include +#include +#include +#include +#include + +namespace Render { + const std::vector ASSET_PATHS = { +#ifdef DATAROOTDIR + DATAROOTDIR, +#endif + "/usr/share", + "/usr/local/share", + }; + + enum eDamageTrackingModes : int8_t { + DAMAGE_TRACKING_INVALID = -1, + DAMAGE_TRACKING_NONE = 0, + DAMAGE_TRACKING_MONITOR, + DAMAGE_TRACKING_FULL, + }; + + enum eRenderPassMode : uint8_t { + RENDER_PASS_ALL = 0, + RENDER_PASS_MAIN, + RENDER_PASS_POPUP + }; + + enum eRenderMode : uint8_t { + RENDER_MODE_NORMAL = 0, + RENDER_MODE_FULL_FAKE = 1, + RENDER_MODE_TO_BUFFER = 2, + RENDER_MODE_TO_BUFFER_READ_ONLY = 3, + }; + + struct SRenderWorkspaceUntilData { + PHLLS ls; + PHLWINDOW w; + }; + + enum eRenderProjectionType : uint8_t { + RPT_MONITOR, + RPT_MIRROR, + RPT_FB, + RPT_EXPORT, + }; + + struct SRenderModifData { + enum eRenderModifType : uint8_t { + RMOD_TYPE_SCALE, /* scale by a float */ + RMOD_TYPE_SCALECENTER, /* scale by a float from the center */ + RMOD_TYPE_TRANSLATE, /* translate by a Vector2D */ + RMOD_TYPE_ROTATE, /* rotate by a float in rad from top left */ + RMOD_TYPE_ROTATECENTER, /* rotate by a float in rad from center */ + }; + + std::vector> modifs; + + void applyToBox(Hyprutils::Math::CBox& box); + void applyToRegion(Hyprutils::Math::CRegion& rg); + float combinedScale(); + + bool enabled = true; + }; + + struct SRenderData { + // can be private + Hyprutils::Math::Mat3x3 targetProjection; + + // ---------------------- + + // used by public + Hyprutils::Math::Vector2D fbSize = {-1, -1}; + PHLMONITORREF pMonitor; + + eRenderProjectionType projectionType = RPT_MONITOR; + + SP currentFB = nullptr; // current rendering to + SP mainFB = nullptr; // main to render to + SP outFB = nullptr; // out to render to (if offloaded, etc) + + CRegion damage; + CRegion finalDamage; // damage used for funal off -> main + + SRenderModifData renderModif; + float mouseZoomFactor = 1.f; + bool mouseZoomUseMouse = true; // true by default + bool useNearestNeighbor = false; + bool blockScreenShader = false; + + Vector2D primarySurfaceUVTopLeft = Vector2D(-1, -1); + Vector2D primarySurfaceUVBottomRight = Vector2D(-1, -1); + + // TODO remove and pass directly + CBox clipBox = {}; // scaled coordinates + PHLWINDOWREF currentWindow; + WP surface; + + bool transformDamage = true; + bool noSimplify = false; + }; + + 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; + }; +} \ No newline at end of file diff --git a/src/signal-safe.hpp b/src/signal-safe.hpp deleted file mode 100644 index a48b51b0d..000000000 --- a/src/signal-safe.hpp +++ /dev/null @@ -1,175 +0,0 @@ -#pragma once - -#include "defines.hpp" -#include - -template -class CMaxLengthCString { - public: - CMaxLengthCString() { - m_str[0] = '\0'; - } - void operator+=(char const* rhs) { - write(rhs, strlen(rhs)); - } - void write(char const* data, size_t len) { - if (m_boundsExceeded || m_strPos + len >= N) { - m_boundsExceeded = true; - return; - } - memcpy(m_str + m_strPos, data, len); - m_strPos += len; - m_str[m_strPos] = '\0'; - } - void write(char c) { - if (m_boundsExceeded || m_strPos + 1 >= N) { - m_boundsExceeded = true; - return; - } - m_str[m_strPos] = c; - m_strPos++; - } - void writeNum(size_t num) { - size_t d = 1; - while (num / 10 >= d) - d *= 10; - while (num > 0) { - char c = '0' + (num / d); - write(c); - num %= d; - d /= 10; - } - } - char const* getStr() { - return m_str; - }; - bool boundsExceeded() { - return m_boundsExceeded; - }; - - private: - char m_str[N]; - size_t m_strPos = 0; - bool m_boundsExceeded = false; -}; - -template -class CBufFileWriter { - public: - CBufFileWriter(int fd_) : m_fd(fd_) {} - ~CBufFileWriter() { - flush(); - } - void write(char const* data, size_t len) { - while (len > 0) { - size_t to_add = std::min(len, sc(BUFSIZE) - m_writeBufPos); - memcpy(m_writeBuf + m_writeBufPos, data, to_add); - data += to_add; - len -= to_add; - m_writeBufPos += to_add; - if (m_writeBufPos == BUFSIZE) - flush(); - } - } - void write(char c) { - if (m_writeBufPos == BUFSIZE) - flush(); - m_writeBuf[m_writeBufPos] = c; - m_writeBufPos++; - } - void operator+=(char const* str) { - write(str, strlen(str)); - } - void operator+=(std::string_view str) { - write(str.data(), str.size()); - } - void operator+=(char c) { - write(c); - } - void writeNum(size_t num) { - size_t d = 1; - while (num / 10 >= d) - d *= 10; - while (num > 0) { - char c = '0' + (num / d); - write(c); - num %= d; - d /= 10; - } - } - void writeCmdOutput(const char* cmd) { - int pipefd[2]; - if (pipe(pipefd) < 0) { - *this += "(argv)); - - CBufFileWriter<64> failmsg(pipefd[1]); - failmsg += " 0) { - write(readbuf, len); - } - if (len < 0) { - *this += "(xcb_get_property_value(proxyVerifyReply)); if (verifyWindow == proxyWindow) { targetWindow = proxyWindow; - Debug::log(LOG, "Using XdndProxy window {:x} for window {:x}", proxyWindow, window); + Log::logger->log(Log::DEBUG, "Using XdndProxy window {:x} for window {:x}", proxyWindow, window); } } free(proxyVerifyReply); // NOLINT(cppcoreguidelines-no-malloc) @@ -103,14 +103,14 @@ void CX11DataDevice::sendEnter(uint32_t serial, SP surf, con auto XSURF = g_pXWayland->m_wm->windowForWayland(surf); if (!XSURF) { - Debug::log(ERR, "CX11DataDevice::sendEnter: No xwayland surface for destination"); + Log::logger->log(Log::ERR, "CX11DataDevice::sendEnter: No xwayland surface for destination"); return; } auto SOURCE = offer->getSource(); if (!SOURCE) { - Debug::log(ERR, "CX11DataDevice::sendEnter: No source"); + Log::logger->log(Log::ERR, "CX11DataDevice::sendEnter: No source"); return; } @@ -141,7 +141,7 @@ void CX11DataDevice::sendEnter(uint32_t serial, SP surf, con auto hlSurface = XSURF->m_surface.lock(); if (!hlSurface) { - Debug::log(ERR, "CX11DataDevice::sendEnter: Non desktop x surface?!"); + Log::logger->log(Log::ERR, "CX11DataDevice::sendEnter: Non desktop x surface?!"); m_lastSurfaceCoords = {}; return; } @@ -198,7 +198,7 @@ void CX11DataDevice::sendMotion(uint32_t timeMs, const Vector2D& local) { void CX11DataDevice::sendDrop() { #ifndef NO_XWAYLAND if (!m_lastSurface || !m_lastOffer) { - Debug::log(ERR, "CX11DataDevice::sendDrop: No surface or offer"); + Log::logger->log(Log::ERR, "CX11DataDevice::sendDrop: No surface or offer"); return; } @@ -256,7 +256,7 @@ bool CX11DataSource::dndDone() { } void CX11DataSource::error(uint32_t code, const std::string& msg) { - Debug::log(ERR, "CX11DataSource error: code {} msg {}", code, msg); + Log::logger->log(Log::ERR, "CX11DataSource error: code {} msg {}", code, msg); m_dndSuccess = false; m_dropped = false; } diff --git a/src/xwayland/Server.cpp b/src/xwayland/Server.cpp index 6750db10f..b33dbfb5f 100644 --- a/src/xwayland/Server.cpp +++ b/src/xwayland/Server.cpp @@ -21,7 +21,7 @@ #include "Server.hpp" #include "XWayland.hpp" #include "config/ConfigValue.hpp" -#include "debug/Log.hpp" +#include "debug/log/Logger.hpp" #include "../defines.hpp" #include "../Compositor.hpp" #include "../managers/CursorManager.hpp" @@ -41,12 +41,12 @@ static CFileDescriptor createSocket(struct sockaddr_un* addr, size_t pathSize) { socklen_t size = offsetof(struct sockaddr_un, sun_path) + pathSize + 1; CFileDescriptor fd{socket(AF_UNIX, SOCK_STREAM, 0)}; if (!fd.isValid()) { - Debug::log(ERR, "Failed to create socket {}{}", dbgSocketPathPrefix, dbgSocketPathRem); + Log::logger->log(Log::ERR, "Failed to create socket {}{}", dbgSocketPathPrefix, dbgSocketPathRem); return {}; } if (!fd.setFlags(fd.getFlags() | FD_CLOEXEC)) { - Debug::log(ERR, "Failed to set flags for socket {}{}", dbgSocketPathPrefix, dbgSocketPathRem); + Log::logger->log(Log::ERR, "Failed to set flags for socket {}{}", dbgSocketPathPrefix, dbgSocketPathRem); return {}; } @@ -54,7 +54,7 @@ static CFileDescriptor createSocket(struct sockaddr_un* addr, size_t pathSize) { unlink(addr->sun_path); if (bind(fd.get(), rc(addr), size) < 0) { - Debug::log(ERR, "Failed to bind socket {}{}", dbgSocketPathPrefix, dbgSocketPathRem); + Log::logger->log(Log::ERR, "Failed to bind socket {}{}", dbgSocketPathPrefix, dbgSocketPathRem); if (isRegularSocket) unlink(addr->sun_path); return {}; @@ -66,11 +66,11 @@ static CFileDescriptor createSocket(struct sockaddr_un* addr, size_t pathSize) { if (isRegularSocket && chmod(addr->sun_path, 0666) < 0) { // We are only extending the default permissions, // and I don't see the reason to make a full stop in case of a failed operation. - Debug::log(ERR, "Failed to set permission mode for socket {}{}", dbgSocketPathPrefix, dbgSocketPathRem); + Log::logger->log(Log::ERR, "Failed to set permission mode for socket {}{}", dbgSocketPathPrefix, dbgSocketPathRem); } if (listen(fd.get(), SOCKET_BACKLOG) < 0) { - Debug::log(ERR, "Failed to listen to socket {}{}", dbgSocketPathPrefix, dbgSocketPathRem); + Log::logger->log(Log::ERR, "Failed to listen to socket {}{}", dbgSocketPathPrefix, dbgSocketPathRem); if (isRegularSocket) unlink(addr->sun_path); return {}; @@ -83,23 +83,23 @@ static bool checkPermissionsForSocketDir() { struct stat buf; if (lstat("/tmp/.X11-unix", &buf)) { - Debug::log(ERR, "Failed to stat X11 socket dir"); + Log::logger->log(Log::ERR, "Failed to stat X11 socket dir"); return false; } if (!(buf.st_mode & S_IFDIR)) { - Debug::log(ERR, "X11 socket dir is not a directory"); + Log::logger->log(Log::ERR, "X11 socket dir is not a directory"); return false; } if ((buf.st_uid != 0) && (buf.st_uid != getuid())) { - Debug::log(ERR, "X11 socket dir is not owned by root or current user"); + Log::logger->log(Log::ERR, "X11 socket dir is not owned by root or current user"); return false; } if (!(buf.st_mode & S_ISVTX)) { if ((buf.st_mode & (S_IWGRP | S_IWOTH))) { - Debug::log(ERR, "X11 socket dir is writable by others"); + Log::logger->log(Log::ERR, "X11 socket dir is writable by others"); return false; } } @@ -112,7 +112,7 @@ static bool ensureSocketDirExists() { if (errno == EEXIST) return checkPermissionsForSocketDir(); else { - Debug::log(ERR, "XWayland: Couldn't create socket dir"); + Log::logger->log(Log::ERR, "XWayland: Couldn't create socket dir"); return false; } } @@ -138,24 +138,29 @@ static bool openSockets(std::array& sockets, int display) { #ifdef __linux__ if (*CREATEABSTRACTSOCKET) { - // cursed... - // but is kept as an option for better compatibility - addr.sun_path[0] = 0; + addr.sun_path[0] = '\0'; path = getSocketPath(display, true); - strncpy(addr.sun_path + 1, path.c_str(), path.length() + 1); + + strncpy(addr.sun_path + 1, path.c_str(), sizeof(addr.sun_path) - 2); } else { path = getSocketPath(display, false); - strncpy(addr.sun_path, path.c_str(), path.length() + 1); + + strncpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path) - 1); + addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; } #else if (*CREATEABSTRACTSOCKET) { - Debug::log(WARN, "The abstract XWayland Unix domain socket might be used only on Linux systems. A regular one'll be created instead."); + Log::logger->log(Log::WARN, "The abstract XWayland Unix domain socket might be used only on Linux systems. A regular one'll be created instead."); } + path = getSocketPath(display, false); - strncpy(addr.sun_path, path.c_str(), path.length() + 1); + + strncpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path) - 1); + addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; #endif sockets[0] = CFileDescriptor{createSocket(&addr, path.length())}; + if (!sockets[0].isValid()) return false; @@ -173,7 +178,7 @@ static bool openSockets(std::array& sockets, int display) { static void startServer(void* data) { if (!g_pXWayland->m_server->start()) - Debug::log(ERR, "The XWayland server could not start! XWayland will not work..."); + Log::logger->log(Log::ERR, "The XWayland server could not start! XWayland will not work..."); } static int xwaylandReady(int fd, uint32_t mask, void* data) { @@ -183,7 +188,7 @@ static int xwaylandReady(int fd, uint32_t mask, void* data) { static bool safeRemove(const std::string& path) { try { return std::filesystem::remove(path); - } catch (const std::exception& e) { Debug::log(ERR, "[XWayland] Failed to remove {}", path); } + } catch (const std::exception& e) { Log::logger->log(Log::ERR, "[XWayland] Failed to remove {}", path); } return false; } @@ -217,7 +222,10 @@ bool CXWaylandServer::tryOpenSockets() { continue; char pidstr[12] = {0}; - read(fd.get(), pidstr, sizeof(pidstr) - 1); + if (read(fd.get(), pidstr, sizeof(pidstr) - 1) < 0) { + Log::logger->log(Log::ERR, "Failed to read on fd {}: {}", fd.get(), strerror(errno)); + continue; + } int32_t pid = 0; try { @@ -232,11 +240,11 @@ bool CXWaylandServer::tryOpenSockets() { } if (m_display < 0) { - Debug::log(ERR, "Failed to find a suitable socket for XWayland"); + Log::logger->log(Log::ERR, "Failed to find a suitable socket for XWayland"); return false; } - Debug::log(LOG, "XWayland found a suitable display socket at DISPLAY: {}", m_displayName); + Log::logger->log(Log::DEBUG, "XWayland found a suitable display socket at DISPLAY: {}", m_displayName); return true; } @@ -295,7 +303,7 @@ bool CXWaylandServer::create() { void CXWaylandServer::runXWayland(CFileDescriptor& notifyFD) { if (!m_xFDs[0].setFlags(m_xFDs[0].getFlags() & ~FD_CLOEXEC) || !m_xFDs[1].setFlags(m_xFDs[1].getFlags() & ~FD_CLOEXEC) || !m_waylandFDs[1].setFlags(m_waylandFDs[1].getFlags() & ~FD_CLOEXEC) || !m_xwmFDs[1].setFlags(m_xwmFDs[1].getFlags() & ~FD_CLOEXEC)) { - Debug::log(ERR, "Failed to unset cloexec on fds"); + Log::logger->log(Log::ERR, "Failed to unset cloexec on fds"); _exit(EXIT_FAILURE); } @@ -305,11 +313,11 @@ void CXWaylandServer::runXWayland(CFileDescriptor& notifyFD) { auto waylandSocket = std::format("{}", m_waylandFDs[1].get()); setenv("WAYLAND_SOCKET", waylandSocket.c_str(), true); - Debug::log(LOG, "Starting XWayland with \"{}\", bon voyage!", cmd); + Log::logger->log(Log::DEBUG, "Starting XWayland with \"{}\", bon voyage!", cmd); execl("/bin/sh", "/bin/sh", "-c", cmd.c_str(), nullptr); - Debug::log(ERR, "XWayland failed to open"); + Log::logger->log(Log::ERR, "XWayland failed to open"); _exit(1); } @@ -317,7 +325,7 @@ bool CXWaylandServer::start() { m_idleSource = nullptr; int wlPair[2] = {-1, -1}; if (socketpair(AF_UNIX, SOCK_STREAM, 0, wlPair) != 0) { - Debug::log(ERR, "socketpair failed (1)"); + Log::logger->log(Log::ERR, "socketpair failed (1)"); die(); return false; } @@ -325,14 +333,14 @@ bool CXWaylandServer::start() { m_waylandFDs[1] = CFileDescriptor{wlPair[1]}; if (!m_waylandFDs[0].setFlags(m_waylandFDs[0].getFlags() | FD_CLOEXEC) || !m_waylandFDs[1].setFlags(m_waylandFDs[1].getFlags() | FD_CLOEXEC)) { - Debug::log(ERR, "set_cloexec failed (1)"); + Log::logger->log(Log::ERR, "set_cloexec failed (1)"); die(); return false; } int xwmPair[2] = {-1, -1}; if (socketpair(AF_UNIX, SOCK_STREAM, 0, xwmPair) != 0) { - Debug::log(ERR, "socketpair failed (2)"); + Log::logger->log(Log::ERR, "socketpair failed (2)"); die(); return false; } @@ -341,14 +349,14 @@ bool CXWaylandServer::start() { m_xwmFDs[1] = CFileDescriptor{xwmPair[1]}; if (!m_xwmFDs[0].setFlags(m_xwmFDs[0].getFlags() | FD_CLOEXEC) || !m_xwmFDs[1].setFlags(m_xwmFDs[1].getFlags() | FD_CLOEXEC)) { - Debug::log(ERR, "set_cloexec failed (2)"); + Log::logger->log(Log::ERR, "set_cloexec failed (2)"); die(); return false; } m_xwaylandClient = wl_client_create(g_pCompositor->m_wlDisplay, m_waylandFDs[0].get()); if (!m_xwaylandClient) { - Debug::log(ERR, "wl_client_create failed"); + Log::logger->log(Log::ERR, "wl_client_create failed"); die(); return false; } @@ -357,7 +365,7 @@ bool CXWaylandServer::start() { int notify[2] = {-1, -1}; if (pipe(notify) < 0) { - Debug::log(ERR, "pipe failed"); + Log::logger->log(Log::ERR, "pipe failed"); die(); return false; } @@ -365,7 +373,7 @@ bool CXWaylandServer::start() { CFileDescriptor notifyFds[2] = {CFileDescriptor{notify[0]}, CFileDescriptor{notify[1]}}; if (!notifyFds[0].setFlags(notifyFds[0].getFlags() | FD_CLOEXEC)) { - Debug::log(ERR, "set_cloexec failed (3)"); + Log::logger->log(Log::ERR, "set_cloexec failed (3)"); die(); return false; } @@ -375,7 +383,7 @@ bool CXWaylandServer::start() { auto serverPID = fork(); if (serverPID < 0) { - Debug::log(ERR, "fork failed"); + Log::logger->log(Log::ERR, "fork failed"); die(); return false; } else if (serverPID == 0) { @@ -392,7 +400,7 @@ int CXWaylandServer::ready(int fd, uint32_t mask) { char buf[64]; ssize_t n = read(fd, buf, sizeof(buf)); if (n < 0 && errno != EINTR) { - Debug::log(ERR, "Xwayland: read from displayFd failed"); + Log::logger->log(Log::ERR, "Xwayland: read from displayFd failed"); mask = 0; } else if (n <= 0 || buf[n - 1] != '\n') return 1; @@ -400,12 +408,12 @@ int CXWaylandServer::ready(int fd, uint32_t mask) { // if we don't have readable here, it failed if (!(mask & WL_EVENT_READABLE)) { - Debug::log(ERR, "Xwayland: startup failed, not setting up xwm"); + Log::logger->log(Log::ERR, "Xwayland: startup failed, not setting up xwm"); g_pXWayland->m_server.reset(); return 1; } - Debug::log(LOG, "XWayland is ready"); + Log::logger->log(Log::DEBUG, "XWayland is ready"); wl_event_source_remove(m_pipeSource); m_pipeFd.reset(); diff --git a/src/xwayland/XDataSource.cpp b/src/xwayland/XDataSource.cpp index 8e7b25054..5d34a9d8d 100644 --- a/src/xwayland/XDataSource.cpp +++ b/src/xwayland/XDataSource.cpp @@ -65,11 +65,11 @@ void CXDataSource::send(const std::string& mime, CFileDescriptor fd) { } if (!mimeAtom) { - Debug::log(ERR, "[XDataSource] mime atom not found"); + Log::logger->log(Log::ERR, "[XDataSource] mime atom not found"); return; } - Debug::log(LOG, "[XDataSource] send with mime {} to fd {}", mime, fd.get()); + Log::logger->log(Log::DEBUG, "[XDataSource] send with mime {} to fd {}", mime, fd.get()); auto transfer = makeUnique(m_selection); transfer->incomingWindow = xcb_generate_id(g_pXWayland->m_wm->getConnection()); @@ -94,15 +94,15 @@ void CXDataSource::send(const std::string& mime, CFileDescriptor fd) { } void CXDataSource::accepted(const std::string& mime) { - Debug::log(LOG, "[XDataSource] accepted is a stub"); + Log::logger->log(Log::DEBUG, "[XDataSource] accepted is a stub"); } void CXDataSource::cancelled() { - Debug::log(LOG, "[XDataSource] cancelled is a stub"); + Log::logger->log(Log::DEBUG, "[XDataSource] cancelled is a stub"); } void CXDataSource::error(uint32_t code, const std::string& msg) { - Debug::log(LOG, "[XDataSource] error is a stub: err {}: {}", code, msg); + Log::logger->log(Log::DEBUG, "[XDataSource] error is a stub: err {}: {}", code, msg); } eDataSourceType CXDataSource::type() { diff --git a/src/xwayland/XSurface.cpp b/src/xwayland/XSurface.cpp index 73c512f26..5c5f3b5c9 100644 --- a/src/xwayland/XSurface.cpp +++ b/src/xwayland/XSurface.cpp @@ -7,8 +7,6 @@ #ifndef NO_XWAYLAND -#include - CXWaylandSurface::CXWaylandSurface(uint32_t xID_, CBox geometry_, bool OR) : m_xID(xID_), m_geometry(geometry_), m_overrideRedirect(OR) { xcb_res_query_client_ids_cookie_t client_id_cookie = {0}; if (g_pXWayland->m_wm->m_xres) { @@ -42,9 +40,43 @@ CXWaylandSurface::CXWaylandSurface(uint32_t xID_, CBox geometry_, bool OR) : m_x free(reply); // NOLINT(cppcoreguidelines-no-malloc) } + // FIXME: this is a race, we need to listen to props changed + recheckSupportedProps(); + m_events.resourceChange.listenStatic([this] { ensureListeners(); }); } +void CXWaylandSurface::recheckSupportedProps() { + m_supportedProps.clear(); + + auto listCookie = xcb_list_properties(g_pXWayland->m_wm->getConnection(), m_xID); + auto* listReply = xcb_list_properties_reply(g_pXWayland->m_wm->getConnection(), listCookie, nullptr); + auto getCookie = xcb_get_property(g_pXWayland->m_wm->getConnection(), 0, m_xID, HYPRATOMS["WM_PROTOCOLS"], XCB_ATOM_ATOM, 0, 32); + auto* getReply = xcb_get_property_reply(g_pXWayland->m_wm->getConnection(), getCookie, nullptr); + + if (listReply) { + const auto* atoms = xcb_list_properties_atoms(listReply); + auto len = xcb_list_properties_atoms_length(listReply); + + for (auto i = 0; i < len; ++i) { + m_supportedProps[atoms[i]] = true; + } + + free(listReply); + } + + if (getReply) { + const auto* protocols = sc(xcb_get_property_value(getReply)); + const auto len = xcb_get_property_value_length(getReply) / sizeof(xcb_atom_t); + + for (auto i = 0u; i < len; ++i) { + m_supportedProps[protocols[i]] = true; + } + + free(getReply); + } +} + void CXWaylandSurface::ensureListeners() { bool connected = m_listeners.destroySurface; @@ -98,7 +130,7 @@ void CXWaylandSurface::map() { m_mapped = true; m_surface->map(); - Debug::log(LOG, "XWayland surface {:x} mapping", rc(this)); + Log::logger->log(Log::DEBUG, "XWayland surface {:x} mapping", rc(this)); m_events.map.emit(); @@ -118,7 +150,7 @@ void CXWaylandSurface::unmap() { m_events.unmap.emit(); m_surface->unmap(); - Debug::log(LOG, "XWayland surface {:x} unmapping", rc(this)); + Log::logger->log(Log::DEBUG, "XWayland surface {:x} unmapping", rc(this)); g_pXWayland->m_wm->updateClientList(); } @@ -128,17 +160,17 @@ void CXWaylandSurface::considerMap() { return; if (!m_surface) { - Debug::log(LOG, "XWayland surface: considerMap, nope, no surface"); + Log::logger->log(Log::DEBUG, "XWayland surface: considerMap, nope, no surface"); return; } if (m_surface->m_current.texture) { - Debug::log(LOG, "XWayland surface: considerMap, sure, we have a buffer"); + Log::logger->log(Log::DEBUG, "XWayland surface: considerMap, sure, we have a buffer"); map(); return; } - Debug::log(LOG, "XWayland surface: considerMap, nope, we don't have a buffer"); + Log::logger->log(Log::DEBUG, "XWayland surface: considerMap, nope, we don't have a buffer"); } bool CXWaylandSurface::wantsFocus() { @@ -226,10 +258,19 @@ void CXWaylandSurface::restackToTop() { } void CXWaylandSurface::close() { - xcb_client_message_data_t msg = {}; - msg.data32[0] = HYPRATOMS["WM_DELETE_WINDOW"]; - msg.data32[1] = XCB_CURRENT_TIME; - g_pXWayland->m_wm->sendWMMessage(m_self.lock(), &msg, XCB_EVENT_MASK_NO_EVENT); + + // Recheck the supported props, check if we maybe have WM_DELETE_WINDOW. + recheckSupportedProps(); + + if (m_supportedProps[HYPRATOMS["WM_DELETE_WINDOW"]]) { + xcb_client_message_data_t msg = {}; + msg.data32[0] = HYPRATOMS["WM_DELETE_WINDOW"]; + msg.data32[1] = XCB_CURRENT_TIME; + g_pXWayland->m_wm->sendWMMessage(m_self.lock(), &msg, XCB_EVENT_MASK_NO_EVENT); + } else { + xcb_kill_client(g_pXWayland->m_wm->getConnection(), m_self->m_xID); + xcb_flush(g_pXWayland->m_wm->getConnection()); + } } void CXWaylandSurface::setWithdrawn(bool withdrawn_) { @@ -250,7 +291,7 @@ void CXWaylandSurface::ping() { bool supportsPing = std::ranges::find(m_protocols, HYPRATOMS["_NET_WM_PING"]) != m_protocols.end(); if (!supportsPing) { - Debug::log(TRACE, "CXWaylandSurface: XID {} does not support ping, just sending an instant reply", m_xID); + Log::logger->log(Log::TRACE, "CXWaylandSurface: XID {} does not support ping, just sending an instant reply", m_xID); g_pANRManager->onResponse(m_self.lock()); return; } diff --git a/src/xwayland/XSurface.hpp b/src/xwayland/XSurface.hpp index 10eecbaf2..c12edcf24 100644 --- a/src/xwayland/XSurface.hpp +++ b/src/xwayland/XSurface.hpp @@ -9,9 +9,10 @@ class CWLSurfaceResource; class CXWaylandSurfaceResource; #ifdef NO_XWAYLAND -using xcb_pixmap_t = uint32_t; -using xcb_window_t = uint32_t; -using xcb_icccm_wm_hints_t = struct { +using xcb_pixmap_t = uint32_t; +using xcb_window_t = uint32_t; +using xcb_atom_t = uint32_t; +struct xcb_icccm_wm_hints_t { int32_t flags; uint32_t input; int32_t initial_state; @@ -21,7 +22,7 @@ using xcb_icccm_wm_hints_t = struct { xcb_pixmap_t icon_mask; xcb_window_t window_group; }; -using xcb_size_hints_t = struct { +struct xcb_size_hints_t { uint32_t flags; int32_t x, y; int32_t width, height; @@ -111,6 +112,7 @@ class CXWaylandSurface { void unmap(); void considerMap(); void setWithdrawn(bool withdrawn); + void recheckSupportedProps(); struct { CHyprSignalListener destroyResource; @@ -118,5 +120,7 @@ class CXWaylandSurface { CHyprSignalListener commitSurface; } m_listeners; + std::unordered_map m_supportedProps; + friend class CXWM; }; diff --git a/src/xwayland/XWM.cpp b/src/xwayland/XWM.cpp index 316a97880..665aedfb4 100644 --- a/src/xwayland/XWM.cpp +++ b/src/xwayland/XWM.cpp @@ -18,6 +18,7 @@ #include "../managers/eventLoop/EventLoopManager.hpp" #include "../managers/SeatManager.hpp" #include "../managers/ANRManager.hpp" +#include "../helpers/env/Env.hpp" #include "../protocols/XWaylandShell.hpp" #include "../protocols/core/Compositor.hpp" #include "../desktop/state/FocusState.hpp" @@ -32,6 +33,8 @@ static int onX11Event(int fd, uint32_t mask, void* data) { return g_pXWayland->m_wm->onEvent(fd, mask); } +static int writeDataSource(int fd, uint32_t mask, void* data); + struct SFreeDeleter { void operator()(void* ptr) const { std::free(ptr); // NOLINT(cppcoreguidelines-no-malloc) @@ -56,15 +59,17 @@ void CXWM::handleCreate(xcb_create_notify_event_t* e) { const auto XSURF = m_surfaces.emplace_back(SP(new CXWaylandSurface(e->window, CBox{e->x, e->y, e->width, e->height}, e->override_redirect))); XSURF->m_self = XSURF; - Debug::log(LOG, "[xwm] New XSurface at {:x} with xid of {}", rc(XSURF.get()), e->window); + Log::logger->log(Log::DEBUG, "[xwm] New XSurface at {:x} with xid of {}", rc(XSURF.get()), e->window); - const auto WINDOW = CWindow::create(XSURF); + const auto WINDOW = Desktop::View::CWindow::create(XSURF); g_pCompositor->m_windows.emplace_back(WINDOW); WINDOW->m_self = WINDOW; - Debug::log(LOG, "[xwm] New XWayland window at {:x} for surf {:x}", rc(WINDOW.get()), rc(XSURF.get())); + Log::logger->log(Log::DEBUG, "[xwm] New XWayland window at {:x} for surf {:x}", rc(WINDOW.get()), rc(XSURF.get())); } void CXWM::handleDestroy(xcb_destroy_notify_event_t* e) { + removeTransfersForWindow(e->window); + const auto XSURF = windowForXID(e->window); if (!XSURF) @@ -123,8 +128,8 @@ void CXWM::handleMapRequest(xcb_map_request_event_t* e) { if (SMALL && !XSURF->m_overrideRedirect) // default to 800 x 800 XSURF->configure({XSURF->m_geometry.pos(), DESIREDSIZE}); - Debug::log(LOG, "[xwm] Mapping window {} in X (geometry {}x{} at {}x{}))", e->window, XSURF->m_geometry.width, XSURF->m_geometry.height, XSURF->m_geometry.x, - XSURF->m_geometry.y); + Log::logger->log(Log::DEBUG, "[xwm] Mapping window {} in X (geometry {}x{} at {}x{}))", e->window, XSURF->m_geometry.width, XSURF->m_geometry.height, XSURF->m_geometry.x, + XSURF->m_geometry.y); // read data again. Some apps for some reason fail to send WINDOW_TYPE // this shouldn't happen but does, I prolly fucked up somewhere, this is a band-aid @@ -208,7 +213,7 @@ std::string CXWM::getAtomName(uint32_t atom) { void CXWM::readProp(SP XSURF, uint32_t atom, xcb_get_property_reply_t* reply) { std::string propName; - if (Debug::m_trace) + if (Env::isTrace()) propName = getAtomName(atom); const auto valueLen = xcb_get_property_value_length(reply); @@ -274,7 +279,7 @@ void CXWM::readProp(SP XSURF, uint32_t atom, xcb_get_property_ XSURF->m_parent = NEWXSURF; NEWXSURF->m_children.emplace_back(XSURF); } else - Debug::log(LOG, "[xwm] Denying transient because it would create a loop"); + Log::logger->log(Log::DEBUG, "[xwm] Denying transient because it would create a loop"); }; auto handleSizeHints = [&]() { @@ -330,24 +335,27 @@ void CXWM::readProp(SP XSURF, uint32_t atom, xcb_get_property_ else if (atom == HYPRATOMS["WM_PROTOCOLS"]) handleWMProtocols(); else { - Debug::log(TRACE, "[xwm] Unhandled prop {} -> {}", atom, propName); + Log::logger->log(Log::TRACE, "[xwm] Unhandled prop {} -> {}", atom, propName); return; } - Debug::log(TRACE, "[xwm] Handled prop {} -> {}", atom, propName); + Log::logger->log(Log::TRACE, "[xwm] Handled prop {} -> {}", atom, propName); } void CXWM::handlePropertyNotify(xcb_property_notify_event_t* e) { const auto XSURF = windowForXID(e->window); - if (!XSURF) + if (!XSURF) { + removeTransfersForWindow(e->window); return; + } xcb_get_property_cookie_t cookie = xcb_get_property(getConnection(), 0, XSURF->m_xID, e->atom, XCB_ATOM_ANY, 0, 2048); XCBReplyPtr reply(xcb_get_property_reply(getConnection(), cookie, nullptr)); if (!reply) { - Debug::log(ERR, "[xwm] Failed to read property notify cookie"); + Log::logger->log(Log::ERR, "[xwm] Failed to read property notify cookie for window {}", e->window); + removeTransfersForWindow(e->window); return; } @@ -369,7 +377,7 @@ void CXWM::handleClientMessage(xcb_client_message_event_t* e) { } } else if (e->type == HYPRATOMS["WL_SURFACE_ID"]) { if (XSURF->m_surface) { - Debug::log(WARN, "[xwm] Re-assignment of WL_SURFACE_ID"); + Log::logger->log(Log::WARN, "[xwm] Re-assignment of WL_SURFACE_ID"); dissociate(XSURF); } @@ -381,7 +389,7 @@ void CXWM::handleClientMessage(xcb_client_message_event_t* e) { } } else if (e->type == HYPRATOMS["WL_SURFACE_SERIAL"]) { if (XSURF->m_wlSerial) { - Debug::log(WARN, "[xwm] Re-assignment of WL_SURFACE_SERIAL"); + Log::logger->log(Log::WARN, "[xwm] Re-assignment of WL_SURFACE_SERIAL"); dissociate(XSURF); } @@ -389,7 +397,7 @@ void CXWM::handleClientMessage(xcb_client_message_event_t* e) { uint32_t serialHigh = e->data.data32[1]; XSURF->m_wlSerial = (sc(serialHigh) << 32) | serialLow; - Debug::log(LOG, "[xwm] surface {:x} requests serial {:x}", rc(XSURF.get()), XSURF->m_wlSerial); + Log::logger->log(Log::DEBUG, "[xwm] surface {:x} requests serial {:x}", rc(XSURF.get()), XSURF->m_wlSerial); for (auto const& res : m_shellResources) { if (!res) @@ -445,7 +453,7 @@ void CXWM::handleClientMessage(xcb_client_message_event_t* e) { XSURF->m_events.activate.emit(); } else if (e->type == HYPRATOMS["XdndStatus"]) { if (m_dndDataOffers.empty() || !m_dndDataOffers.at(0)->getSource()) { - Debug::log(TRACE, "[xwm] Rejecting XdndStatus message: nothing to get"); + Log::logger->log(Log::TRACE, "[xwm] Rejecting XdndStatus message: nothing to get"); return; } @@ -455,22 +463,22 @@ void CXWM::handleClientMessage(xcb_client_message_event_t* e) { if (ACCEPTED) m_dndDataOffers.at(0)->getSource()->accepted(""); - Debug::log(LOG, "[xwm] XdndStatus: accepted: {}"); + Log::logger->log(Log::DEBUG, "[xwm] XdndStatus: accepted: {}"); } else if (e->type == HYPRATOMS["XdndFinished"]) { if (m_dndDataOffers.empty() || !m_dndDataOffers.at(0)->getSource()) { - Debug::log(TRACE, "[xwm] Rejecting XdndFinished message: nothing to get"); + Log::logger->log(Log::TRACE, "[xwm] Rejecting XdndFinished message: nothing to get"); return; } m_dndDataOffers.at(0)->getSource()->sendDndFinished(); - Debug::log(LOG, "[xwm] XdndFinished"); + Log::logger->log(Log::DEBUG, "[xwm] XdndFinished"); } else { - Debug::log(TRACE, "[xwm] Unhandled message prop {} -> {}", e->type, propName); + Log::logger->log(Log::TRACE, "[xwm] Unhandled message prop {} -> {}", e->type, propName); return; } - Debug::log(TRACE, "[xwm] Handled message prop {} -> {}", e->type, propName); + Log::logger->log(Log::TRACE, "[xwm] Handled message prop {} -> {}", e->type, propName); } void CXWM::handleFocusIn(xcb_focus_in_event_t* e) { @@ -489,15 +497,15 @@ void CXWM::handleFocusIn(xcb_focus_in_event_t* e) { } void CXWM::handleFocusOut(xcb_focus_out_event_t* e) { - Debug::log(TRACE, "[xwm] focusOut mode={}, detail={}, event={}", e->mode, e->detail, e->event); + Log::logger->log(Log::TRACE, "[xwm] focusOut mode={}, detail={}, event={}", e->mode, e->detail, e->event); const auto XSURF = windowForXID(e->event); if (!XSURF) return; - Debug::log(TRACE, "[xwm] focusOut for {} {} {} surface {}", XSURF->m_mapped ? "mapped" : "unmapped", XSURF->m_fullscreen ? "fullscreen" : "windowed", - XSURF == m_focusedSurface ? "focused" : "unfocused", XSURF->m_state.title); + Log::logger->log(Log::TRACE, "[xwm] focusOut for {} {} {} surface {}", XSURF->m_mapped ? "mapped" : "unmapped", XSURF->m_fullscreen ? "fullscreen" : "windowed", + XSURF == m_focusedSurface ? "focused" : "unfocused", XSURF->m_state.title); // do something? } @@ -556,7 +564,7 @@ void CXWM::focusWindow(SP surf) { void CXWM::handleError(xcb_value_error_t* e) { const char* major_name = xcb_errors_get_name_for_major_code(m_errors, e->major_opcode); if (!major_name) { - Debug::log(ERR, "xcb error happened, but could not get major name"); + Log::logger->log(Log::ERR, "xcb error happened, but could not get major name"); return; } @@ -565,12 +573,12 @@ void CXWM::handleError(xcb_value_error_t* e) { const char* extension; const char* error_name = xcb_errors_get_name_for_error(m_errors, e->error_code, &extension); if (!error_name) { - Debug::log(ERR, "xcb error happened, but could not get error name"); + Log::logger->log(Log::ERR, "xcb error happened, but could not get error name"); return; } - Debug::log(ERR, "[xwm] xcb error: {} ({}), code {} ({}), seq {}, val {}", major_name, minor_name ? minor_name : "no minor", error_name, extension ? extension : "no extension", - e->sequence, e->bad_value); + Log::logger->log(Log::ERR, "[xwm] xcb error: {} ({}), code {} ({}), seq {}, val {}", major_name, minor_name ? minor_name : "no minor", error_name, + extension ? extension : "no extension", e->sequence, e->bad_value); } void CXWM::selectionSendNotify(xcb_selection_request_event_t* e, bool success) { @@ -620,19 +628,19 @@ std::string CXWM::mimeFromAtom(xcb_atom_t atom) { } void CXWM::handleSelectionNotify(xcb_selection_notify_event_t* e) { - Debug::log(TRACE, "[xwm] Selection notify for {} prop {} target {}", e->selection, e->property, e->target); + Log::logger->log(Log::TRACE, "[xwm] Selection notify for {} prop {} target {}", e->selection, e->property, e->target); SXSelection* sel = getSelection(e->selection); if (e->property == XCB_ATOM_NONE) { auto it = std::ranges::find_if(sel->transfers, [](const auto& t) { return !t->propertyReply; }); if (it != sel->transfers.end()) { - Debug::log(TRACE, "[xwm] converting selection failed"); + Log::logger->log(Log::TRACE, "[xwm] converting selection failed"); sel->transfers.erase(it); } } else if (e->target == HYPRATOMS["TARGETS"]) { if (!m_focusedSurface) { - Debug::log(TRACE, "[xwm] denying access to write to clipboard because no X client is in focus"); + Log::logger->log(Log::TRACE, "[xwm] denying access to write to clipboard because no X client is in focus"); return; } @@ -642,19 +650,33 @@ void CXWM::handleSelectionNotify(xcb_selection_notify_event_t* e) { } bool CXWM::handleSelectionPropertyNotify(xcb_property_notify_event_t* e) { - if (e->state != XCB_PROPERTY_DELETE) - return false; - - for (auto* sel : {&m_clipboard, &m_primarySelection}) { + for (auto* sel : {&m_clipboard, &m_primarySelection, &m_dndSelection}) { auto it = std::ranges::find_if(sel->transfers, [e](const auto& t) { return t->incomingWindow == e->window; }); - if (it != sel->transfers.end()) { - if (!(*it)->getIncomingSelectionProp(true)) { - sel->transfers.erase(it); - return false; + if (it == sel->transfers.end()) + continue; + + auto& transfer = *it; + + if (e->state == XCB_PROPERTY_NEW_VALUE) { + if (!transfer->incremental) { + getTransferData(*sel); + return true; } + + if (!transfer->getIncomingSelectionProp(true) || xcb_get_property_value_length(transfer->propertyReply) == 0) { + sel->transfers.erase(it); + return true; + } + + int result = sel->onWrite(); + + if (result == 1 && !transfer->eventSource) { + transfer->eventSource = wl_event_loop_add_fd(g_pCompositor->m_wlEventLoop, transfer->wlFD.get(), WL_EVENT_WRITABLE, ::writeDataSource, sel); + } + } else if (e->state == XCB_PROPERTY_DELETE) { getTransferData(*sel); - return true; } + return true; } return false; @@ -672,13 +694,13 @@ SXSelection* CXWM::getSelection(xcb_atom_t atom) { } void CXWM::handleSelectionRequest(xcb_selection_request_event_t* e) { - Debug::log(TRACE, "[xwm] Selection request for {} prop {} target {} time {} requestor {} selection {}", e->selection, e->property, e->target, e->time, e->requestor, - e->selection); + Log::logger->log(Log::TRACE, "[xwm] Selection request for {} prop {} target {} time {} requestor {} selection {}", e->selection, e->property, e->target, e->time, e->requestor, + e->selection); SXSelection* sel = getSelection(e->selection); if (!sel) { - Debug::log(ERR, "[xwm] No selection"); + Log::logger->log(Log::ERR, "[xwm] No selection"); selectionSendNotify(e, false); return; } @@ -689,13 +711,13 @@ void CXWM::handleSelectionRequest(xcb_selection_request_event_t* e) { } if (sel->window != e->owner && e->time != XCB_CURRENT_TIME && e->time < sel->timestamp) { - Debug::log(ERR, "[xwm] outdated selection request. Time {} < {}", e->time, sel->timestamp); + Log::logger->log(Log::ERR, "[xwm] outdated selection request. Time {} < {}", e->time, sel->timestamp); selectionSendNotify(e, false); return; } if (!g_pSeatManager->m_state.keyboardFocusResource || g_pSeatManager->m_state.keyboardFocusResource->client() != g_pXWayland->m_server->m_xwaylandClient) { - Debug::log(TRACE, "[xwm] Ignoring clipboard access: xwayland not in focus"); + Log::logger->log(Log::TRACE, "[xwm] Ignoring clipboard access: xwayland not in focus"); selectionSendNotify(e, false); return; } @@ -709,7 +731,7 @@ void CXWM::handleSelectionRequest(xcb_selection_request_event_t* e) { mimes = m_dndDataOffers.at(0)->m_source->mimes(); if (mimes.empty()) - Debug::log(WARN, "[xwm] WARNING: No mimes in TARGETS?"); + Log::logger->log(Log::WARN, "[xwm] WARNING: No mimes in TARGETS?"); std::vector atoms; // reserve to avoid reallocations @@ -732,13 +754,13 @@ void CXWM::handleSelectionRequest(xcb_selection_request_event_t* e) { std::string mime = mimeFromAtom(e->target); if (mime == "INVALID") { - Debug::log(LOG, "[xwm] Ignoring clipboard access: invalid mime atom {}", e->target); + Log::logger->log(Log::DEBUG, "[xwm] Ignoring clipboard access: invalid mime atom {}", e->target); selectionSendNotify(e, false); return; } if (!sel->sendData(e, mime)) { - Debug::log(LOG, "[xwm] Failed to send selection :("); + Log::logger->log(Log::DEBUG, "[xwm] Failed to send selection :("); selectionSendNotify(e, false); return; } @@ -746,7 +768,7 @@ void CXWM::handleSelectionRequest(xcb_selection_request_event_t* e) { } bool CXWM::handleSelectionXFixesNotify(xcb_xfixes_selection_notify_event_t* e) { - Debug::log(TRACE, "[xwm] Selection xfixes notify for {}", e->selection); + Log::logger->log(Log::TRACE, "[xwm] Selection xfixes notify for {}", e->selection); // IMPORTANT: mind the g_pSeatManager below SXSelection* sel = getSelection(e->selection); @@ -806,8 +828,8 @@ bool CXWM::handleSelectionEvent(xcb_generic_event_t* e) { int CXWM::onEvent(int fd, uint32_t mask) { if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { - Debug::log(ERR, "XWayland has yeeten the xwm off?!"); - Debug::log(CRIT, "XWayland has yeeten the xwm off?!"); + Log::logger->log(Log::ERR, "XWayland has yeeten the xwm off?!"); + Log::logger->log(Log::CRIT, "XWayland has yeeten the xwm off?!"); // Attempt to create fresh instance g_pEventLoopManager->doLater([]() { g_pXWayland->m_wm.reset(); @@ -843,7 +865,7 @@ int CXWM::onEvent(int fd, uint32_t mask) { case XCB_FOCUS_OUT: handleFocusOut(rc(event.get())); break; case 0: handleError(rc(event.get())); break; default: { - Debug::log(TRACE, "[xwm] unhandled event {}", event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK); + Log::logger->log(Log::TRACE, "[xwm] unhandled event {}", event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK); } } } @@ -864,7 +886,7 @@ void CXWM::gatherResources() { XCBReplyPtr reply(xcb_intern_atom_reply(getConnection(), cookie, nullptr)); if (!reply) { - Debug::log(ERR, "[xwm] Atom failed: {}", ATOM.first); + Log::logger->log(Log::ERR, "[xwm] Atom failed: {}", ATOM.first); continue; } @@ -874,13 +896,13 @@ void CXWM::gatherResources() { m_xfixes = xcb_get_extension_data(getConnection(), &xcb_xfixes_id); if (!m_xfixes || !m_xfixes->present) - Debug::log(WARN, "XFixes not available"); + Log::logger->log(Log::WARN, "XFixes not available"); auto xfixes_cookie = xcb_xfixes_query_version(getConnection(), XCB_XFIXES_MAJOR_VERSION, XCB_XFIXES_MINOR_VERSION); XCBReplyPtr xfixes_reply(xcb_xfixes_query_version_reply(getConnection(), xfixes_cookie, nullptr)); if (xfixes_reply) { - Debug::log(LOG, "xfixes version: {}.{}", xfixes_reply->major_version, xfixes_reply->minor_version); + Log::logger->log(Log::DEBUG, "xfixes version: {}.{}", xfixes_reply->major_version, xfixes_reply->minor_version); m_xfixesMajor = xfixes_reply->major_version; } @@ -893,7 +915,7 @@ void CXWM::gatherResources() { if (!xres_reply) return; - Debug::log(LOG, "xres version: {}.{}", xres_reply->server_major, xres_reply->server_minor); + Log::logger->log(Log::DEBUG, "xres version: {}.{}", xres_reply->server_major, xres_reply->server_minor); if (xres_reply->server_major > 1 || (xres_reply->server_major == 1 && xres_reply->server_minor >= 2)) { m_xres = xresReply1; } @@ -917,7 +939,7 @@ void CXWM::getVisual() { } if (visualtype == nullptr) { - Debug::log(LOG, "xwm: No 32-bit visualtype"); + Log::logger->log(Log::DEBUG, "xwm: No 32-bit visualtype"); return; } @@ -931,7 +953,7 @@ void CXWM::getRenderFormat() { XCBReplyPtr reply(xcb_render_query_pict_formats_reply(getConnection(), cookie, nullptr)); if (!reply) { - Debug::log(LOG, "xwm: No xcb_render_query_pict_formats_reply_t reply"); + Log::logger->log(Log::DEBUG, "xwm: No xcb_render_query_pict_formats_reply_t reply"); return; } @@ -947,7 +969,7 @@ void CXWM::getRenderFormat() { } if (format == nullptr) { - Debug::log(LOG, "xwm: No 32-bit render format"); + Log::logger->log(Log::DEBUG, "xwm: No 32-bit render format"); return; } @@ -957,13 +979,13 @@ void CXWM::getRenderFormat() { CXWM::CXWM() : m_connection(makeUnique(g_pXWayland->m_server->m_xwmFDs[0].get())) { if (m_connection->hasError()) { - Debug::log(ERR, "[xwm] Couldn't start, error {}", m_connection->hasError()); + Log::logger->log(Log::ERR, "[xwm] Couldn't start, error {}", m_connection->hasError()); return; } CXCBErrorContext xcbErrCtx(getConnection()); if (!xcbErrCtx.isValid()) { - Debug::log(ERR, "[xwm] Couldn't allocate errors context"); + Log::logger->log(Log::ERR, "[xwm] Couldn't allocate errors context"); return; } @@ -1050,8 +1072,8 @@ void CXWM::activateSurface(SP surf, bool activate) { } void CXWM::sendState(SP surf) { - Debug::log(TRACE, "[xwm] sendState for {} {} {} surface {}", surf->m_mapped ? "mapped" : "unmapped", surf->m_fullscreen ? "fullscreen" : "windowed", - surf == m_focusedSurface ? "focused" : "unfocused", surf->m_state.title); + Log::logger->log(Log::TRACE, "[xwm] sendState for {} {} {} surface {}", surf->m_mapped ? "mapped" : "unmapped", surf->m_fullscreen ? "fullscreen" : "windowed", + surf == m_focusedSurface ? "focused" : "unfocused", surf->m_state.title); if (surf->m_fullscreen && surf->m_mapped && surf == m_focusedSurface) surf->setWithdrawn(false); // resend normal state @@ -1068,8 +1090,8 @@ void CXWM::sendState(SP surf) { if (surf->m_fullscreen) props.push_back(HYPRATOMS["_NET_WM_STATE_FULLSCREEN"]); if (surf->m_maximized) { - props.push_back(HYPRATOMS["NET_WM_STATE_MAXIMIZED_VERT"]); - props.push_back(HYPRATOMS["NET_WM_STATE_MAXIMIZED_HORZ"]); + props.push_back(HYPRATOMS["_NET_WM_STATE_MAXIMIZED_VERT"]); + props.push_back(HYPRATOMS["_NET_WM_STATE_MAXIMIZED_HORZ"]); } if (surf->m_minimized) props.push_back(HYPRATOMS["_NET_WM_STATE_HIDDEN"]); @@ -1083,7 +1105,7 @@ void CXWM::onNewSurface(SP surf) { if (surf->client() != g_pXWayland->m_server->m_xwaylandClient) return; - Debug::log(LOG, "[xwm] New XWayland surface at {:x}", rc(surf.get())); + Log::logger->log(Log::DEBUG, "[xwm] New XWayland surface at {:x}", rc(surf.get())); const auto WLID = surf->id(); @@ -1095,11 +1117,11 @@ void CXWM::onNewSurface(SP surf) { return; } - Debug::log(WARN, "[xwm] CXWM::onNewSurface: no matching xwaylandSurface"); + Log::logger->log(Log::WARN, "[xwm] CXWM::onNewSurface: no matching xwaylandSurface"); } void CXWM::onNewResource(SP resource) { - Debug::log(LOG, "[xwm] New XWayland resource at {:x}", rc(resource.get())); + Log::logger->log(Log::DEBUG, "[xwm] New XWayland resource at {:x}", rc(resource.get())); std::erase_if(m_shellResources, [](const auto& e) { return e.expired(); }); m_shellResources.emplace_back(resource); @@ -1124,7 +1146,7 @@ void CXWM::readWindowData(SP surf) { xcb_get_property_cookie_t cookie = xcb_get_property(getConnection(), 0, surf->m_xID, interestingProps[i], XCB_ATOM_ANY, 0, 2048); XCBReplyPtr reply(xcb_get_property_reply(getConnection(), cookie, nullptr)); if (!reply) { - Debug::log(ERR, "[xwm] Failed to get window property"); + Log::logger->log(Log::ERR, "[xwm] Failed to get window property"); continue; } readProp(surf, interestingProps[i], reply.get()); @@ -1147,7 +1169,7 @@ void CXWM::associate(SP surf, SP wlSurf) { auto existing = std::ranges::find_if(m_surfaces, [wlSurf](const auto& e) { return e->m_surface == wlSurf; }); if (existing != m_surfaces.end()) { - Debug::log(WARN, "[xwm] associate() called but surface is already associated to {:x}, ignoring...", rc(surf.get())); + Log::logger->log(Log::WARN, "[xwm] associate() called but surface is already associated to {:x}, ignoring...", rc(surf.get())); return; } @@ -1169,7 +1191,7 @@ void CXWM::dissociate(SP surf) { surf->m_surface.reset(); surf->m_events.resourceChange.emit(); - Debug::log(LOG, "Dissociate for {:x}", rc(surf.get())); + Log::logger->log(Log::DEBUG, "Dissociate for {:x}", rc(surf.get())); } void CXWM::updateClientList() { @@ -1259,13 +1281,13 @@ void CXWM::initSelection() { void CXWM::setClipboardToWayland(SXSelection& sel) { auto source = makeShared(sel); if (source->mimes().empty()) { - Debug::log(ERR, "[xwm] can't set selection: no MIMEs"); + Log::logger->log(Log::ERR, "[xwm] can't set selection: no MIMEs"); return; } sel.dataSource = source; - Debug::log(LOG, "[xwm] X selection at {:x} takes {}", rc(sel.dataSource.get()), (&sel == &m_clipboard) ? "clipboard" : "primary selection"); + Log::logger->log(Log::DEBUG, "[xwm] X selection at {:x} takes {}", rc(sel.dataSource.get()), (&sel == &m_clipboard) ? "clipboard" : "primary selection"); if (&sel == &m_clipboard) g_pSeatManager->setCurrentSelection(sel.dataSource); @@ -1279,29 +1301,29 @@ static int writeDataSource(int fd, uint32_t mask, void* data) { } void CXWM::getTransferData(SXSelection& sel) { - Debug::log(LOG, "[xwm] getTransferData"); + Log::logger->log(Log::DEBUG, "[xwm] getTransferData"); auto it = std::ranges::find_if(sel.transfers, [](const auto& t) { return !t->propertyReply; }); if (it == sel.transfers.end()) { - Debug::log(ERR, "[xwm] No pending transfer found"); + Log::logger->log(Log::ERR, "[xwm] No pending transfer found"); return; } auto& transfer = *it; if (!transfer || !transfer->incomingWindow) { - Debug::log(ERR, "[xwm] Invalid transfer state"); + Log::logger->log(Log::ERR, "[xwm] Invalid transfer state"); sel.transfers.erase(it); return; } if (!transfer->getIncomingSelectionProp(true)) { - Debug::log(ERR, "[xwm] Failed to get property data"); + Log::logger->log(Log::ERR, "[xwm] Failed to get property data"); sel.transfers.erase(it); return; } if (!transfer->propertyReply) { - Debug::log(ERR, "[xwm] No property reply"); + Log::logger->log(Log::ERR, "[xwm] No property reply"); sel.transfers.erase(it); return; } @@ -1314,20 +1336,23 @@ void CXWM::getTransferData(SXSelection& sel) { return; } - const size_t transferIndex = std::distance(sel.transfers.begin(), it); - int writeResult = sel.onWrite(); + // Store window ID before onWrite() - transfer may be erased during the call + const xcb_window_t targetWindow = transfer->incomingWindow; + int writeResult = sel.onWrite(); if (writeResult != 1) return; - if (transferIndex >= sel.transfers.size()) + // Re-find the transfer by window ID (safe after potential vector modification) + auto updatedIt = std::ranges::find_if(sel.transfers, [targetWindow](const auto& t) { return t->incomingWindow == targetWindow; }); + if (updatedIt == sel.transfers.end()) return; - Hyprutils::Memory::CUniquePointer& updatedTransfer = sel.transfers[transferIndex]; + auto& updatedTransfer = *updatedIt; if (!updatedTransfer) return; - if (updatedTransfer->eventSource && updatedTransfer->wlFD.get() == -1) + if (updatedTransfer->eventSource || updatedTransfer->wlFD.get() == -1) return; updatedTransfer->eventSource = wl_event_loop_add_fd(g_pCompositor->m_wlEventLoop, updatedTransfer->wlFD.get(), WL_EVENT_WRITABLE, ::writeDataSource, &sel); @@ -1335,7 +1360,7 @@ void CXWM::getTransferData(SXSelection& sel) { void CXWM::setCursor(unsigned char* pixData, uint32_t stride, const Vector2D& size, const Vector2D& hotspot) { if (!m_renderFormatID) { - Debug::log(ERR, "[xwm] can't set cursor: no render format"); + Log::logger->log(Log::ERR, "[xwm] can't set cursor: no render format"); return; } @@ -1374,7 +1399,7 @@ SP CXWM::createX11DataOffer(SP surf, SPlog(Log::ERR, "[xwm] No xwayland surface for destination in createX11DataOffer"); return nullptr; } @@ -1432,7 +1457,7 @@ int SXSelection::onRead(int fd, uint32_t mask) { auto it = std::ranges::find_if(transfers, [fd](const auto& t) { return t->wlFD.get() == fd; }); if (it == transfers.end()) { - Debug::log(ERR, "[xwm] No transfer found for fd {}", fd); + Log::logger->log(Log::ERR, "[xwm] No transfer found for fd {}", fd); return 0; } @@ -1443,7 +1468,12 @@ int SXSelection::onRead(int fd, uint32_t mask) { ssize_t bytesRead = read(fd, transfer->data.data() + oldSize, INCR_CHUNK_SIZE - 1); if (bytesRead < 0) { - Debug::log(ERR, "[xwm] readDataSource died"); + if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) { + transfer->data.resize(oldSize); + return 1; + } + + Log::logger->log(Log::ERR, "[xwm] readDataSource died"); g_pXWayland->m_wm->selectionSendNotify(&transfer->request, false); transfers.erase(it); return 0; @@ -1453,13 +1483,13 @@ int SXSelection::onRead(int fd, uint32_t mask) { if (bytesRead == 0) { if (transfer->data.empty()) { - Debug::log(WARN, "[xwm] Transfer ended with zero bytes — rejecting"); + Log::logger->log(Log::WARN, "[xwm] Transfer ended with zero bytes — rejecting"); g_pXWayland->m_wm->selectionSendNotify(&transfer->request, false); transfers.erase(it); return 0; } - Debug::log(LOG, "[xwm] Transfer complete, total size: {}", transfer->data.size()); + Log::logger->log(Log::DEBUG, "[xwm] Transfer complete, total size: {}", transfer->data.size()); auto conn = g_pXWayland->m_wm->getConnection(); xcb_change_property(conn, XCB_PROP_MODE_REPLACE, transfer->request.requestor, transfer->request.property, transfer->request.target, 8, transfer->data.size(), transfer->data.data()); @@ -1468,13 +1498,13 @@ int SXSelection::onRead(int fd, uint32_t mask) { g_pXWayland->m_wm->selectionSendNotify(&transfer->request, true); transfers.erase(it); } else - Debug::log(LOG, "[xwm] Received {} bytes, awaiting more...", bytesRead); + Log::logger->log(Log::DEBUG, "[xwm] Received {} bytes, awaiting more...", bytesRead); return 1; } static int readDataSource(int fd, uint32_t mask, void* data) { - Debug::log(LOG, "[xwm] readDataSource on fd {}", fd); + Log::logger->log(Log::DEBUG, "[xwm] readDataSource on fd {}", fd); auto selection = sc(data); @@ -1491,30 +1521,30 @@ bool SXSelection::sendData(xcb_selection_request_event_t* e, std::string mime) { selection = g_pXWayland->m_wm->m_dndDataOffers.at(0)->getSource(); if (!selection) { - Debug::log(ERR, "[xwm] sendData: no selection source available"); + Log::logger->log(Log::ERR, "[xwm] sendData: no selection source available"); return false; } const auto MIMES = selection->mimes(); if (MIMES.empty()) { - Debug::log(ERR, "[xwm] sendData: selection source has no mimes"); + Log::logger->log(Log::ERR, "[xwm] sendData: selection source has no mimes"); return false; } if (std::ranges::find(MIMES, mime) == MIMES.end()) { // try to guess mime, don't just blindly send random-ass shit that the app will have no fucking // clue what to do with - Debug::log(ERR, "[xwm] X client asked for MIME '{}' that this selection doesn't support, guessing.", mime); + Log::logger->log(Log::ERR, "[xwm] X client asked for MIME '{}' that this selection doesn't support, guessing.", mime); auto needle = mime; auto selectedMime = *MIMES.begin(); if (mime.contains('/')) needle = mime.substr(0, mime.find('/')); - Debug::log(TRACE, "[xwm] X MIME needle '{}'", needle); + Log::logger->log(Log::TRACE, "[xwm] X MIME needle '{}'", needle); - if (Debug::m_trace) { + if (Env::isTrace()) { std::string mimeList = ""; for (const auto& m : MIMES) { mimeList += "'" + m + "', "; @@ -1523,7 +1553,7 @@ bool SXSelection::sendData(xcb_selection_request_event_t* e, std::string mime) { if (!MIMES.empty()) mimeList = mimeList.substr(0, mimeList.size() - 2); - Debug::log(TRACE, "[xwm] X MIME supported: {}", mimeList); + Log::logger->log(Log::TRACE, "[xwm] X MIME supported: {}", mimeList); } bool found = false; @@ -1531,7 +1561,7 @@ bool SXSelection::sendData(xcb_selection_request_event_t* e, std::string mime) { for (const auto& m : MIMES) { if (m.starts_with(needle)) { selectedMime = m; - Debug::log(TRACE, "[xwm] X MIME needle found type '{}'", m); + Log::logger->log(Log::TRACE, "[xwm] X MIME needle found type '{}'", m); found = true; break; } @@ -1541,14 +1571,14 @@ bool SXSelection::sendData(xcb_selection_request_event_t* e, std::string mime) { for (const auto& m : MIMES) { if (m.contains(needle)) { selectedMime = m; - Debug::log(TRACE, "[xwm] X MIME needle found type '{}'", m); + Log::logger->log(Log::TRACE, "[xwm] X MIME needle found type '{}'", m); found = true; break; } } } - Debug::log(ERR, "[xwm] Guessed mime: '{}'. Hopefully we're right enough.", selectedMime); + Log::logger->log(Log::ERR, "[xwm] Guessed mime: '{}'. Hopefully we're right enough.", selectedMime); mime = selectedMime; } @@ -1558,7 +1588,7 @@ bool SXSelection::sendData(xcb_selection_request_event_t* e, std::string mime) { int p[2]; if (pipe(p) == -1) { - Debug::log(ERR, "[xwm] sendData: pipe() failed"); + Log::logger->log(Log::ERR, "[xwm] sendData: pipe() failed"); return false; } @@ -1569,7 +1599,7 @@ bool SXSelection::sendData(xcb_selection_request_event_t* e, std::string mime) { transfer->wlFD = CFileDescriptor{p[0]}; - Debug::log(LOG, "[xwm] sending wayland selection to xwayland with mime {}, target {}, fds {} {}", mime, e->target, p[0], p[1]); + Log::logger->log(Log::DEBUG, "[xwm] sending wayland selection to xwayland with mime {}, target {}, fds {} {}", mime, e->target, p[0], p[1]); selection->send(mime, CFileDescriptor{p[1]}); @@ -1582,7 +1612,7 @@ bool SXSelection::sendData(xcb_selection_request_event_t* e, std::string mime) { int SXSelection::onWrite() { auto it = std::ranges::find_if(transfers, [](const auto& t) { return t->propertyReply; }); if (it == transfers.end()) { - Debug::log(ERR, "[xwm] No transfer with property data found"); + Log::logger->log(Log::ERR, "[xwm] No transfer with property data found"); return 0; } @@ -1594,28 +1624,44 @@ int SXSelection::onWrite() { if (len == -1) { if (errno == EAGAIN) return 1; - Debug::log(ERR, "[xwm] write died in transfer get"); + Log::logger->log(Log::ERR, "[xwm] write died in transfer get"); transfers.erase(it); return 0; } if (len < remainder) { transfer->propertyStart += len; - Debug::log(LOG, "[xwm] wl client read partially: len {}", len); + Log::logger->log(Log::DEBUG, "[xwm] wl client read partially: len {}", len); } else { - Debug::log(LOG, "[xwm] cb transfer to wl client complete, read {} bytes", len); + 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); @@ -1633,7 +1679,7 @@ bool SXTransfer::getIncomingSelectionProp(bool erase) { propertyReply = xcb_get_property_reply(*g_pXWayland->m_wm->m_connection, cookie, nullptr); if (!propertyReply) { - Debug::log(ERR, "[SXTransfer] couldn't get a prop reply"); + Log::logger->log(Log::ERR, "[SXTransfer] couldn't get a prop reply"); return false; } diff --git a/src/xwayland/XWM.hpp b/src/xwayland/XWM.hpp index b328a2c98..af1fa06af 100644 --- a/src/xwayland/XWM.hpp +++ b/src/xwayland/XWM.hpp @@ -35,9 +35,9 @@ struct SXTransfer { xcb_selection_request_event_t request; - int propertyStart; - xcb_get_property_reply_t* propertyReply; - xcb_window_t incomingWindow; + int propertyStart = 0; + xcb_get_property_reply_t* propertyReply = nullptr; + xcb_window_t incomingWindow = 0; bool getIncomingSelectionProp(bool erase); }; @@ -54,6 +54,7 @@ struct SXSelection { bool sendData(xcb_selection_request_event_t* e, std::string mime); int onRead(int fd, uint32_t mask); int onWrite(); + void removeTransfer(xcb_window_t window); struct { CHyprSignalListener setSelection; @@ -71,11 +72,11 @@ class CXCBConnection { ~CXCBConnection() { if (m_connection) { - Debug::log(LOG, "Disconnecting XCB connection {:x}", rc(m_connection)); + Log::logger->log(Log::DEBUG, "Disconnecting XCB connection {:x}", rc(m_connection)); xcb_disconnect(m_connection); m_connection = nullptr; } else - Debug::log(ERR, "Double xcb_disconnect attempt"); + Log::logger->log(Log::ERR, "Double xcb_disconnect attempt"); } bool hasError() const { @@ -164,6 +165,8 @@ class CXWM { void handleFocusOut(xcb_focus_out_event_t* e); void handleError(xcb_value_error_t* e); + void removeTransfersForWindow(xcb_window_t window); + bool handleSelectionEvent(xcb_generic_event_t* e); void handleSelectionNotify(xcb_selection_notify_event_t* e); bool handleSelectionPropertyNotify(xcb_property_notify_event_t* e); diff --git a/src/xwayland/XWayland.cpp b/src/xwayland/XWayland.cpp index f7bdf1e6f..a022217ab 100644 --- a/src/xwayland/XWayland.cpp +++ b/src/xwayland/XWayland.cpp @@ -1,13 +1,13 @@ #include "XWayland.hpp" #include "../Compositor.hpp" -#include "../debug/Log.hpp" +#include "../debug/log/Logger.hpp" #include "../helpers/fs/FsUtils.hpp" CXWayland::CXWayland(const bool wantsEnabled) { #ifndef NO_XWAYLAND // Disable Xwayland and clean up if the user disabled it. if (!wantsEnabled) { - Debug::log(LOG, "XWayland has been disabled, cleaning up..."); + Log::logger->log(Log::DEBUG, "XWayland has been disabled, cleaning up..."); for (auto& w : g_pCompositor->m_windows) { if (!w->m_isX11) continue; @@ -20,29 +20,29 @@ CXWayland::CXWayland(const bool wantsEnabled) { if (!NFsUtils::executableExistsInPath("Xwayland")) { // If Xwayland doesn't exist, don't try to start it. - Debug::log(LOG, "Unable to find XWayland; not starting it."); + Log::logger->log(Log::DEBUG, "Unable to find XWayland; not starting it."); return; } - Debug::log(LOG, "Starting up the XWayland server"); + Log::logger->log(Log::DEBUG, "Starting up the XWayland server"); m_server = makeUnique(); if (!m_server->create()) { - Debug::log(ERR, "XWayland failed to start: it will not work."); + Log::logger->log(Log::ERR, "XWayland failed to start: it will not work."); return; } m_enabled = true; #else - Debug::log(LOG, "Not starting XWayland: disabled at compile time"); + Log::logger->log(Log::DEBUG, "Not starting XWayland: disabled at compile time"); #endif } void CXWayland::setCursor(unsigned char* pixData, uint32_t stride, const Vector2D& size, const Vector2D& hotspot) { #ifndef NO_XWAYLAND if (!m_wm) { - Debug::log(ERR, "Couldn't set XCursor: no XWM yet"); + Log::logger->log(Log::ERR, "Couldn't set XCursor: no XWM yet"); return; } diff --git a/start/CMakeLists.txt b/start/CMakeLists.txt new file mode 100644 index 000000000..d0c40ceb7 --- /dev/null +++ b/start/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.19) + +project(start-hyprland DESCRIPTION "Hyprland watchdog binary") + +include(GNUInstallDirs) + +set(CMAKE_CXX_STANDARD 26) +set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) + +find_package(PkgConfig REQUIRED) + +pkg_check_modules(starthyprland_deps REQUIRED IMPORTED_TARGET hyprutils>=0.10.3) + +find_package(glaze 7...<8 QUIET) +if (NOT glaze_FOUND) + set(GLAZE_VERSION v7.2.0) + message(STATUS "start: glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent") + include(FetchContent) + FetchContent_Declare( + glaze + GIT_REPOSITORY https://github.com/stephenberry/glaze.git + GIT_TAG ${GLAZE_VERSION} + GIT_SHALLOW TRUE + EXCLUDE_FROM_ALL + ) + FetchContent_MakeAvailable(glaze) +endif() + +file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp") + +add_executable(start-hyprland ${SRCFILES}) + +target_link_libraries(start-hyprland PUBLIC PkgConfig::starthyprland_deps) +target_link_libraries(start-hyprland PUBLIC glaze::glaze) + +install(TARGETS start-hyprland) + +if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) + set(CMAKE_EXECUTABLE_ENABLE_EXPORTS TRUE) +endif() diff --git a/start/src/core/Instance.cpp b/start/src/core/Instance.cpp new file mode 100644 index 000000000..ef758422a --- /dev/null +++ b/start/src/core/Instance.cpp @@ -0,0 +1,197 @@ +#include "Instance.hpp" +#include "State.hpp" +#include "../helpers/Logger.hpp" +#include "../helpers/Nix.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) +#include +#elif defined(__FreeBSD__) +#include +#include +#endif + +#include + +using namespace Hyprutils::OS; +using namespace std::string_literals; + +// +void CHyprlandInstance::runHyprlandThread(bool safeMode) { + std::vector argsStd; + argsStd.emplace_back("--watchdog-fd"); + argsStd.emplace_back(std::format("{}", m_toHlPid.get())); + if (safeMode) + argsStd.emplace_back("--safe-mode"); + + for (const auto& a : g_state->rawArgvNoBinPath) { + argsStd.emplace_back(a); + } + + // spawn a process manually. Hyprutils' Async is detached, while Sync redirects stdout + // TODO: make Sync respect fds? + + std::vector args = {strdup(g_state->customPath.value_or("Hyprland").c_str())}; + for (const auto& a : argsStd) { + args.emplace_back(strdup(a.c_str())); + } + args.emplace_back(nullptr); + + 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()); + + g_logger->log(Hyprutils::CLI::LOG_ERR, "fork(): execvp failed: {}", strerror(errno)); + std::fflush(stdout); + exit(1); + } else + m_hlPid = forkRet; + + m_hlThread = std::thread([this] { + while (true) { + int status = 0; + int ret = waitpid(m_hlPid, &status, 0); + if (ret == -1) { + g_logger->log(Hyprutils::CLI::LOG_ERR, "Couldn't waitpid for hyprland: {}", strerror(errno)); + break; + } + + if (WIFEXITED(status)) + break; + } + + if (write(m_wakeupWrite.get(), "vax", 3) < 0) + g_logger->log(Hyprutils::CLI::LOG_ERR, "Failed to write to wakeup fd {}: {}", m_wakeupWrite.get(), strerror(errno)); + + std::fflush(stdout); + std::fflush(stderr); + }); +} + +void CHyprlandInstance::forceQuit() { + m_hyprlandExiting = true; + kill(m_hlPid, SIGTERM); // gracefully, can get stuck but it's unlikely + + m_hlThread.join(); // needs this otherwise can crash +} + +void CHyprlandInstance::clearFd(const Hyprutils::OS::CFileDescriptor& fd) { + if (!fd.isReadable()) { + g_logger->log(Hyprutils::CLI::LOG_ERR, "Can't clear a unreadable fd"); + return; + } + + static std::array buf; + if (read(fd.get(), buf.data(), 1023) < 0) + g_logger->log(Hyprutils::CLI::LOG_ERR, "Failed clearing fd {}: {}", fd.get(), strerror(errno)); +} + +void CHyprlandInstance::dispatchHyprlandEvent() { + std::string recvd = ""; + static std::array buf; + ssize_t n = read(m_fromHlPid.get(), buf.data(), 4096); + if (n < 0) { + g_logger->log(Hyprutils::CLI::LOG_ERR, "Failed dispatching hl events"); + return; + } + + recvd.append(buf.data(), n); + + if (recvd.empty()) + return; + + for (const auto& s : std::views::split(recvd, '\n')) { + const std::string_view sv = std::string_view{s}; + if (sv == "vax") { + // init passed + m_hyprlandInitialized = true; + continue; + } + + if (sv == "end") { + // exiting + m_hyprlandExiting = true; + continue; + } + } +} + +bool CHyprlandInstance::run(bool safeMode) { + int pipefds[2]; + if (pipe(pipefds) != 0) { + g_logger->log(Hyprutils::CLI::LOG_ERR, "pipe() failed, exiting"); + exit(1); + } + + m_fromHlPid = CFileDescriptor{pipefds[0]}; + m_toHlPid = CFileDescriptor{pipefds[1]}; + + if (pipe(pipefds) != 0) { + g_logger->log(Hyprutils::CLI::LOG_ERR, "pipe() failed, exiting"); + exit(1); + } + + m_wakeupRead = CFileDescriptor{pipefds[0]}; + m_wakeupWrite = CFileDescriptor{pipefds[1]}; + + runHyprlandThread(safeMode); + + pollfd pollfds[2] = { + { + .fd = m_wakeupRead.get(), + .events = POLLIN, + .revents = 0, + }, + { + .fd = m_fromHlPid.get(), + .events = POLLIN, + .revents = 0, + }, + }; + + while (true) { + int ret = poll(pollfds, 2, -1); + + if (ret < 0) { + g_logger->log(Hyprutils::CLI::LOG_ERR, "poll() failed, exiting"); + exit(1); + } + + if (pollfds[1].revents & POLLIN) { + g_logger->log(Hyprutils::CLI::LOG_DEBUG, "got an event from hyprland"); + dispatchHyprlandEvent(); + continue; + } + + if (pollfds[0].revents & POLLIN) { + g_logger->log(Hyprutils::CLI::LOG_DEBUG, "hyprland exit, breaking poll, checking state"); + clearFd(m_wakeupRead); + break; + } + } + + m_hlThread.join(); + + return !m_hyprlandInitialized || m_hyprlandExiting; +} diff --git a/start/src/core/Instance.hpp b/start/src/core/Instance.hpp new file mode 100644 index 000000000..2c72dc12d --- /dev/null +++ b/start/src/core/Instance.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +#include "../helpers/Memory.hpp" + +class CHyprlandInstance { + public: + CHyprlandInstance() = default; + ~CHyprlandInstance() = default; + + CHyprlandInstance(const CHyprlandInstance&) = delete; + CHyprlandInstance(CHyprlandInstance&) = delete; + CHyprlandInstance(CHyprlandInstance&&) = delete; + + bool run(bool safeMode = false); // if returns false, restart. + void forceQuit(); + + private: + void runHyprlandThread(bool safeMode); + void clearFd(const Hyprutils::OS::CFileDescriptor& fd); + void dispatchHyprlandEvent(); + + int m_hlPid = -1; + + Hyprutils::OS::CFileDescriptor m_fromHlPid, m_toHlPid; + Hyprutils::OS::CFileDescriptor m_wakeupRead, m_wakeupWrite; + + bool m_hyprlandInitialized = false; + bool m_hyprlandExiting = false; + + std::thread m_hlThread; +}; + +inline UP g_instance; diff --git a/start/src/core/State.hpp b/start/src/core/State.hpp new file mode 100644 index 000000000..6a44c8d03 --- /dev/null +++ b/start/src/core/State.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "../helpers/Memory.hpp" + +#include +#include + +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/Logger.hpp b/start/src/helpers/Logger.hpp new file mode 100644 index 000000000..ae7712032 --- /dev/null +++ b/start/src/helpers/Logger.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +#include "Memory.hpp" + +// we do this to add a from start-hyprland to the logs +inline UP g_loggerMain = makeUnique(); +inline UP g_logger; diff --git a/start/src/helpers/Memory.hpp b/start/src/helpers/Memory.hpp new file mode 100644 index 000000000..66ba2c1fd --- /dev/null +++ b/start/src/helpers/Memory.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +using namespace Hyprutils::Memory; + +template +using SP = Hyprutils::Memory::CSharedPointer; +template +using WP = Hyprutils::Memory::CWeakPointer; +template +using UP = Hyprutils::Memory::CUniquePointer; +template +using ASP = Hyprutils::Memory::CAtomicSharedPointer; diff --git a/start/src/helpers/Nix.cpp b/start/src/helpers/Nix.cpp new file mode 100644 index 000000000..da66183e5 --- /dev/null +++ b/start/src/helpers/Nix.cpp @@ -0,0 +1,125 @@ +#include "Nix.hpp" + +#include "Logger.hpp" +#include "../core/State.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +using namespace Hyprutils::String; +using namespace Hyprutils::OS; + +using namespace Hyprutils::File; + +static std::optional getFromEtcOsRelease(const std::string_view& sv) { + static std::string content = ""; + static bool once = true; + + if (once) { + once = false; + + auto read = readFileAsString("/etc/os-release"); + content = read.value_or(""); + } + + static CVarList2 vars(std::move(content), 0, '\n', true); + + for (const auto& v : vars) { + if (v.starts_with(sv) && v.contains('=')) { + // found + auto value = trim(v.substr(v.find('=') + 1)); + + if (value.back() == value.front() && value.back() == '"') + value = value.substr(1, value.size() - 2); + + return std::string{value}; + } + } + + return std::nullopt; +} + +static bool executableExistsInPath(const std::string& exe) { + const char* PATHENV = std::getenv("PATH"); + if (!PATHENV) + return false; + + CVarList2 paths(PATHENV, 0, ':', true); + std::error_code ec; + + for (const auto& PATH : paths) { + std::filesystem::path candidate = std::filesystem::path(PATH) / exe; + if (!std::filesystem::exists(candidate, ec) || ec) + continue; + if (!std::filesystem::is_regular_file(candidate, ec) || ec) + continue; + auto perms = std::filesystem::status(candidate, ec).permissions(); + if (ec) + continue; + if ((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none) + return true; + } + + return false; +} + +std::expected Nix::nixEnvironmentOk() { + if (!shouldUseNixGL()) + return {}; + + if (!executableExistsInPath("nixGL")) + return std::unexpected( + "Hyprland was installed using Nix, but you're not on NixOS. This requires nixGL to be installed as well.\nYou can install nixGL by running \"nix profile install " + "github:guibou/nixGL --impure\" in your terminal."); + + return {}; +} + +bool Nix::shouldUseNixGL() { + if (g_state->forceNixGl) + return true; + + if (g_state->noNixGl) + return false; + + // check if installed hyprland is nix'd + CProcess proc("Hyprland", {"--version-json"}); + if (!proc.runSync()) { + g_logger->log(Hyprutils::CLI::LOG_ERR, "failed to obtain hyprland version string"); + return false; + } + + auto json = glz::read_json(proc.stdOut()); + if (!json) { + g_logger->log(Hyprutils::CLI::LOG_ERR, "failed to obtain hyprland version string (bad json)"); + return false; + } + + const auto FLAGS = (*json)["flags"].get_array(); + const bool IS_NIX = std::ranges::any_of(FLAGS, [](const auto& e) { return e.get_string() == std::string_view{"nix"}; }); + + if (IS_NIX) { + const auto NAME = getFromEtcOsRelease("NAME"); + + // Hyprland is nix: recommend nixGL iff !NIX && !NIX-OPENGL + + if (*NAME == "NixOS") + return false; + + std::error_code ec; + + if (std::filesystem::exists("/run/opengl-driver", ec) && !ec) // NOLINTNEXTLINE + return false; + + // we are not on nix / no nix opengl driver + return true; + } + + return false; +} diff --git a/start/src/helpers/Nix.hpp b/start/src/helpers/Nix.hpp new file mode 100644 index 000000000..edc01b199 --- /dev/null +++ b/start/src/helpers/Nix.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + +namespace Nix { + std::expected nixEnvironmentOk(); + bool shouldUseNixGL(); +}; \ No newline at end of file diff --git a/start/src/main.cpp b/start/src/main.cpp new file mode 100644 index 000000000..45a783579 --- /dev/null +++ b/start/src/main.cpp @@ -0,0 +1,121 @@ +#include +#include +#include + +#include "helpers/Logger.hpp" +#include "helpers/Nix.hpp" +#include "core/State.hpp" +#include "core/Instance.hpp" + +using namespace Hyprutils::CLI; + +#define ASSERT(expr) \ + if (!(expr)) { \ + g_logger->log(LOG_CRIT, "Failed assertion at line {} in {}: {} was false", __LINE__, \ + ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find("/src/") + 1); })(), #expr); \ + std::abort(); \ + } + +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 +)#"; + +// +static void onSignal(int sig) { + if (!g_instance) + return; + + g_instance->forceQuit(); + g_instance.reset(); + + exit(0); +} + +static void terminateChildOnSignal(int signal) { + struct sigaction sa; + sa.sa_handler = onSignal; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + int ret = sigaction(signal, &sa, nullptr); + if (ret != 0) + g_logger->log(Hyprutils::CLI::LOG_WARN, "Failed to set up handler for signal {}: {}", signal, strerror(errno)); +} + +int main(int argc, const char** argv, const char** envp) { + g_logger = makeUnique(*g_loggerMain); + g_logger->setName("start-hyprland"); + g_logger->setLogLevel(Hyprutils::CLI::LOG_DEBUG); + + terminateChildOnSignal(SIGTERM); + terminateChildOnSignal(SIGINT); + + int startArgv = -1; + + for (int i = 1; i < argc; ++i) { + std::string_view arg = argv[i]; + + if (arg == "--") { + startArgv = i + 1; + break; + } + if (arg == "-h" || arg == "--help") { + std::println("{}", HELP_INFO); + return 0; + } + if (arg == "--path" || arg == "-p") { + if (i + 1 >= argc) { + std::println("{} requires a path", arg); + return 1; + } + + 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) + g_state->rawArgvNoBinPath = std::span{argv + startArgv, argc - startArgv}; + + 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(); + const bool RET = g_instance->run(safeMode); + g_instance.reset(); + + if (!RET) { + g_logger->log(Hyprutils::CLI::LOG_ERR, "Hyprland exit not-cleanly, restarting"); + safeMode = true; + continue; + } + + g_logger->log(Hyprutils::CLI::LOG_DEBUG, "Hyprland exit cleanly."); + break; + } + + return 0; +} diff --git a/subprojects/tracy b/subprojects/tracy index 37aff70df..05cceee0d 160000 --- a/subprojects/tracy +++ b/subprojects/tracy @@ -1 +1 @@ -Subproject commit 37aff70dfa50cf6307b3fee6074d627dc2929143 +Subproject commit 05cceee0df3b8d7c6fa87e9638af311dbabc63cb diff --git a/systemd/hyprland-uwsm.desktop b/systemd/hyprland-uwsm.desktop index 3f37532d0..2ea70cb6e 100644 --- a/systemd/hyprland-uwsm.desktop +++ b/systemd/hyprland-uwsm.desktop @@ -1,7 +1,7 @@ [Desktop Entry] Name=Hyprland (uwsm-managed) Comment=An intelligent dynamic tiling Wayland compositor -Exec=uwsm start -- hyprland.desktop +Exec=uwsm start -e -D Hyprland hyprland.desktop TryExec=uwsm DesktopNames=Hyprland Type=Application diff --git a/tests/config/MonitorParser.cpp b/tests/config/MonitorParser.cpp new file mode 100644 index 000000000..29bd8db10 --- /dev/null +++ b/tests/config/MonitorParser.cpp @@ -0,0 +1,214 @@ +#include + +#include + +using namespace Config; + +TEST(Config, monitorParserName) { + CMonitorRuleParser parser("DP-1"); + EXPECT_EQ(parser.name(), "DP-1"); + EXPECT_FALSE(parser.getError().has_value()); +} + +TEST(Config, monitorParserModeStandard) { + CMonitorRuleParser parser("DP-1"); + EXPECT_TRUE(parser.parseMode("1920x1080")); + EXPECT_EQ(parser.rule().m_resolution, Vector2D(1920, 1080)); + + CMonitorRuleParser parser2("DP-2"); + EXPECT_TRUE(parser2.parseMode("2560x1440@144")); + EXPECT_EQ(parser2.rule().m_resolution, Vector2D(2560, 1440)); + EXPECT_FLOAT_EQ(parser2.rule().m_refreshRate, 144.0f); + + CMonitorRuleParser parser3("DP-3"); + EXPECT_TRUE(parser3.parseMode("3840x2160@60.0")); + EXPECT_EQ(parser3.rule().m_resolution, Vector2D(3840, 2160)); + EXPECT_FLOAT_EQ(parser3.rule().m_refreshRate, 60.0f); +} + +TEST(Config, monitorParserModeKeywords) { + CMonitorRuleParser p1("DP-1"); + EXPECT_TRUE(p1.parseMode("preferred")); + EXPECT_EQ(p1.rule().m_resolution, Vector2D()); + + CMonitorRuleParser p2("DP-1"); + EXPECT_TRUE(p2.parseMode("highrr")); + EXPECT_EQ(p2.rule().m_resolution, Vector2D(-1, -1)); + + CMonitorRuleParser p3("DP-1"); + EXPECT_TRUE(p3.parseMode("highres")); + EXPECT_EQ(p3.rule().m_resolution, Vector2D(-1, -2)); + + CMonitorRuleParser p4("DP-1"); + EXPECT_TRUE(p4.parseMode("maxwidth")); + EXPECT_EQ(p4.rule().m_resolution, Vector2D(-1, -3)); +} + +TEST(Config, monitorParserModeInvalid) { + CMonitorRuleParser parser("DP-1"); + EXPECT_FALSE(parser.parseMode("notaresolution")); + EXPECT_TRUE(parser.getError().has_value()); +} + +TEST(Config, monitorParserPositionExplicit) { + CMonitorRuleParser parser("DP-1"); + EXPECT_TRUE(parser.parsePosition("1920x0")); + EXPECT_EQ(parser.rule().m_offset, Vector2D(1920, 0)); + + CMonitorRuleParser parser2("DP-2"); + EXPECT_TRUE(parser2.parsePosition("0x1080")); + EXPECT_EQ(parser2.rule().m_offset, Vector2D(0, 1080)); +} + +TEST(Config, monitorParserPositionAuto) { + CMonitorRuleParser p1("DP-1"); + EXPECT_TRUE(p1.parsePosition("auto")); + EXPECT_EQ(p1.rule().m_autoDir, DIR_AUTO_RIGHT); + + CMonitorRuleParser p2("DP-1"); + EXPECT_TRUE(p2.parsePosition("auto-left")); + EXPECT_EQ(p2.rule().m_autoDir, DIR_AUTO_LEFT); + + CMonitorRuleParser p3("DP-1"); + EXPECT_TRUE(p3.parsePosition("auto-up")); + EXPECT_EQ(p3.rule().m_autoDir, DIR_AUTO_UP); + + CMonitorRuleParser p4("DP-1"); + EXPECT_TRUE(p4.parsePosition("auto-down")); + EXPECT_EQ(p4.rule().m_autoDir, DIR_AUTO_DOWN); + + CMonitorRuleParser p5("DP-1"); + EXPECT_TRUE(p5.parsePosition("auto-right")); + EXPECT_EQ(p5.rule().m_autoDir, DIR_AUTO_RIGHT); +} + +TEST(Config, monitorParserPositionAutoCenter) { + CMonitorRuleParser p1("DP-1"); + EXPECT_TRUE(p1.parsePosition("auto-center-right")); + EXPECT_EQ(p1.rule().m_autoDir, DIR_AUTO_CENTER_RIGHT); + + CMonitorRuleParser p2("DP-1"); + EXPECT_TRUE(p2.parsePosition("auto-center-left")); + EXPECT_EQ(p2.rule().m_autoDir, DIR_AUTO_CENTER_LEFT); + + CMonitorRuleParser p3("DP-1"); + EXPECT_TRUE(p3.parsePosition("auto-center-up")); + EXPECT_EQ(p3.rule().m_autoDir, DIR_AUTO_CENTER_UP); + + CMonitorRuleParser p4("DP-1"); + EXPECT_TRUE(p4.parsePosition("auto-center-down")); + EXPECT_EQ(p4.rule().m_autoDir, DIR_AUTO_CENTER_DOWN); +} + +TEST(Config, monitorParserScaleValid) { + CMonitorRuleParser p1("DP-1"); + EXPECT_TRUE(p1.parseScale("1.5")); + EXPECT_FLOAT_EQ(p1.rule().m_scale, 1.5f); + + CMonitorRuleParser p2("DP-1"); + EXPECT_TRUE(p2.parseScale("2")); + EXPECT_FLOAT_EQ(p2.rule().m_scale, 2.0f); + + CMonitorRuleParser p3("DP-1"); + EXPECT_TRUE(p3.parseScale("auto")); + EXPECT_FLOAT_EQ(p3.rule().m_scale, -1.0f); + + CMonitorRuleParser p4("DP-1"); + EXPECT_TRUE(p4.parseScale("0.25")); + EXPECT_FLOAT_EQ(p4.rule().m_scale, 0.25f); +} + +TEST(Config, monitorParserScaleInvalid) { + CMonitorRuleParser parser("DP-1"); + EXPECT_FALSE(parser.parseScale("notanumber")); + EXPECT_TRUE(parser.getError().has_value()); +} + +TEST(Config, monitorParserTransformValid) { + for (int i = 0; i <= 7; i++) { + CMonitorRuleParser parser("DP-1"); + EXPECT_TRUE(parser.parseTransform(std::to_string(i))); + EXPECT_EQ(parser.rule().m_transform, static_cast(i)); + } +} + +TEST(Config, monitorParserTransformInvalid) { + CMonitorRuleParser p1("DP-1"); + EXPECT_FALSE(p1.parseTransform("8")); + + CMonitorRuleParser p2("DP-1"); + EXPECT_FALSE(p2.parseTransform("-1")); + + CMonitorRuleParser p3("DP-1"); + EXPECT_FALSE(p3.parseTransform("abc")); +} + +TEST(Config, monitorParserBitdepth) { + CMonitorRuleParser p1("DP-1"); + EXPECT_TRUE(p1.parseBitdepth("10")); + EXPECT_TRUE(p1.rule().m_enable10bit); + + CMonitorRuleParser p2("DP-1"); + EXPECT_TRUE(p2.parseBitdepth("8")); + EXPECT_FALSE(p2.rule().m_enable10bit); +} + +TEST(Config, monitorParserCM) { + CMonitorRuleParser p1("DP-1"); + EXPECT_TRUE(p1.parseCM("srgb")); + EXPECT_EQ(p1.rule().m_cmType, NCMType::CM_SRGB); + + CMonitorRuleParser p2("DP-1"); + EXPECT_TRUE(p2.parseCM("wide")); + EXPECT_EQ(p2.rule().m_cmType, NCMType::CM_WIDE); + + CMonitorRuleParser p3("DP-1"); + EXPECT_FALSE(p3.parseCM("invalid")); + EXPECT_TRUE(p3.getError().has_value()); +} + +TEST(Config, monitorParserVRR) { + CMonitorRuleParser p1("DP-1"); + EXPECT_TRUE(p1.parseVRR("1")); + EXPECT_EQ(p1.rule().m_vrr, 1); + + CMonitorRuleParser p2("DP-1"); + EXPECT_TRUE(p2.parseVRR("0")); + EXPECT_EQ(p2.rule().m_vrr, 0); + + CMonitorRuleParser p3("DP-1"); + EXPECT_FALSE(p3.parseVRR("abc")); + EXPECT_TRUE(p3.getError().has_value()); +} + +TEST(Config, monitorParserSDRBrightness) { + CMonitorRuleParser parser("DP-1"); + EXPECT_TRUE(parser.parseSDRBrightness("1.5")); + EXPECT_FLOAT_EQ(parser.rule().m_sdrBrightness, 1.5f); + + CMonitorRuleParser p2("DP-1"); + EXPECT_FALSE(p2.parseSDRBrightness("notanumber")); + EXPECT_TRUE(p2.getError().has_value()); +} + +TEST(Config, monitorParserICC) { + CMonitorRuleParser p1("DP-1"); + EXPECT_TRUE(p1.parseICC("/path/to/profile.icc")); + EXPECT_EQ(p1.rule().m_iccFile, "/path/to/profile.icc"); + + CMonitorRuleParser p2("DP-1"); + EXPECT_FALSE(p2.parseICC("")); + EXPECT_TRUE(p2.getError().has_value()); +} + +TEST(Config, monitorParserSetDisabled) { + CMonitorRuleParser parser("DP-1"); + parser.setDisabled(); + EXPECT_TRUE(parser.rule().m_disabled); +} + +TEST(Config, monitorParserSetMirror) { + CMonitorRuleParser parser("DP-1"); + parser.setMirror("HDMI-A-1"); + EXPECT_EQ(parser.rule().m_mirrorOf, "HDMI-A-1"); +} diff --git a/tests/desktop/Reserved.cpp b/tests/desktop/Reserved.cpp new file mode 100644 index 000000000..8e8977274 --- /dev/null +++ b/tests/desktop/Reserved.cpp @@ -0,0 +1,55 @@ + +#include + +#include + +TEST(Desktop, reservedArea) { + Desktop::CReservedArea a{{20, 30}, {40, 50}}; + CBox box = {1000, 1000, 1000, 1000}; + a.applyip(box); + + EXPECT_EQ(box.x, 1020); + EXPECT_EQ(box.y, 1030); + EXPECT_EQ(box.w, 1000 - 20 - 40); + EXPECT_EQ(box.h, 1000 - 30 - 50); + + box = a.apply(CBox{1000, 1000, 1000, 1000}); + + EXPECT_EQ(box.x, 1020); + EXPECT_EQ(box.y, 1030); + EXPECT_EQ(box.w, 1000 - 20 - 40); + EXPECT_EQ(box.h, 1000 - 30 - 50); + + a.addType(Desktop::RESERVED_DYNAMIC_TYPE_LS, {10, 20}, {30, 40}); + + box = a.apply(CBox{1000, 1000, 1000, 1000}); + + EXPECT_EQ(box.x, 1000 + 20 + 10); + EXPECT_EQ(box.y, 1000 + 30 + 20); + EXPECT_EQ(box.w, 1000 - 20 - 40 - 10 - 30); + EXPECT_EQ(box.h, 1000 - 30 - 50 - 20 - 40); + + Desktop::CReservedArea b{CBox{10, 10, 1000, 1000}, CBox{20, 30, 900, 900}}; + + EXPECT_EQ(b.left(), 20 - 10); + 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); + + Desktop::CReservedArea e{CBox{20, 30, 900, 900}, CBox{0, 0, 100, 100}}; + EXPECT_EQ(e.ok(), false); +} \ No newline at end of file diff --git a/tests/desktop/rule/matchEngine/BoolMatchEngine.cpp b/tests/desktop/rule/matchEngine/BoolMatchEngine.cpp new file mode 100644 index 000000000..99f6d99c4 --- /dev/null +++ b/tests/desktop/rule/matchEngine/BoolMatchEngine.cpp @@ -0,0 +1,70 @@ +#include + +#include + +using namespace Desktop::Rule; + +TEST(BoolMatchEngine, truthyStringOne) { + CBoolMatchEngine engine("1"); + EXPECT_TRUE(engine.match(true)); + EXPECT_FALSE(engine.match(false)); +} + +TEST(BoolMatchEngine, truthyStringTrue) { + CBoolMatchEngine engine("true"); + EXPECT_TRUE(engine.match(true)); + EXPECT_FALSE(engine.match(false)); +} + +TEST(BoolMatchEngine, truthyStringYes) { + CBoolMatchEngine engine("yes"); + EXPECT_TRUE(engine.match(true)); + EXPECT_FALSE(engine.match(false)); +} + +TEST(BoolMatchEngine, truthyStringOn) { + CBoolMatchEngine engine("on"); + EXPECT_TRUE(engine.match(true)); + EXPECT_FALSE(engine.match(false)); +} + +TEST(BoolMatchEngine, truthyCaseInsensitive) { + CBoolMatchEngine upper("TRUE"); + EXPECT_TRUE(upper.match(true)); + + CBoolMatchEngine mixed("Yes"); + EXPECT_TRUE(mixed.match(true)); + + CBoolMatchEngine onUpper("ON"); + EXPECT_TRUE(onUpper.match(true)); +} + +TEST(BoolMatchEngine, falsyStringZero) { + CBoolMatchEngine engine("0"); + EXPECT_TRUE(engine.match(false)); + EXPECT_FALSE(engine.match(true)); +} + +TEST(BoolMatchEngine, falsyStringFalse) { + CBoolMatchEngine engine("false"); + EXPECT_TRUE(engine.match(false)); + EXPECT_FALSE(engine.match(true)); +} + +TEST(BoolMatchEngine, falsyStringNo) { + CBoolMatchEngine engine("no"); + EXPECT_TRUE(engine.match(false)); + EXPECT_FALSE(engine.match(true)); +} + +TEST(BoolMatchEngine, falsyStringEmpty) { + CBoolMatchEngine engine(""); + EXPECT_TRUE(engine.match(false)); + EXPECT_FALSE(engine.match(true)); +} + +TEST(BoolMatchEngine, falsyStringArbitrary) { + CBoolMatchEngine engine("banana"); + EXPECT_TRUE(engine.match(false)); + EXPECT_FALSE(engine.match(true)); +} diff --git a/tests/desktop/rule/matchEngine/IntMatchEngine.cpp b/tests/desktop/rule/matchEngine/IntMatchEngine.cpp new file mode 100644 index 000000000..a5fb13ea5 --- /dev/null +++ b/tests/desktop/rule/matchEngine/IntMatchEngine.cpp @@ -0,0 +1,52 @@ +#include + +#include + +using namespace Desktop::Rule; + +TEST(IntMatchEngine, positiveInteger) { + CIntMatchEngine engine("42"); + EXPECT_TRUE(engine.match(42)); + EXPECT_FALSE(engine.match(41)); + EXPECT_FALSE(engine.match(0)); +} + +TEST(IntMatchEngine, zero) { + CIntMatchEngine engine("0"); + EXPECT_TRUE(engine.match(0)); + EXPECT_FALSE(engine.match(1)); +} + +TEST(IntMatchEngine, negativeInteger) { + CIntMatchEngine engine("-5"); + EXPECT_TRUE(engine.match(-5)); + EXPECT_FALSE(engine.match(5)); +} + +TEST(IntMatchEngine, invalidStringDefaultsToZero) { + CIntMatchEngine engine("abc"); + EXPECT_TRUE(engine.match(0)); + EXPECT_FALSE(engine.match(1)); +} + +TEST(IntMatchEngine, emptyStringDefaultsToZero) { + CIntMatchEngine engine(""); + EXPECT_TRUE(engine.match(0)); + EXPECT_FALSE(engine.match(1)); +} + +TEST(IntMatchEngine, leadingWhitespace) { + CIntMatchEngine engine(" 123"); + EXPECT_TRUE(engine.match(123)); +} + +TEST(IntMatchEngine, trailingNonDigits) { + CIntMatchEngine engine("123abc"); + EXPECT_TRUE(engine.match(123)); +} + +TEST(IntMatchEngine, largeValue) { + CIntMatchEngine engine("999999"); + EXPECT_TRUE(engine.match(999999)); + EXPECT_FALSE(engine.match(0)); +} diff --git a/tests/desktop/rule/matchEngine/RegexMatchEngine.cpp b/tests/desktop/rule/matchEngine/RegexMatchEngine.cpp new file mode 100644 index 000000000..daaed29bf --- /dev/null +++ b/tests/desktop/rule/matchEngine/RegexMatchEngine.cpp @@ -0,0 +1,69 @@ +#include + +#include + +using namespace Desktop::Rule; + +TEST(RegexMatchEngine, exactMatch) { + CRegexMatchEngine engine("firefox"); + EXPECT_TRUE(engine.match("firefox")); + EXPECT_FALSE(engine.match("Firefox")); + EXPECT_FALSE(engine.match("firefox2")); +} + +TEST(RegexMatchEngine, wildcardPattern) { + CRegexMatchEngine engine("fire.*"); + EXPECT_TRUE(engine.match("firefox")); + EXPECT_TRUE(engine.match("firewall")); + EXPECT_FALSE(engine.match("ice")); +} + +TEST(RegexMatchEngine, fullMatchRequired) { + CRegexMatchEngine engine("fire"); + EXPECT_TRUE(engine.match("fire")); + EXPECT_FALSE(engine.match("firefox")); + EXPECT_FALSE(engine.match("campfire")); +} + +TEST(RegexMatchEngine, characterClass) { + CRegexMatchEngine engine("kitty_[ABC]"); + EXPECT_TRUE(engine.match("kitty_A")); + EXPECT_TRUE(engine.match("kitty_B")); + EXPECT_TRUE(engine.match("kitty_C")); + EXPECT_FALSE(engine.match("kitty_D")); +} + +TEST(RegexMatchEngine, negativePrefix) { + CRegexMatchEngine engine("negative:firefox"); + EXPECT_FALSE(engine.match("firefox")); + EXPECT_TRUE(engine.match("chromium")); + EXPECT_TRUE(engine.match("anything")); +} + +TEST(RegexMatchEngine, negativeWithWildcard) { + CRegexMatchEngine engine("negative:.*\\.tmp"); + EXPECT_FALSE(engine.match("file.tmp")); + EXPECT_TRUE(engine.match("file.txt")); + EXPECT_TRUE(engine.match("file.cpp")); +} + +TEST(RegexMatchEngine, emptyPattern) { + CRegexMatchEngine engine(""); + EXPECT_TRUE(engine.match("")); + EXPECT_FALSE(engine.match("anything")); +} + +TEST(RegexMatchEngine, dotMatchesSingleChar) { + CRegexMatchEngine engine("a.c"); + EXPECT_TRUE(engine.match("abc")); + EXPECT_TRUE(engine.match("axc")); + EXPECT_FALSE(engine.match("ac")); + EXPECT_FALSE(engine.match("abbc")); +} + +TEST(RegexMatchEngine, alternation) { + CRegexMatchEngine engine("cat|dog"); + EXPECT_TRUE(engine.match("cat")); + EXPECT_TRUE(engine.match("dog")); + EXPECT_FALSE(engine.match("bird")); +} diff --git a/tests/desktop/rule/matchEngine/TagMatchEngine.cpp b/tests/desktop/rule/matchEngine/TagMatchEngine.cpp new file mode 100644 index 000000000..c6fe90ef1 --- /dev/null +++ b/tests/desktop/rule/matchEngine/TagMatchEngine.cpp @@ -0,0 +1,68 @@ +#include +#include + +#include + +using namespace Desktop::Rule; + +TEST(TagMatchEngine, matchesExistingTag) { + CTagKeeper keeper; + keeper.applyTag("myTag"); + + CTagMatchEngine engine("myTag"); + EXPECT_TRUE(engine.match(keeper)); +} + +TEST(TagMatchEngine, doesNotMatchMissingTag) { + CTagKeeper keeper; + keeper.applyTag("otherTag"); + + CTagMatchEngine engine("myTag"); + EXPECT_FALSE(engine.match(keeper)); +} + +TEST(TagMatchEngine, emptyKeeper) { + CTagKeeper keeper; + + CTagMatchEngine engine("myTag"); + EXPECT_FALSE(engine.match(keeper)); +} + +TEST(TagMatchEngine, negativeTagMatching) { + CTagKeeper keeper; + keeper.applyTag("myTag"); + + CTagMatchEngine negative("negative:myTag"); + EXPECT_FALSE(negative.match(keeper)); + + CTagMatchEngine negativeOther("negative:otherTag"); + EXPECT_TRUE(negativeOther.match(keeper)); +} + +TEST(TagMatchEngine, dynamicTagMatchesWithoutStrict) { + CTagKeeper keeper; + keeper.applyTag("myTag", true); // dynamic adds "*" suffix + + CTagMatchEngine engine("myTag"); + EXPECT_TRUE(engine.match(keeper)); +} + +TEST(TagMatchEngine, caseSensitive) { + CTagKeeper keeper; + keeper.applyTag("MyTag"); + + CTagMatchEngine lower("mytag"); + EXPECT_FALSE(lower.match(keeper)); + + CTagMatchEngine exact("MyTag"); + EXPECT_TRUE(exact.match(keeper)); +} + +TEST(TagMatchEngine, tagRemoval) { + CTagKeeper keeper; + keeper.applyTag("myTag"); + keeper.applyTag("-myTag"); + + CTagMatchEngine engine("myTag"); + EXPECT_FALSE(engine.match(keeper)); +} diff --git a/tests/helpers/ByteOperations.cpp b/tests/helpers/ByteOperations.cpp new file mode 100644 index 000000000..3ac5639ed --- /dev/null +++ b/tests/helpers/ByteOperations.cpp @@ -0,0 +1,39 @@ +#include + +#include + +TEST(Helpers, byteOperatorsIntegral) { + EXPECT_EQ(1_kB, 1024ULL); + EXPECT_EQ(1_MB, 1024ULL * 1024); + EXPECT_EQ(1_GB, 1024ULL * 1024 * 1024); + EXPECT_EQ(1_TB, 1024ULL * 1024 * 1024 * 1024); + EXPECT_EQ(5_MB, 5ULL * 1024 * 1024); +} + +TEST(Helpers, byteOperatorsFloating) { + EXPECT_DOUBLE_EQ(1.5_kB, 1.5L * 1024); + EXPECT_DOUBLE_EQ(0.5_MB, 0.5L * 1024 * 1024); + EXPECT_DOUBLE_EQ(2.5_GB, 2.5L * 1024 * 1024 * 1024); + EXPECT_DOUBLE_EQ(0.25_TB, 0.25L * 1024 * 1024 * 1024 * 1024); +} + +TEST(Helpers, byteOperatorsZero) { + EXPECT_EQ(0_kB, 0ULL); + EXPECT_EQ(0_MB, 0ULL); + EXPECT_EQ(0_GB, 0ULL); + EXPECT_EQ(0_TB, 0ULL); +} + +TEST(Helpers, byteConversionFunctions) { + EXPECT_EQ(kBtoBytes(1ULL), 1024ULL); + EXPECT_EQ(MBtoBytes(1ULL), 1024ULL * 1024); + EXPECT_EQ(GBtoBytes(1ULL), 1024ULL * 1024 * 1024); + EXPECT_EQ(TBtoBytes(1ULL), 1024ULL * 1024 * 1024 * 1024); + EXPECT_EQ(kBtoBytes(0ULL), 0ULL); +} + +TEST(Helpers, byteOperatorsChain) { + EXPECT_EQ(1_MB, 1024_kB); + EXPECT_EQ(1_GB, 1024_MB); + EXPECT_EQ(1_TB, 1024_GB); +} diff --git a/tests/helpers/CMType.cpp b/tests/helpers/CMType.cpp new file mode 100644 index 000000000..0d2981cf5 --- /dev/null +++ b/tests/helpers/CMType.cpp @@ -0,0 +1,49 @@ +#include + +#include + +using namespace NCMType; + +TEST(Helpers, cmTypeFromStringValid) { + EXPECT_EQ(fromString("auto"), CM_AUTO); + EXPECT_EQ(fromString("srgb"), CM_SRGB); + EXPECT_EQ(fromString("wide"), CM_WIDE); + EXPECT_EQ(fromString("edid"), CM_EDID); + EXPECT_EQ(fromString("hdr"), CM_HDR); + EXPECT_EQ(fromString("hdredid"), CM_HDR_EDID); + EXPECT_EQ(fromString("dcip3"), CM_DCIP3); + EXPECT_EQ(fromString("dp3"), CM_DP3); + EXPECT_EQ(fromString("adobe"), CM_ADOBE); +} + +TEST(Helpers, cmTypeFromStringInvalid) { + EXPECT_EQ(fromString(""), std::nullopt); + EXPECT_EQ(fromString("invalid"), std::nullopt); + EXPECT_EQ(fromString("SRGB"), std::nullopt); + EXPECT_EQ(fromString("HDR"), std::nullopt); + EXPECT_EQ(fromString("Auto"), std::nullopt); +} + +TEST(Helpers, cmTypeToString) { + EXPECT_EQ(toString(CM_AUTO), "auto"); + EXPECT_EQ(toString(CM_SRGB), "srgb"); + EXPECT_EQ(toString(CM_WIDE), "wide"); + EXPECT_EQ(toString(CM_EDID), "edid"); + EXPECT_EQ(toString(CM_HDR), "hdr"); + EXPECT_EQ(toString(CM_HDR_EDID), "hdredid"); + EXPECT_EQ(toString(CM_DCIP3), "dcip3"); + EXPECT_EQ(toString(CM_DP3), "dp3"); + EXPECT_EQ(toString(CM_ADOBE), "adobe"); +} + +TEST(Helpers, cmTypeRoundTrip) { + EXPECT_EQ(fromString(toString(CM_AUTO)), CM_AUTO); + EXPECT_EQ(fromString(toString(CM_SRGB)), CM_SRGB); + EXPECT_EQ(fromString(toString(CM_WIDE)), CM_WIDE); + EXPECT_EQ(fromString(toString(CM_EDID)), CM_EDID); + EXPECT_EQ(fromString(toString(CM_HDR)), CM_HDR); + EXPECT_EQ(fromString(toString(CM_HDR_EDID)), CM_HDR_EDID); + EXPECT_EQ(fromString(toString(CM_DCIP3)), CM_DCIP3); + EXPECT_EQ(fromString(toString(CM_DP3)), CM_DP3); + EXPECT_EQ(fromString(toString(CM_ADOBE)), CM_ADOBE); +} diff --git a/tests/helpers/Color.cpp b/tests/helpers/Color.cpp new file mode 100644 index 000000000..4dd562684 --- /dev/null +++ b/tests/helpers/Color.cpp @@ -0,0 +1,96 @@ +#include + +#include + +TEST(Helpers, colorConstructorDefault) { + CHyprColor c; + EXPECT_DOUBLE_EQ(c.r, 0.0); + EXPECT_DOUBLE_EQ(c.g, 0.0); + EXPECT_DOUBLE_EQ(c.b, 0.0); + EXPECT_DOUBLE_EQ(c.a, 0.0); +} + +TEST(Helpers, colorConstructorFloats) { + CHyprColor c(0.5f, 0.25f, 0.75f, 1.0f); + EXPECT_FLOAT_EQ(c.r, 0.5); + EXPECT_FLOAT_EQ(c.g, 0.25); + EXPECT_FLOAT_EQ(c.b, 0.75); + EXPECT_FLOAT_EQ(c.a, 1.0); +} + +TEST(Helpers, colorConstructorHex) { + // Format: 0xAARRGGBB + CHyprColor white(0xFFFFFFFFULL); + EXPECT_NEAR(white.r, 1.0, 0.01); + EXPECT_NEAR(white.g, 1.0, 0.01); + EXPECT_NEAR(white.b, 1.0, 0.01); + EXPECT_NEAR(white.a, 1.0, 0.01); + + CHyprColor red(0xFFFF0000ULL); + EXPECT_NEAR(red.r, 1.0, 0.01); + EXPECT_NEAR(red.g, 0.0, 0.01); + EXPECT_NEAR(red.b, 0.0, 0.01); + EXPECT_NEAR(red.a, 1.0, 0.01); + + CHyprColor transparent(0x00000000ULL); + EXPECT_NEAR(transparent.r, 0.0, 0.01); + EXPECT_NEAR(transparent.g, 0.0, 0.01); + EXPECT_NEAR(transparent.b, 0.0, 0.01); + EXPECT_NEAR(transparent.a, 0.0, 0.01); +} + +TEST(Helpers, colorGetAsHex) { + CHyprColor white(1.0f, 1.0f, 1.0f, 1.0f); + EXPECT_EQ(white.getAsHex(), 0xFFFFFFFF); + + CHyprColor black(0.0f, 0.0f, 0.0f, 1.0f); + EXPECT_EQ(black.getAsHex(), 0xFF000000); + + CHyprColor transparent(0.0f, 0.0f, 0.0f, 0.0f); + EXPECT_EQ(transparent.getAsHex(), 0x00000000); +} + +TEST(Helpers, colorStripA) { + CHyprColor c(0.5f, 0.25f, 0.75f, 0.3f); + CHyprColor stripped = c.stripA(); + + EXPECT_FLOAT_EQ(stripped.r, 0.5); + EXPECT_FLOAT_EQ(stripped.g, 0.25); + EXPECT_FLOAT_EQ(stripped.b, 0.75); + EXPECT_FLOAT_EQ(stripped.a, 1.0); + + // original unchanged + EXPECT_FLOAT_EQ(c.a, 0.3f); +} + +TEST(Helpers, colorModifyA) { + CHyprColor c(0.5f, 0.25f, 0.75f, 1.0f); + CHyprColor modified = c.modifyA(0.5f); + + EXPECT_FLOAT_EQ(modified.r, 0.5); + EXPECT_FLOAT_EQ(modified.g, 0.25); + EXPECT_FLOAT_EQ(modified.b, 0.75); + EXPECT_FLOAT_EQ(modified.a, 0.5); + + // original unchanged + EXPECT_FLOAT_EQ(c.a, 1.0); +} + +TEST(Helpers, colorEquality) { + CHyprColor a(1.0f, 0.0f, 0.0f, 1.0f); + CHyprColor b(1.0f, 0.0f, 0.0f, 1.0f); + CHyprColor c(0.0f, 1.0f, 0.0f, 1.0f); + CHyprColor d(1.0f, 0.0f, 0.0f, 0.5f); + + EXPECT_EQ(a, b); + EXPECT_NE(a, c); + EXPECT_NE(a, d); // different alpha +} + +TEST(Helpers, colorConstants) { + EXPECT_EQ(Colors::WHITE, CHyprColor(1.0f, 1.0f, 1.0f, 1.0f)); + EXPECT_EQ(Colors::BLACK, CHyprColor(0.0f, 0.0f, 0.0f, 1.0f)); + EXPECT_EQ(Colors::RED, CHyprColor(1.0f, 0.0f, 0.0f, 1.0f)); + EXPECT_EQ(Colors::GREEN, CHyprColor(0.0f, 1.0f, 0.0f, 1.0f)); + EXPECT_EQ(Colors::BLUE, CHyprColor(0.0f, 0.0f, 1.0f, 1.0f)); +} diff --git a/tests/helpers/DamageRing.cpp b/tests/helpers/DamageRing.cpp new file mode 100644 index 000000000..c9f1653fe --- /dev/null +++ b/tests/helpers/DamageRing.cpp @@ -0,0 +1,313 @@ +#include + +#include + +// --- setSize --- + +TEST(DamageRing, setSizeMarksDamageEntire) { + CDamageRing ring; + ring.setSize({100, 200}); + + // After setSize the entire screen should be damaged + EXPECT_TRUE(ring.hasChanged()); +} + +TEST(DamageRing, setSizeSameSizeNoChange) { + CDamageRing ring; + ring.setSize({100, 200}); + ring.rotate(); // clear current damage + + // Setting same size again must not re-damage + ring.setSize({100, 200}); + EXPECT_FALSE(ring.hasChanged()); +} + +TEST(DamageRing, setSizeNewSizeReDamages) { + CDamageRing ring; + ring.setSize({100, 200}); + ring.rotate(); // clear current damage + + ring.setSize({300, 400}); + EXPECT_TRUE(ring.hasChanged()); +} + +TEST(DamageRing, setSizeZeroSize) { + CDamageRing ring; + ring.setSize({0, 0}); + + // Zero-size screen: damageEntire covers a 0x0 box, which is empty + EXPECT_FALSE(ring.hasChanged()); +} + +// --- damage --- + +TEST(DamageRing, damageReturnsTrueForRegionInsideSize) { + CDamageRing ring; + ring.setSize({200, 200}); + ring.rotate(); // clear current damage + + CRegion rg(10, 10, 50, 50); + EXPECT_TRUE(ring.damage(rg)); +} + +TEST(DamageRing, damageReturnsFalseForEmptyRegion) { + CDamageRing ring; + ring.setSize({200, 200}); + ring.rotate(); + + CRegion empty; + EXPECT_FALSE(ring.damage(empty)); +} + +TEST(DamageRing, damageReturnsFalseForRegionOutsideSize) { + CDamageRing ring; + ring.setSize({100, 100}); + ring.rotate(); + + // Region entirely outside the screen bounds + CRegion outside(200, 200, 50, 50); + EXPECT_FALSE(ring.damage(outside)); +} + +TEST(DamageRing, damageClipsRegionToSize) { + CDamageRing ring; + ring.setSize({100, 100}); + ring.rotate(); + + // Region that partially overlaps the screen + CRegion partial(50, 50, 200, 200); + EXPECT_TRUE(ring.damage(partial)); + EXPECT_TRUE(ring.hasChanged()); +} + +TEST(DamageRing, damageSetsHasChanged) { + CDamageRing ring; + ring.setSize({200, 200}); + ring.rotate(); + + EXPECT_FALSE(ring.hasChanged()); + + CRegion rg(0, 0, 10, 10); + ring.damage(rg); + EXPECT_TRUE(ring.hasChanged()); +} + +// --- damageEntire --- + +TEST(DamageRing, damageEntireSetsHasChanged) { + CDamageRing ring; + ring.setSize({200, 200}); + ring.rotate(); + + EXPECT_FALSE(ring.hasChanged()); + ring.damageEntire(); + EXPECT_TRUE(ring.hasChanged()); +} + +TEST(DamageRing, damageEntireOnZeroSizeDoesNotSetHasChanged) { + CDamageRing ring; + ring.setSize({0, 0}); + ring.rotate(); + + ring.damageEntire(); + EXPECT_FALSE(ring.hasChanged()); +} + +// --- rotate --- + +TEST(DamageRing, rotateClearsCurrentDamage) { + CDamageRing ring; + ring.setSize({200, 200}); + // setSize already damaged; rotate should clear it + ring.rotate(); + EXPECT_FALSE(ring.hasChanged()); +} + +TEST(DamageRing, rotatePreservesCurrentAsPreviousForNextFrame) { + CDamageRing ring; + ring.setSize({200, 200}); + ring.rotate(); // clear initial damage + + CRegion rg(0, 0, 50, 50); + ring.damage(rg); + ring.rotate(); // the damage just applied becomes "previous[0]" + + // With age=2 we should see that previous damage accumulated + CRegion bufDamage = ring.getBufferDamage(2); + EXPECT_FALSE(bufDamage.empty()); +} + +TEST(DamageRing, rotateMultipleTimes) { + CDamageRing ring; + ring.setSize({200, 200}); + + // Rotate more times than the ring length — must not crash + for (int i = 0; i < DAMAGE_RING_PREVIOUS_LEN + 5; ++i) { + ring.damageEntire(); + ring.rotate(); + } + EXPECT_FALSE(ring.hasChanged()); +} + +// --- getBufferDamage --- + +TEST(DamageRing, getBufferDamageAge0ReturnsFullScreen) { + CDamageRing ring; + ring.setSize({100, 100}); + ring.rotate(); + + // age <= 0 returns full screen + CRegion full = ring.getBufferDamage(0); + EXPECT_FALSE(full.empty()); +} + +TEST(DamageRing, getBufferDamageNegativeAgeReturnsFullScreen) { + CDamageRing ring; + ring.setSize({100, 100}); + ring.rotate(); + + CRegion full = ring.getBufferDamage(-1); + EXPECT_FALSE(full.empty()); +} + +TEST(DamageRing, getBufferDamageExceedsRingLengthReturnsFullScreen) { + CDamageRing ring; + ring.setSize({100, 100}); + ring.rotate(); + + // age > DAMAGE_RING_PREVIOUS_LEN + 1 returns full screen + CRegion full = ring.getBufferDamage(DAMAGE_RING_PREVIOUS_LEN + 2); + EXPECT_FALSE(full.empty()); +} + +TEST(DamageRing, getBufferDamageAge1ReturnCurrentOnly) { + CDamageRing ring; + ring.setSize({200, 200}); + ring.rotate(); + + EXPECT_TRUE(ring.getBufferDamage(1).empty()); + + CRegion rg(10, 10, 20, 20); + ring.damage(rg); + EXPECT_FALSE(ring.getBufferDamage(1).empty()); +} + +TEST(DamageRing, getBufferDamageAge2AccumulatesOnePreviousFrame) { + CDamageRing ring; + ring.setSize({200, 200}); + ring.rotate(); + + // Frame N: damage a region, then rotate + CRegion rg(0, 0, 30, 30); + ring.damage(rg); + ring.rotate(); + + // Frame N+1: no new damage + // age=2 should include previous frame's damage + CRegion buf = ring.getBufferDamage(2); + EXPECT_FALSE(buf.empty()); +} + +TEST(DamageRing, getBufferDamageAccumulatesUpToRingLength) { + CDamageRing ring; + ring.setSize({200, 200}); + ring.rotate(); + + // Fill the ring with damage then check full accumulation + for (int i = 0; i < DAMAGE_RING_PREVIOUS_LEN; ++i) { + CRegion rg(i * 10, 0, 5, 5); + ring.damage(rg); + ring.rotate(); + } + + // age = DAMAGE_RING_PREVIOUS_LEN + 1 accumulates all stored frames + CRegion buf = ring.getBufferDamage(DAMAGE_RING_PREVIOUS_LEN + 1); + EXPECT_FALSE(buf.empty()); +} + +TEST(DamageRing, getBufferDamageCoalescesWhenTooManyRects) { + CDamageRing ring; + ring.setSize({500, 500}); + ring.rotate(); + + // Add many non-overlapping rects across several frames so that + // accumulation produces more than 8 rectangles, triggering the + // getExtents() fallback path. + int x = 0; + for (int frame = 0; frame < DAMAGE_RING_PREVIOUS_LEN; ++frame) { + for (int r = 0; r < 4; ++r) { + ring.damage(CRegion(x, 0, 5, 5)); + x += 10; + } + ring.rotate(); + } + + // Current frame damage + ring.damage(CRegion(x, 0, 5, 5)); + + // age = DAMAGE_RING_PREVIOUS_LEN + 1 accumulates all frames + CRegion buf = ring.getBufferDamage(DAMAGE_RING_PREVIOUS_LEN + 1); + EXPECT_FALSE(buf.empty()); + + // The result should be coalesced into a single extents rect (<=1 rect) + EXPECT_LE(buf.getRects().size(), 1); +} + +TEST(DamageRing, getBufferDamageEmptyRingReturnsEmptyForValidAge) { + CDamageRing ring; + ring.setSize({200, 200}); + + // Rotate enough times to clear all previous slots + for (int i = 0; i < DAMAGE_RING_PREVIOUS_LEN + 1; ++i) + ring.rotate(); + + // No damage in any slot + CRegion buf = ring.getBufferDamage(1); + EXPECT_TRUE(buf.empty()); +} + +// --- hasChanged --- + +TEST(DamageRing, hasChangedFalseInitially) { + CDamageRing ring; + // No size set, no damage applied + EXPECT_FALSE(ring.hasChanged()); +} + +TEST(DamageRing, hasChangedTrueAfterDamage) { + CDamageRing ring; + ring.setSize({100, 100}); + ring.rotate(); + + CRegion rg(0, 0, 10, 10); + ring.damage(rg); + EXPECT_TRUE(ring.hasChanged()); +} + +TEST(DamageRing, hasChangedFalseAfterRotate) { + CDamageRing ring; + ring.setSize({100, 100}); + // setSize caused damage; rotate clears it + ring.rotate(); + EXPECT_FALSE(ring.hasChanged()); +} + +TEST(DamageRing, hasChangedTrueAfterDamageEntire) { + CDamageRing ring; + ring.setSize({100, 100}); + ring.rotate(); + + ring.damageEntire(); + EXPECT_TRUE(ring.hasChanged()); +} + +TEST(DamageRing, hasChangedFalseAfterDamageOutsideBounds) { + CDamageRing ring; + ring.setSize({100, 100}); + ring.rotate(); + + // Damage entirely outside the screen must not change state + CRegion outside(500, 500, 10, 10); + ring.damage(outside); + EXPECT_FALSE(ring.hasChanged()); +} diff --git a/tests/helpers/Direction.cpp b/tests/helpers/Direction.cpp new file mode 100644 index 000000000..9bf1c6f7a --- /dev/null +++ b/tests/helpers/Direction.cpp @@ -0,0 +1,40 @@ +#include + +#include + +using namespace Math; + +TEST(Helpers, directionFromCharValid) { + EXPECT_EQ(fromChar('r'), DIRECTION_RIGHT); + EXPECT_EQ(fromChar('l'), DIRECTION_LEFT); + EXPECT_EQ(fromChar('u'), DIRECTION_UP); + EXPECT_EQ(fromChar('d'), DIRECTION_DOWN); + EXPECT_EQ(fromChar('t'), DIRECTION_UP); + EXPECT_EQ(fromChar('b'), DIRECTION_DOWN); +} + +TEST(Helpers, directionFromCharInvalid) { + EXPECT_EQ(fromChar('x'), DIRECTION_DEFAULT); + EXPECT_EQ(fromChar('z'), DIRECTION_DEFAULT); + EXPECT_EQ(fromChar('0'), DIRECTION_DEFAULT); + EXPECT_EQ(fromChar(' '), DIRECTION_DEFAULT); + EXPECT_EQ(fromChar('\0'), DIRECTION_DEFAULT); +} + +TEST(Helpers, directionToString) { + EXPECT_STREQ(toString(DIRECTION_UP), "up"); + EXPECT_STREQ(toString(DIRECTION_DOWN), "down"); + EXPECT_STREQ(toString(DIRECTION_LEFT), "left"); + EXPECT_STREQ(toString(DIRECTION_RIGHT), "right"); + EXPECT_STREQ(toString(DIRECTION_DEFAULT), "default"); +} + +TEST(Helpers, directionFromCharToString) { + EXPECT_STREQ(toString(fromChar('r')), "right"); + EXPECT_STREQ(toString(fromChar('l')), "left"); + EXPECT_STREQ(toString(fromChar('u')), "up"); + EXPECT_STREQ(toString(fromChar('d')), "down"); + EXPECT_STREQ(toString(fromChar('t')), "up"); + EXPECT_STREQ(toString(fromChar('b')), "down"); + EXPECT_STREQ(toString(fromChar('x')), "default"); +} diff --git a/tests/helpers/Drm.cpp b/tests/helpers/Drm.cpp new file mode 100644 index 000000000..6ebc23675 --- /dev/null +++ b/tests/helpers/Drm.cpp @@ -0,0 +1,32 @@ +#include + +#include + +#include +#include +#include + +TEST(Helpers, drmDevIDFromFDCharacterDevice) { + const int FD = open("/dev/null", O_RDONLY | O_CLOEXEC); + ASSERT_GE(FD, 0); + + struct stat stat = {}; + ASSERT_EQ(fstat(FD, &stat), 0); + + const auto devID = DRM::devIDFromFD(FD); + EXPECT_TRUE(devID.has_value()); + EXPECT_EQ(*devID, stat.st_rdev); + + close(FD); +} + +TEST(Helpers, drmDevIDFromFDRejectsRegularFiles) { + char path[] = "/tmp/hyprland-drm-testXXXXXX"; + const int FD = mkstemp(path); + ASSERT_GE(FD, 0); + + EXPECT_FALSE(DRM::devIDFromFD(FD).has_value()); + + close(FD); + unlink(path); +} diff --git a/tests/helpers/Expression.cpp b/tests/helpers/Expression.cpp new file mode 100644 index 000000000..8db677763 --- /dev/null +++ b/tests/helpers/Expression.cpp @@ -0,0 +1,67 @@ +#include + +#include + +using namespace Math; + +TEST(Helpers, expressionBasicArithmetic) { + CExpression expr; + EXPECT_DOUBLE_EQ(expr.compute("2 + 3").value(), 5.0); + EXPECT_DOUBLE_EQ(expr.compute("10 - 4").value(), 6.0); + EXPECT_DOUBLE_EQ(expr.compute("3 * 7").value(), 21.0); + EXPECT_DOUBLE_EQ(expr.compute("10 / 4").value(), 2.5); +} + +TEST(Helpers, expressionOrderOfOperations) { + CExpression expr; + EXPECT_DOUBLE_EQ(expr.compute("2 + 3 * 4").value(), 14.0); + EXPECT_DOUBLE_EQ(expr.compute("(2 + 3) * 4").value(), 20.0); + EXPECT_DOUBLE_EQ(expr.compute("10 - 2 * 3").value(), 4.0); + EXPECT_DOUBLE_EQ(expr.compute("(10 - 2) * 3").value(), 24.0); +} + +TEST(Helpers, expressionNegativeNumbers) { + CExpression expr; + EXPECT_DOUBLE_EQ(expr.compute("-5 + 3").value(), -2.0); + EXPECT_DOUBLE_EQ(expr.compute("-5 * -3").value(), 15.0); + EXPECT_DOUBLE_EQ(expr.compute("0 - 10").value(), -10.0); +} + +TEST(Helpers, expressionFloatingPoint) { + CExpression expr; + EXPECT_DOUBLE_EQ(expr.compute("1.5 + 2.5").value(), 4.0); + EXPECT_DOUBLE_EQ(expr.compute("0.1 * 10").value(), 1.0); + EXPECT_NEAR(expr.compute("1 / 3").value(), 0.3333, 0.001); +} + +TEST(Helpers, expressionWithVariables) { + CExpression expr; + expr.addVariable("x", 10); + EXPECT_DOUBLE_EQ(expr.compute("x * 2").value(), 20.0); + EXPECT_DOUBLE_EQ(expr.compute("x + 5").value(), 15.0); + EXPECT_DOUBLE_EQ(expr.compute("x * x").value(), 100.0); +} + +TEST(Helpers, expressionMultipleVariables) { + CExpression expr; + expr.addVariable("w", 1920); + expr.addVariable("h", 1080); + EXPECT_DOUBLE_EQ(expr.compute("w / 2").value(), 960.0); + EXPECT_DOUBLE_EQ(expr.compute("w + h").value(), 3000.0); + EXPECT_DOUBLE_EQ(expr.compute("w * h").value(), 1920.0 * 1080.0); +} + +TEST(Helpers, expressionInvalidInput) { + CExpression expr; + EXPECT_EQ(expr.compute(""), std::nullopt); + EXPECT_EQ(expr.compute("2 +"), std::nullopt); + EXPECT_EQ(expr.compute("abc"), std::nullopt); + EXPECT_EQ(expr.compute("* 5"), std::nullopt); +} + +TEST(Helpers, expressionZero) { + CExpression expr; + EXPECT_DOUBLE_EQ(expr.compute("0").value(), 0.0); + EXPECT_DOUBLE_EQ(expr.compute("0 * 999").value(), 0.0); + EXPECT_DOUBLE_EQ(expr.compute("5 - 5").value(), 0.0); +} diff --git a/tests/helpers/Format.cpp b/tests/helpers/Format.cpp new file mode 100644 index 000000000..705d13d1d --- /dev/null +++ b/tests/helpers/Format.cpp @@ -0,0 +1,79 @@ +#include + +#include + +#include +#include +#include + +using namespace Hyprgraphics::Egl; +using namespace NFormatUtils; + +TEST(Helpers, formatDrmToShm) { + EXPECT_EQ(drmToShm(DRM_FORMAT_XRGB8888), WL_SHM_FORMAT_XRGB8888); + EXPECT_EQ(drmToShm(DRM_FORMAT_ARGB8888), WL_SHM_FORMAT_ARGB8888); +} + +TEST(Helpers, formatShmToDrm) { + EXPECT_EQ(shmToDRM(WL_SHM_FORMAT_XRGB8888), DRM_FORMAT_XRGB8888); + EXPECT_EQ(shmToDRM(WL_SHM_FORMAT_ARGB8888), DRM_FORMAT_ARGB8888); +} + +TEST(Helpers, formatDrmShmRoundTrip) { + EXPECT_EQ(shmToDRM(drmToShm(DRM_FORMAT_XRGB8888)), DRM_FORMAT_XRGB8888); + EXPECT_EQ(shmToDRM(drmToShm(DRM_FORMAT_ARGB8888)), DRM_FORMAT_ARGB8888); +} + +TEST(Helpers, formatIsFormatYUV) { + EXPECT_TRUE(isFormatYUV(DRM_FORMAT_YUYV)); + EXPECT_TRUE(isFormatYUV(DRM_FORMAT_NV12)); + EXPECT_TRUE(isFormatYUV(DRM_FORMAT_NV21)); + EXPECT_FALSE(isFormatYUV(DRM_FORMAT_XRGB8888)); + EXPECT_FALSE(isFormatYUV(DRM_FORMAT_ARGB8888)); +} + +TEST(Helpers, formatGetPixelFormatFromDRM) { + const auto* xrgb = getPixelFormatFromDRM(DRM_FORMAT_XRGB8888); + ASSERT_NE(xrgb, nullptr); + EXPECT_EQ(xrgb->drmFormat, DRM_FORMAT_XRGB8888); + EXPECT_FALSE(xrgb->withAlpha); + + const auto* argb = getPixelFormatFromDRM(DRM_FORMAT_ARGB8888); + ASSERT_NE(argb, nullptr); + EXPECT_EQ(argb->drmFormat, DRM_FORMAT_ARGB8888); + EXPECT_TRUE(argb->withAlpha); + + EXPECT_EQ(getPixelFormatFromDRM(0), nullptr); +} + +TEST(Helpers, formatIsFormatOpaque) { + EXPECT_TRUE(isDrmFormatOpaque(DRM_FORMAT_XRGB8888)); + EXPECT_FALSE(isDrmFormatOpaque(DRM_FORMAT_ARGB8888)); +} + +TEST(Helpers, formatPixelsPerBlock) { + const auto* fmt = getPixelFormatFromDRM(DRM_FORMAT_XRGB8888); + ASSERT_NE(fmt, nullptr); + EXPECT_GT(pixelsPerBlock(fmt), 0); +} + +TEST(Helpers, formatMinStride) { + const auto* fmt = getPixelFormatFromDRM(DRM_FORMAT_XRGB8888); + ASSERT_NE(fmt, nullptr); + // XRGB8888 = 4 bytes per pixel, 1920 wide = 7680 bytes stride + EXPECT_EQ(minStride(fmt, 1920), 1920 * 4); + EXPECT_EQ(minStride(fmt, 0), 0); +} + +TEST(Helpers, formatDrmFormatName) { + EXPECT_FALSE(drmFormatName(DRM_FORMAT_XRGB8888).empty()); + EXPECT_FALSE(drmFormatName(DRM_FORMAT_ARGB8888).empty()); + EXPECT_EQ(drmFormatName(0), "INVALID"); +} + +TEST(Helpers, formatAlphaFormat) { + EXPECT_EQ(alphaFormat(DRM_FORMAT_XRGB8888), DRM_FORMAT_ARGB8888); + EXPECT_EQ(alphaFormat(DRM_FORMAT_XBGR8888), DRM_FORMAT_ABGR8888); + // Format without alpha stripped entry returns DRM_FORMAT_INVALID (0) + EXPECT_EQ(alphaFormat(DRM_FORMAT_ARGB8888), 0u); +} diff --git a/tests/helpers/MathTransform.cpp b/tests/helpers/MathTransform.cpp new file mode 100644 index 000000000..686721b95 --- /dev/null +++ b/tests/helpers/MathTransform.cpp @@ -0,0 +1,73 @@ +#include + +#include + +using namespace Math; + +// wlTransformToHyprutils + +TEST(Helpers, mathWlTransformToHyprutils) { + EXPECT_EQ(wlTransformToHyprutils(WL_OUTPUT_TRANSFORM_NORMAL), eTransform::HYPRUTILS_TRANSFORM_NORMAL); + EXPECT_EQ(wlTransformToHyprutils(WL_OUTPUT_TRANSFORM_90), eTransform::HYPRUTILS_TRANSFORM_90); + EXPECT_EQ(wlTransformToHyprutils(WL_OUTPUT_TRANSFORM_180), eTransform::HYPRUTILS_TRANSFORM_180); + EXPECT_EQ(wlTransformToHyprutils(WL_OUTPUT_TRANSFORM_270), eTransform::HYPRUTILS_TRANSFORM_270); + EXPECT_EQ(wlTransformToHyprutils(WL_OUTPUT_TRANSFORM_FLIPPED), eTransform::HYPRUTILS_TRANSFORM_FLIPPED); + EXPECT_EQ(wlTransformToHyprutils(WL_OUTPUT_TRANSFORM_FLIPPED_90), eTransform::HYPRUTILS_TRANSFORM_FLIPPED_90); + EXPECT_EQ(wlTransformToHyprutils(WL_OUTPUT_TRANSFORM_FLIPPED_180), eTransform::HYPRUTILS_TRANSFORM_FLIPPED_180); + EXPECT_EQ(wlTransformToHyprutils(WL_OUTPUT_TRANSFORM_FLIPPED_270), eTransform::HYPRUTILS_TRANSFORM_FLIPPED_270); +} + +TEST(Helpers, mathWlTransformToHyprutilsInvalid) { + // Invalid value falls back to NORMAL + EXPECT_EQ(wlTransformToHyprutils(static_cast(99)), eTransform::HYPRUTILS_TRANSFORM_NORMAL); +} + +// invertTransform + +TEST(Helpers, mathInvertTransformNonRotated) { + // Non-rotated transforms are their own inverse + EXPECT_EQ(invertTransform(WL_OUTPUT_TRANSFORM_NORMAL), WL_OUTPUT_TRANSFORM_NORMAL); + EXPECT_EQ(invertTransform(WL_OUTPUT_TRANSFORM_180), WL_OUTPUT_TRANSFORM_180); + EXPECT_EQ(invertTransform(WL_OUTPUT_TRANSFORM_FLIPPED), WL_OUTPUT_TRANSFORM_FLIPPED); + EXPECT_EQ(invertTransform(WL_OUTPUT_TRANSFORM_FLIPPED_180), WL_OUTPUT_TRANSFORM_FLIPPED_180); +} + +TEST(Helpers, mathInvertTransformRotated) { + // 90 and 270 swap when inverted (non-flipped) + EXPECT_EQ(invertTransform(WL_OUTPUT_TRANSFORM_90), WL_OUTPUT_TRANSFORM_270); + EXPECT_EQ(invertTransform(WL_OUTPUT_TRANSFORM_270), WL_OUTPUT_TRANSFORM_90); +} + +TEST(Helpers, mathInvertTransformFlippedRotated) { + // Flipped rotations: flipped bit stays, 90/270 don't swap + EXPECT_EQ(invertTransform(WL_OUTPUT_TRANSFORM_FLIPPED_90), WL_OUTPUT_TRANSFORM_FLIPPED_90); + EXPECT_EQ(invertTransform(WL_OUTPUT_TRANSFORM_FLIPPED_270), WL_OUTPUT_TRANSFORM_FLIPPED_270); +} + +TEST(Helpers, mathInvertTransformDoubleInvert) { + // Double invert returns original for all transforms + for (int i = 0; i <= 7; i++) { + auto t = static_cast(i); + EXPECT_EQ(invertTransform(invertTransform(t)), t); + } +} + +// composeTransform + +TEST(Helpers, mathComposeTransformIdentity) { + // Composing with NORMAL is identity + for (int i = 0; i <= 7; i++) { + auto t = static_cast(i); + EXPECT_EQ(composeTransform(t, eTransform::HYPRUTILS_TRANSFORM_NORMAL), t); + EXPECT_EQ(composeTransform(eTransform::HYPRUTILS_TRANSFORM_NORMAL, t), t); + } +} + +TEST(Helpers, mathComposeTransformRotation) { + // 90 + 90 = 180 + EXPECT_EQ(composeTransform(eTransform::HYPRUTILS_TRANSFORM_90, eTransform::HYPRUTILS_TRANSFORM_90), eTransform::HYPRUTILS_TRANSFORM_180); + // 90 + 180 = 270 + EXPECT_EQ(composeTransform(eTransform::HYPRUTILS_TRANSFORM_90, eTransform::HYPRUTILS_TRANSFORM_180), eTransform::HYPRUTILS_TRANSFORM_270); + // 180 + 180 = NORMAL (360) + EXPECT_EQ(composeTransform(eTransform::HYPRUTILS_TRANSFORM_180, eTransform::HYPRUTILS_TRANSFORM_180), eTransform::HYPRUTILS_TRANSFORM_NORMAL); +} diff --git a/tests/helpers/MiscFunctions.cpp b/tests/helpers/MiscFunctions.cpp new file mode 100644 index 000000000..e0857987d --- /dev/null +++ b/tests/helpers/MiscFunctions.cpp @@ -0,0 +1,145 @@ +#include + +#include + +// escapeJSONStrings + +TEST(Helpers, escapeJSONStringsBasic) { + EXPECT_EQ(escapeJSONStrings("hello"), "hello"); + EXPECT_EQ(escapeJSONStrings(""), ""); +} + +TEST(Helpers, escapeJSONStringsSpecialChars) { + EXPECT_EQ(escapeJSONStrings("say \"hello\""), "say \\\"hello\\\""); + EXPECT_EQ(escapeJSONStrings("back\\slash"), "back\\\\slash"); + EXPECT_EQ(escapeJSONStrings("line\nbreak"), "line\\nbreak"); + EXPECT_EQ(escapeJSONStrings("tab\there"), "tab\\there"); + EXPECT_EQ(escapeJSONStrings("cr\rhere"), "cr\\rhere"); +} + +// isDirection + +TEST(Helpers, isDirectionString) { + EXPECT_TRUE(isDirection("l")); + EXPECT_TRUE(isDirection("r")); + EXPECT_TRUE(isDirection("u")); + EXPECT_TRUE(isDirection("d")); + EXPECT_TRUE(isDirection("t")); + EXPECT_TRUE(isDirection("b")); + EXPECT_FALSE(isDirection("x")); + EXPECT_FALSE(isDirection("left")); + EXPECT_FALSE(isDirection("")); +} + +TEST(Helpers, isDirectionChar) { + EXPECT_TRUE(isDirection('l')); + EXPECT_TRUE(isDirection('r')); + EXPECT_TRUE(isDirection('u')); + EXPECT_TRUE(isDirection('d')); + EXPECT_TRUE(isDirection('t')); + EXPECT_TRUE(isDirection('b')); + EXPECT_FALSE(isDirection('x')); + EXPECT_FALSE(isDirection('0')); + EXPECT_FALSE(isDirection(' ')); +} + +// normalizeAngleRad + +TEST(Helpers, normalizeAngleRadInRange) { + EXPECT_DOUBLE_EQ(normalizeAngleRad(0.0), 0.0); + EXPECT_DOUBLE_EQ(normalizeAngleRad(M_PI), M_PI); + EXPECT_DOUBLE_EQ(normalizeAngleRad(M_PI * 2), M_PI * 2); +} + +TEST(Helpers, normalizeAngleRadNegative) { + EXPECT_NEAR(normalizeAngleRad(-M_PI), M_PI, 0.001); + EXPECT_NEAR(normalizeAngleRad(-M_PI / 2), 3 * M_PI / 2, 0.001); +} + +TEST(Helpers, normalizeAngleRadLarge) { + EXPECT_NEAR(normalizeAngleRad(3 * M_PI), M_PI, 0.001); + EXPECT_NEAR(normalizeAngleRad(5 * M_PI), M_PI, 0.001); +} + +// stringToPercentage + +TEST(Helpers, stringToPercentagePercent) { + EXPECT_FLOAT_EQ(stringToPercentage("50%", 200.0f), 100.0f); + EXPECT_FLOAT_EQ(stringToPercentage("100%", 500.0f), 500.0f); + EXPECT_FLOAT_EQ(stringToPercentage("0%", 1000.0f), 0.0f); + EXPECT_FLOAT_EQ(stringToPercentage("25%", 400.0f), 100.0f); +} + +TEST(Helpers, stringToPercentageAbsolute) { + EXPECT_FLOAT_EQ(stringToPercentage("42", 999.0f), 42.0f); + EXPECT_FLOAT_EQ(stringToPercentage("0", 999.0f), 0.0f); + EXPECT_FLOAT_EQ(stringToPercentage("1.5", 999.0f), 1.5f); +} + +// truthy + +TEST(Helpers, truthyTrue) { + EXPECT_TRUE(truthy("1")); + EXPECT_TRUE(truthy("true")); + EXPECT_TRUE(truthy("True")); + EXPECT_TRUE(truthy("TRUE")); + EXPECT_TRUE(truthy("yes")); + EXPECT_TRUE(truthy("Yes")); + EXPECT_TRUE(truthy("on")); + EXPECT_TRUE(truthy("On")); +} + +TEST(Helpers, truthyFalse) { + EXPECT_FALSE(truthy("0")); + EXPECT_FALSE(truthy("false")); + EXPECT_FALSE(truthy("no")); + EXPECT_FALSE(truthy("off")); + EXPECT_FALSE(truthy("")); + EXPECT_FALSE(truthy("random")); +} + +// configStringToInt + +TEST(Helpers, configStringToIntDecimal) { + EXPECT_EQ(configStringToInt("42").value(), 42); + EXPECT_EQ(configStringToInt("0").value(), 0); + EXPECT_EQ(configStringToInt("-1").value(), -1); +} + +TEST(Helpers, configStringToIntHex) { + EXPECT_EQ(configStringToInt("0xFF").value(), 255); + EXPECT_EQ(configStringToInt("0x00").value(), 0); + EXPECT_EQ(configStringToInt("0x10").value(), 16); +} + +TEST(Helpers, configStringToIntRgba) { + auto result = configStringToInt("rgba(255, 0, 0, 1.0)"); + EXPECT_TRUE(result.has_value()); +} + +TEST(Helpers, configStringToIntBooleanStrings) { + // "true", "yes", "on" -> 1; "false", "no", "off" -> 0 + EXPECT_EQ(configStringToInt("true").value(), 1); + EXPECT_EQ(configStringToInt("yes").value(), 1); + EXPECT_EQ(configStringToInt("on").value(), 1); + EXPECT_EQ(configStringToInt("false").value(), 0); + EXPECT_EQ(configStringToInt("no").value(), 0); + EXPECT_EQ(configStringToInt("off").value(), 0); +} + +TEST(Helpers, configStringToIntInvalid) { + EXPECT_FALSE(configStringToInt("").has_value()); + EXPECT_FALSE(configStringToInt("abc").has_value()); +} + +// configStringToVector2D + +TEST(Helpers, configStringToVector2DValid) { + EXPECT_EQ(configStringToVector2D("1920 1080"), Vector2D(1920, 1080)); + EXPECT_EQ(configStringToVector2D("0 0"), Vector2D(0, 0)); +} + +TEST(Helpers, configStringToVector2DInvalid) { + EXPECT_THROW(configStringToVector2D("notvalid"), std::invalid_argument); + EXPECT_THROW(configStringToVector2D(""), std::invalid_argument); +} diff --git a/tests/helpers/TagKeeper.cpp b/tests/helpers/TagKeeper.cpp new file mode 100644 index 000000000..79eb72251 --- /dev/null +++ b/tests/helpers/TagKeeper.cpp @@ -0,0 +1,148 @@ +#include + +#include + +// --- applyTag: set with + prefix --- + +TEST(TagKeeper, applyTagSetAddsTag) { + CTagKeeper keeper; + EXPECT_TRUE(keeper.applyTag("+myTag")); + EXPECT_TRUE(keeper.isTagged("myTag")); +} + +TEST(TagKeeper, applyTagSetReturnsFalseIfAlreadySet) { + CTagKeeper keeper; + keeper.applyTag("+myTag"); + EXPECT_FALSE(keeper.applyTag("+myTag")); +} + +// --- applyTag: unset with - prefix --- + +TEST(TagKeeper, applyTagUnsetRemovesTag) { + CTagKeeper keeper; + keeper.applyTag("+myTag"); + EXPECT_TRUE(keeper.applyTag("-myTag")); + EXPECT_FALSE(keeper.isTagged("myTag")); +} + +TEST(TagKeeper, applyTagUnsetReturnsFalseIfNotSet) { + CTagKeeper keeper; + EXPECT_FALSE(keeper.applyTag("-myTag")); +} + +// --- applyTag: toggle without prefix --- + +TEST(TagKeeper, applyTagToggleSetsWhenAbsent) { + CTagKeeper keeper; + EXPECT_TRUE(keeper.applyTag("myTag")); + EXPECT_TRUE(keeper.isTagged("myTag")); +} + +TEST(TagKeeper, applyTagToggleUnsetsWhenPresent) { + CTagKeeper keeper; + keeper.applyTag("+myTag"); + EXPECT_TRUE(keeper.applyTag("myTag")); + EXPECT_FALSE(keeper.isTagged("myTag")); +} + +// --- applyTag: dynamic tags --- + +TEST(TagKeeper, applyTagDynamicAppendsStar) { + CTagKeeper keeper; + keeper.applyTag("myTag", true); + EXPECT_TRUE(keeper.getTags().contains("myTag*")); + EXPECT_FALSE(keeper.getTags().contains("myTag")); +} + +TEST(TagKeeper, applyTagDynamicDoesNotDoubleAppendStar) { + CTagKeeper keeper; + keeper.applyTag("myTag*", true); + EXPECT_TRUE(keeper.getTags().contains("myTag*")); + EXPECT_FALSE(keeper.getTags().contains("myTag**")); +} + +// --- isTagged: basic matching --- + +TEST(TagKeeper, isTaggedReturnsFalseWhenEmpty) { + CTagKeeper keeper; + EXPECT_FALSE(keeper.isTagged("myTag")); +} + +TEST(TagKeeper, isTaggedExactMatch) { + CTagKeeper keeper; + keeper.applyTag("+myTag"); + EXPECT_TRUE(keeper.isTagged("myTag")); + EXPECT_FALSE(keeper.isTagged("otherTag")); +} + +// --- isTagged: dynamic (star) matching --- + +TEST(TagKeeper, isTaggedMatchesDynamicWhenNotStrict) { + CTagKeeper keeper; + keeper.applyTag("myTag", true); // stores "myTag*" + EXPECT_TRUE(keeper.isTagged("myTag")); +} + +TEST(TagKeeper, isTaggedStrictDoesNotMatchDynamic) { + CTagKeeper keeper; + keeper.applyTag("myTag", true); // stores "myTag*" + EXPECT_FALSE(keeper.isTagged("myTag", true)); +} + +TEST(TagKeeper, isTaggedStrictMatchesExact) { + CTagKeeper keeper; + keeper.applyTag("+myTag"); + EXPECT_TRUE(keeper.isTagged("myTag", true)); +} + +// --- isTagged: negative prefix --- + +TEST(TagKeeper, isTaggedNegativeInvertsResult) { + CTagKeeper keeper; + EXPECT_TRUE(keeper.isTagged("negative:myTag")); + + keeper.applyTag("+myTag"); + EXPECT_FALSE(keeper.isTagged("negative:myTag")); +} + +TEST(TagKeeper, isTaggedNegativeWithDynamic) { + CTagKeeper keeper; + keeper.applyTag("myTag", true); // stores "myTag*" + EXPECT_FALSE(keeper.isTagged("negative:myTag")); +} + +// --- removeDynamicTag --- + +TEST(TagKeeper, removeDynamicTagRemovesStarVariant) { + CTagKeeper keeper; + keeper.applyTag("myTag", true); // stores "myTag*" + EXPECT_TRUE(keeper.removeDynamicTag("myTag")); + EXPECT_FALSE(keeper.isTagged("myTag")); +} + +TEST(TagKeeper, removeDynamicTagReturnsFalseIfNoDynamic) { + CTagKeeper keeper; + keeper.applyTag("+myTag"); // stores "myTag" (not dynamic) + EXPECT_FALSE(keeper.removeDynamicTag("myTag")); + EXPECT_TRUE(keeper.isTagged("myTag")); // static tag untouched +} + +TEST(TagKeeper, removeDynamicTagOnEmpty) { + CTagKeeper keeper; + EXPECT_FALSE(keeper.removeDynamicTag("myTag")); +} + +// --- getTags --- + +TEST(TagKeeper, getTagsReturnsAllStoredTags) { + CTagKeeper keeper; + keeper.applyTag("+a"); + keeper.applyTag("+b"); + keeper.applyTag("c", true); + + const auto& tags = keeper.getTags(); + EXPECT_EQ(tags.size(), 3u); + EXPECT_TRUE(tags.contains("a")); + EXPECT_TRUE(tags.contains("b")); + EXPECT_TRUE(tags.contains("c*")); +} diff --git a/tests/helpers/TransferFunction.cpp b/tests/helpers/TransferFunction.cpp new file mode 100644 index 000000000..8d5c21e3c --- /dev/null +++ b/tests/helpers/TransferFunction.cpp @@ -0,0 +1,43 @@ +#include + +#include + +using namespace NTransferFunction; + +TEST(Helpers, transferFunctionFromStringNamed) { + EXPECT_EQ(fromString("default"), TF_DEFAULT); + EXPECT_EQ(fromString("auto"), TF_AUTO); + EXPECT_EQ(fromString("srgb"), TF_SRGB); + EXPECT_EQ(fromString("gamma22"), TF_GAMMA22); + EXPECT_EQ(fromString("gamma22force"), TF_FORCED_GAMMA22); +} + +TEST(Helpers, transferFunctionFromStringNumeric) { + EXPECT_EQ(fromString("0"), TF_DEFAULT); + EXPECT_EQ(fromString("1"), TF_GAMMA22); + EXPECT_EQ(fromString("2"), TF_FORCED_GAMMA22); + EXPECT_EQ(fromString("3"), TF_SRGB); +} + +TEST(Helpers, transferFunctionFromStringInvalid) { + EXPECT_EQ(fromString(""), TF_DEFAULT); + EXPECT_EQ(fromString("invalid"), TF_DEFAULT); + EXPECT_EQ(fromString("SRGB"), TF_DEFAULT); + EXPECT_EQ(fromString("Gamma22"), TF_DEFAULT); +} + +TEST(Helpers, transferFunctionToString) { + EXPECT_FALSE(toString(TF_DEFAULT).empty()); + EXPECT_FALSE(toString(TF_AUTO).empty()); + EXPECT_FALSE(toString(TF_SRGB).empty()); + EXPECT_FALSE(toString(TF_GAMMA22).empty()); + EXPECT_FALSE(toString(TF_FORCED_GAMMA22).empty()); +} + +TEST(Helpers, transferFunctionRoundTrip) { + EXPECT_EQ(fromString(toString(TF_DEFAULT)), TF_DEFAULT); + EXPECT_EQ(fromString(toString(TF_AUTO)), TF_AUTO); + EXPECT_EQ(fromString(toString(TF_SRGB)), TF_SRGB); + EXPECT_EQ(fromString(toString(TF_GAMMA22)), TF_GAMMA22); + EXPECT_EQ(fromString(toString(TF_FORCED_GAMMA22)), TF_FORCED_GAMMA22); +}