diff --git a/.github/actions/setup_base/action.yml b/.github/actions/setup_base/action.yml index d7b52a79..9fcaabfb 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 \ @@ -45,6 +46,7 @@ runs: libxkbfile \ lld \ meson \ + muparser \ ninja \ pango \ pixman \ @@ -74,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 a0685fcf..6b89255a 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 fda97f57..75b4b7c5 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,6 +1,8 @@ diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d9c0d1a6..2ec558ed 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -21,7 +21,7 @@ jobs: - name: Build Hyprland run: | - CFLAGS=-Werror CXXFLAGS=-Werror make all + CFLAGS=-Werror CXXFLAGS=-Werror make nopch - name: Compress and package artifacts run: | @@ -41,86 +41,43 @@ jobs: name: Build archive path: Hyprland.tar.xz - meson: - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork - name: "Build Hyprland with Meson (Arch)" - runs-on: ubuntu-latest - container: - image: archlinux - steps: - - name: Checkout repository actions - uses: actions/checkout@v4 - with: - sparse-checkout: .github/actions - - - name: Setup base - uses: ./.github/actions/setup_base - - - name: Configure - run: meson setup build -Ddefault_library=static - - - name: Compile - run: ninja -C build - - no-pch: - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork - name: "Build Hyprland without precompiled headers (Arch)" - runs-on: ubuntu-latest - container: - image: archlinux - steps: - - name: Checkout repository actions - uses: actions/checkout@v4 - with: - sparse-checkout: .github/actions - - - name: Setup base - uses: ./.github/actions/setup_base - with: - INSTALL_XORG_PKGS: true - - - name: Compile - run: make nopch - - noxwayland: - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork - name: "Build Hyprland in pure Wayland (Arch)" - runs-on: ubuntu-latest - container: - image: archlinux - steps: - - name: Checkout repository actions - uses: actions/checkout@v4 - with: - sparse-checkout: .github/actions - - - name: Setup base - uses: ./.github/actions/setup_base - - - name: Configure - run: mkdir -p build && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DNO_XWAYLAND:STRING=true -H./ -B./build -G Ninja - - - name: Compile - run: make release - clang-format: permissions: read-all if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork - name: "Code Style (Arch)" + name: "Code Style" runs-on: ubuntu-latest container: image: archlinux steps: - - name: Checkout repository actions + - name: Checkout repository uses: actions/checkout@v4 - with: - sparse-checkout: .github/actions - - name: Setup base - uses: ./.github/actions/setup_base + # - name: clang-format check + # uses: jidicula/clang-format-action@v4.16.0 + # with: + # exclude-regex: ^subprojects$ - - name: Configure - run: meson setup build -Ddefault_library=static + - name: Install clang-format + run: | + pacman --noconfirm --noprogressbar -Syyu + pacman --noconfirm --noprogressbar -Sy clang - name: clang-format check - run: ninja -C build clang-format-check + run: .github/workflows/clang-format-check.sh "." "llvm" "^subprojects$" "" + + - name: Save PR head commit SHA + if: failure() && github.event_name == 'pull_request' + shell: bash + run: | + SHA="${{ github.event.pull_request.head.sha }}" + echo "SHA=$SHA" >> $GITHUB_ENV + - name: Save latest commit SHA if not PR + if: failure() && github.event_name != 'pull_request' + shell: bash + run: echo "SHA=${{ github.sha }}" >> $GITHUB_ENV + + - name: Report failure in job summary + if: failure() + run: | + DEEPLINK="${{ github.server_url }}/${{ github.repository }}/commit/${{ env.SHA }}" + echo -e "Format check failed on commit [${GITHUB_SHA:0:8}]($DEEPLINK) with files:\n$(<$GITHUB_WORKSPACE/failing-files.txt)" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/clang-format-check.sh b/.github/workflows/clang-format-check.sh new file mode 100755 index 00000000..41237aa7 --- /dev/null +++ b/.github/workflows/clang-format-check.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# +# Adapted from https://github.com/jidicula/clang-format-action + +############################################################################### +# check.sh # +############################################################################### +# USAGE: ./entrypoint.sh [] [] +# +# Checks all C/C++/Protobuf/CUDA files (.h, .H, .hpp, .hh, .h++, .hxx and .c, +# .C, .cpp, .cc, .c++, .cxx, .proto, .cu) in the provided GitHub repository path +# (arg1) for conforming to clang-format. If no path is provided or provided path +# is not a directory, all C/C++/Protobuf/CUDA files are checked. If any files +# are incorrectly formatted, the script lists them and exits with 1. +# +# Define your own formatting rules in a .clang-format file at your repository +# root. Otherwise, the provided style guide (arg2) is used as a fallback. + +# format_diff function +# Accepts a filepath argument. The filepath passed to this function must point +# to a C/C++/Protobuf/CUDA file. +format_diff() { + local filepath="$1" + + # Invoke clang-format with dry run and formatting error output + local_format="$(clang-format \ + --dry-run \ + --Werror \ + --style=file \ + --fallback-style="$FALLBACK_STYLE" \ + "${filepath}")" + + local format_status="$?" + if [[ ${format_status} -ne 0 ]]; then + # Append Markdown-bulleted monospaced filepath of failing file to + # summary file. + echo "* \`$filepath\`" >>failing-files.txt + + echo "Failed on file: $filepath" >&2 + echo "$local_format" >&2 + exit_code=1 # flip the global exit code + return "${format_status}" + fi + return 0 +} + +CHECK_PATH="$1" +FALLBACK_STYLE="$2" +EXCLUDE_REGEX="$3" +INCLUDE_REGEX="$4" + +# Set the regex to an empty string regex if nothing was provided +if [[ -z $EXCLUDE_REGEX ]]; then + EXCLUDE_REGEX="^$" +fi + +# Set the filetype regex if nothing was provided. +# Find all C/C++/Protobuf/CUDA files: +# h, H, hpp, hh, h++, hxx +# c, C, cpp, cc, c++, cxx +# ino, pde +# proto +# cu +if [[ -z $INCLUDE_REGEX ]]; then + INCLUDE_REGEX='^.*\.((((c|C)(c|pp|xx|\+\+)?$)|((h|H)h?(pp|xx|\+\+)?$))|(ino|pde|proto|cu))$' +fi + +cd "$GITHUB_WORKSPACE" || exit 2 + +if [[ ! -d $CHECK_PATH ]]; then + echo "Not a directory in the workspace, fallback to all files." >&2 + CHECK_PATH="." +fi + +# initialize exit code +exit_code=0 + +# All files improperly formatted will be printed to the output. +src_files=$(find "$CHECK_PATH" -name .git -prune -o -regextype posix-egrep -regex "$INCLUDE_REGEX" -print) + +# check formatting in each source file +IFS=$'\n' # Loop below should separate on new lines, not spaces. +for file in $src_files; do + # Only check formatting if the path doesn't match the regex + if ! [[ ${file} =~ $EXCLUDE_REGEX ]]; then + format_diff "${file}" + fi +done + +# global exit code is flipped to nonzero if any invocation of `format_diff` has +# a formatting difference. +exit "$exit_code" diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index e935a605..505829e3 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -4,43 +4,23 @@ jobs: clang-format: permissions: write-all if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork - name: "Code Style (Arch)" + name: "Code Style" runs-on: ubuntu-latest - container: - image: archlinux steps: - - name: Checkout repository actions + - name: Checkout repository uses: actions/checkout@v4 - with: - sparse-checkout: .github/actions - - - name: Setup base - uses: ./.github/actions/setup_base - - - name: Configure - run: meson setup build -Ddefault_library=static - name: clang-format check - run: ninja -C build clang-format-check + uses: jidicula/clang-format-action@v4.16.0 + with: + exclude-regex: ^subprojects$ - - name: clang-format apply - if: ${{ failure() && github.event_name == 'pull_request' }} - run: ninja -C build clang-format - - - name: Create patch + - 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), or directly apply this patch:' > clang-format.patch - echo '
' >> clang-format.patch - echo 'clang-format.patch' >> clang-format.patch - echo >> clang-format.patch - echo '```diff' >> clang-format.patch - git diff >> clang-format.patch - echo '```' >> clang-format.patch - echo >> clang-format.patch - echo '
' >> clang-format.patch + 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: Comment patch + - name: Post comment if: ${{ failure() && github.event_name == 'pull_request' }} uses: mshick/add-pr-comment@v2 with: diff --git a/.github/workflows/new-pr-comment.yml b/.github/workflows/new-pr-comment.yml new file mode 100644 index 00000000..36ea1909 --- /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 ae615057..5b22e992 100644 --- a/.github/workflows/nix-ci.yml +++ b/.github/workflows/nix-ci.yml @@ -25,6 +25,5 @@ jobs: 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 086f0077..68357093 100644 --- a/.github/workflows/nix-test.yml +++ b/.github/workflows/nix-test.yml @@ -20,25 +20,13 @@ jobs: - name: Restore and save Nix store uses: nix-community/cache-nix-action@v6 with: - # restore and save a cache using this key - primary-key: nix-${{ runner.os }} + # 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 - # 1G = 1073741824 gc-max-store-size-linux: 5G - # do purge caches - purge: true - # purge all versions of the cache - purge-prefixes: nix-${{ runner.os }} - # created more than this number of seconds ago - purge-created: 0 - # or, last accessed more than this number of seconds ago - # relative to the start of the `Post Restore and save Nix store` phase - purge-last-accessed: 0 - # except any version with the key that is the same as the `primary-key` - purge-primary-key: never - uses: cachix/cachix-action@v15 with: diff --git a/.github/workflows/nix-update-inputs.yml b/.github/workflows/nix-update-inputs.yml index c83e9880..a3084b27 100644 --- a/.github/workflows/nix-update-inputs.yml +++ b/.github/workflows/nix-update-inputs.yml @@ -27,25 +27,13 @@ jobs: - name: Restore and save Nix store uses: nix-community/cache-nix-action@v6 with: - # restore and save a cache using this key - primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} + # 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 }}- + 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 - # 1G = 1073741824 - gc-max-store-size-linux: 1G - # do purge caches - purge: true - # purge all versions of the cache - purge-prefixes: nix-${{ runner.os }}- - # created more than this number of seconds ago - purge-created: 0 - # or, last accessed more than this number of seconds ago - # relative to the start of the `Post Restore and save Nix store` phase - purge-last-accessed: 0 - # except any version with the key that is the same as the `primary-key` - purge-primary-key: never + gc-max-store-size-linux: 5G - name: Update inputs run: nix/update-inputs.sh diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 1d514c50..b46b3795 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -25,25 +25,13 @@ jobs: - name: Restore and save Nix store uses: nix-community/cache-nix-action@v6 with: - # restore and save a cache using this key - primary-key: nix-${{ runner.os }} + # 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 - # 1G = 1073741824 gc-max-store-size-linux: 5G - # do purge caches - purge: true - # purge all versions of the cache - purge-prefixes: nix-${{ runner.os }} - # created more than this number of seconds ago - purge-created: 0 - # or, last accessed more than this number of seconds ago - # relative to the start of the `Post Restore and save Nix store` phase - purge-last-accessed: 0 - # except any version with the key that is the same as the `primary-key` - purge-primary-key: never - uses: cachix/cachix-action@v15 with: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1288ae33..09aae111 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -8,20 +8,37 @@ on: jobs: source-tarball: runs-on: ubuntu-latest - container: - image: archlinux steps: - - name: Checkout repository actions - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v5 with: - sparse-checkout: .github/actions + fetch-depth: 0 + submodules: recursive - - name: Setup base - uses: ./.github/actions/setup_base - - - name: Generate version + - name: Populate git info in version.h.in run: | - cmake -S . -B /tmp/build + git fetch --tags --unshallow || true + + COMMIT_HASH=$(git rev-parse HEAD) + BRANCH="${GITHUB_REF_NAME:-$(git rev-parse --abbrev-ref HEAD)}" + COMMIT_MSG=$(git show -s --format=%s | sed 's/[&/]/\\&/g') + COMMIT_DATE=$(git show -s --format=%cd --date=local) + GIT_DIRTY=$(git diff-index --quiet HEAD -- && echo "clean" || echo "dirty") + GIT_TAG=$(git describe --tags --always || echo "unknown") + GIT_COMMITS=$(git rev-list --count HEAD) + + echo "Branch: $BRANCH" + echo "Tag: $GIT_TAG" + + sed -i \ + -e "s|@GIT_COMMIT_HASH@|$COMMIT_HASH|" \ + -e "s|@GIT_BRANCH@|$BRANCH|" \ + -e "s|@GIT_COMMIT_MESSAGE@|$COMMIT_MSG|" \ + -e "s|@GIT_COMMIT_DATE@|$COMMIT_DATE|" \ + -e "s|@GIT_DIRTY@|$GIT_DIRTY|" \ + -e "s|@GIT_TAG@|$GIT_TAG|" \ + -e "s|@GIT_COMMITS@|$GIT_COMMITS|" \ + src/version.h.in - name: Create tarball with submodules id: tar diff --git a/.github/workflows/translation-ai-check.yml b/.github/workflows/translation-ai-check.yml new file mode 100644 index 00000000..d6a62a60 --- /dev/null +++ b/.github/workflows/translation-ai-check.yml @@ -0,0 +1,139 @@ +name: AI Translation Check + +on: + # pull_request_target: + # types: + # - opened + issue_comment: + types: + - created + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + review: + name: Review Translation + if: ${{ github.event_name == 'pull_request_target' || (github.event_name == 'issue_comment' && github.event.action == 'created' && github.event.issue.pull_request != null && github.event.comment.user.login == 'vaxerski' && github.event.comment.body == 'ai, please recheck' ) }} + runs-on: ubuntu-latest + env: + OPENAI_MODEL: gpt-5-mini + SYSTEM_PROMPT: | + You are a programmer and a translator. Your job is to review the attached patch for adding translation to a piece of software and make sure the submitted translation is not malicious, and that it makes sense. If the translation is not malicious, and doesn't contain obvious grammatical mistakes, say "Translation check OK". Otherwise, say "Translation check not ok" and list bad entries. + Examples of bad translations include obvious trolling (slurs, etc) or nonsense sentences. Meaningful improvements may be suggested, but if there are only minor improvements, just reply with "Translation check OK". Do not provide anything but the result and (if applicable) the bad entries or improvements. + + AI_PROMPT: Translation patch below. + + steps: + - name: Checkout source code + uses: actions/checkout@v5 + + - uses: dorny/paths-filter@v3 + id: changes + with: + filters: | + i18n: + - 'src/i18n/**' + + - name: Stop if i18n not changed + if: steps.changes.outputs.i18n != 'true' + run: echo "No i18n changes in this PR; skipping." && exit 0 + + - name: Determine PR number + id: pr + run: | + if [ "${{ github.event_name }}" = "pull_request_target" ]; then + echo "number=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT" + else + echo "number=${{ github.event.issue.number }}" >> "$GITHUB_OUTPUT" + fi + + - name: Download combined PR diff + id: get_diff + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ steps.pr.outputs.number }} + run: | + # Get the combined diff for the entire PR + curl -sSL \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3.diff" \ + "https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUMBER" \ + -o pr.diff + + # Compute character length + LEN=$(wc -c < pr.diff | tr -d ' ') + echo "len=$LEN" >> "$GITHUB_OUTPUT" + if [ "$LEN" -gt 25000 ]; then + echo "too_long=true" >> "$GITHUB_OUTPUT" + else + echo "too_long=false" >> "$GITHUB_OUTPUT" + fi + + echo "got diff:" + cat pr.diff + + - name: Comment when diff length exceeded + if: steps.get_diff.outputs.too_long == 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ steps.pr.outputs.number }} + run: | + jq -n --arg body "Diff length exceeded, can't query API" '{body: ("AI translation check result:\n\n" + $body)}' > body.json + curl -sS -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Content-Type: application/json" \ + "https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUMBER/comments" \ + --data @body.json + + - name: Query OpenAI and post review + if: steps.get_diff.outputs.too_long == 'false' + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + OPENAI_MODEL: ${{ env.OPENAI_MODEL }} + SYSTEM_PROMPT: ${{ env.SYSTEM_PROMPT }} + AI_PROMPT: ${{ env.AI_PROMPT }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ steps.pr.outputs.number }} + run: | + # Prepare OpenAI chat request payload (embed diff safely) + jq -n \ + --arg model "$OPENAI_MODEL" \ + --arg sys "$SYSTEM_PROMPT" \ + --arg prompt "$AI_PROMPT" \ + --rawfile diff pr.diff \ + '{model:$model, + messages:[ + {role:"system", content:$sys}, + {role:"user", content: ($prompt + "\n\n```diff\n" + $diff + "\n```")} + ] + }' > payload.json + + # Call OpenAI + curl -sS https://api.openai.com/v1/chat/completions \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: application/json" \ + -d @payload.json > response.json + + # Extract response text + COMMENT=$(jq -r '.choices[0].message.content // empty' response.json) + if [ -z "$COMMENT" ]; then + COMMENT="AI did not return a response." + fi + + # If failed, add a note + ADDITIONAL_NOTE="" + if [[ "$COMMENT" == *"not ok"* ]]; then + ADDITIONAL_NOTE=$(echo -ne "\n\nPlease note this check is a guideline, not a hard requirement. It is here to help you translate. If you disagree with some points, just state that. Any typos should be fixed.") + fi + + # Post the review as a PR comment + jq -n --arg body "$COMMENT" --arg note "$ADDITIONAL_NOTE" '{body: ("AI translation check result:\n\n" + $body + $note)}' > body.json + echo "CURLing https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUMBER/comments" + curl -sS -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Content-Type: application/json" \ + "https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUMBER/comments" \ + --data @body.json diff --git a/.gitignore b/.gitignore index 4ced1678..4e5c2323 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,8 @@ src/render/shaders/*.inc src/render/shaders/Shaders.hpp hyprctl/hyprctl +hyprctl/hw-protocols/*.c* +hyprctl/hw-protocols/*.h* gmon.out *.out @@ -42,6 +44,7 @@ PKGBUILD src/version.h hyprpm/Makefile hyprctl/Makefile +example/hyprland.desktop **/.#*.* **/#*.*# diff --git a/CMakeLists.txt b/CMakeLists.txt index 29de4a32..87574b82 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) @@ -33,11 +39,23 @@ find_package(PkgConfig REQUIRED) # provide a .pc file and won't be detected this way pkg_check_modules(udis_dep IMPORTED_TARGET udis86>=1.7.2) -# Fallback to subproject +# Find non-pkgconfig udis86, otherwise fallback to subproject if(NOT udis_dep_FOUND) - add_subdirectory("subprojects/udis86") - include_directories("subprojects/udis86") - message(STATUS "udis86 dependency not found, falling back to subproject") + find_library(udis_nopc udis86) + if(NOT("${udis_nopc}" MATCHES "udis_nopc-NOTFOUND")) + message(STATUS "Found udis86 at ${udis_nopc}") + else() + add_subdirectory("subprojects/udis86") + include_directories("subprojects/udis86") + message(STATUS "udis86 dependency not found, falling back to subproject") + endif() +endif() + +find_library(librt rt) +if("${librt}" MATCHES "librt-NOTFOUND") + unset(LIBRT) +else() + set(LIBRT rt) endif() if(CMAKE_BUILD_TYPE) @@ -68,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}") @@ -90,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 @@ -104,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.8.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.0) +set(HYPRGRAPHICS_MINIMUM_VERSION 0.1.6) + +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) @@ -128,13 +156,41 @@ set(HYPRGRAPHICS_VERSION "${hyprgraphics_dep_VERSION}") find_package(Git QUIET) -set(GIT_COMMIT_HASH "unknown") -set(GIT_BRANCH "unknown") -set(GIT_COMMIT_MESSAGE "unknown") -set(GIT_COMMIT_DATE "unknown") -set(GIT_DIRTY "unknown") -set(GIT_TAG "unknown") -set(GIT_COMMITS "0") +# Populate variables with env vars if present +set(GIT_COMMIT_HASH "$ENV{GIT_COMMIT_HASH}") +if(NOT GIT_COMMIT_HASH) + set(GIT_COMMIT_HASH "unknown") +endif() + +set(GIT_BRANCH "$ENV{GIT_BRANCH}") +if(NOT GIT_BRANCH) + set(GIT_BRANCH "unknown") +endif() + +set(GIT_COMMIT_MESSAGE "$ENV{GIT_COMMIT_MESSAGE}") +if(NOT GIT_COMMIT_MESSAGE) + set(GIT_COMMIT_MESSAGE "unknown") +endif() + +set(GIT_COMMIT_DATE "$ENV{GIT_COMMIT_DATE}") +if(NOT GIT_COMMIT_DATE) + set(GIT_COMMIT_DATE "unknown") +endif() + +set(GIT_DIRTY "$ENV{GIT_DIRTY}") +if(NOT GIT_DIRTY) + set(GIT_DIRTY "unknown") +endif() + +set(GIT_TAG "$ENV{GIT_TAG}") +if(NOT GIT_TAG) + set(GIT_TAG "unknown") +endif() + +set(GIT_COMMITS "$ENV{GIT_COMMITS}") +if(NOT GIT_COMMITS) + set(GIT_COMMITS "0") +endif() if(Git_FOUND) execute_process( @@ -155,10 +211,10 @@ if(Git_FOUND) execute_process(COMMAND ${GIT_EXECUTABLE} branch --show-current WORKING_DIRECTORY ${GIT_TOPLEVEL} OUTPUT_VARIABLE GIT_BRANCH OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${GIT_EXECUTABLE} show -s --format=%s + execute_process(COMMAND sh "-c" "${GIT_EXECUTABLE} show -s --format=%s --no-show-signature | sed \"s/\\\"/\'/g\"" WORKING_DIRECTORY ${GIT_TOPLEVEL} OUTPUT_VARIABLE GIT_COMMIT_MESSAGE OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${GIT_EXECUTABLE} show -s --format=%cd --date=local + execute_process(COMMAND ${GIT_EXECUTABLE} show -s --format=%cd --date=local --no-show-signature WORKING_DIRECTORY ${GIT_TOPLEVEL} OUTPUT_VARIABLE GIT_COMMIT_DATE OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND ${GIT_EXECUTABLE} diff-index --quiet HEAD -- @@ -188,28 +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.45) +set(LIBINPUT_MINIMUM_VERSION 1.28) + pkg_check_modules( deps REQUIRED - IMPORTED_TARGET - xkbcommon + 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) + re2 + 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) @@ -217,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) @@ -227,23 +297,8 @@ 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) - 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() + target_link_libraries(hyprland_lib PUBLIC asan) + target_compile_options(hyprland_lib PUBLIC -fsanitize=address) endif() add_compile_options(-fno-pie -fno-builtin) @@ -254,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") @@ -263,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) @@ -283,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 @@ -294,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() @@ -321,29 +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 - rt + 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_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) @@ -357,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( @@ -370,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) @@ -403,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) @@ -452,11 +558,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") @@ -475,6 +584,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) @@ -483,7 +597,6 @@ add_compile_definitions(DATAROOTDIR="${CMAKE_INSTALL_FULL_DATAROOTDIR}") # installable assets file(GLOB_RECURSE INSTALLABLE_ASSETS "assets/install/*") -list(FILTER INSTALLABLE_ASSETS EXCLUDE REGEX "meson.build") install(FILES ${INSTALLABLE_ASSETS} DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/hypr) @@ -521,10 +634,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) @@ -533,12 +673,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 e881cf92..efdec21a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2022-2025, vaxerski +Copyright (c) 2022-2026, vaxerski All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/Makefile b/Makefile index 282258ed..852fcddf 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ nopch: clear: rm -rf build rm -f ./protocols/*.h ./protocols/*.c ./protocols/*.cpp ./protocols/*.hpp + rm -f ./hyprctl/hw-protocols/*.cpp ./hyprctl/hw-protocols/*.hpp all: $(MAKE) clear @@ -87,7 +88,7 @@ asan: @echo "Wayland done" patch -p1 < ./scripts/hyprlandStaticAsan.diff - cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DWITH_ASAN:STRING=True -DUSE_TRACY:STRING=False -DUSE_TRACY_GPU:STRING=False -S . -B ./build -G Ninja + cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DWITH_ASAN:STRING=True -DUSE_TRACY:STRING=False -DUSE_TRACY_GPU:STRING=False -S . -B ./build cmake --build ./build --config Debug --target all @echo "Hyprland done" diff --git a/VERSION b/VERSION index 4f9b378b..524456c7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.52.0 +0.54.0 diff --git a/assets/install/meson.build b/assets/install/meson.build deleted file mode 100644 index 45076469..00000000 --- a/assets/install/meson.build +++ /dev/null @@ -1,10 +0,0 @@ -globber = run_command('sh', '-c', 'find . -type f -not -name "*.build"', check: true) -files = globber.stdout().strip().split('\n') - -foreach file : files - install_data( - file, - install_dir: join_paths(get_option('datadir'), 'hypr'), - install_tag: 'runtime', - ) -endforeach diff --git a/assets/meson.build b/assets/meson.build deleted file mode 100644 index 2a28121d..00000000 --- a/assets/meson.build +++ /dev/null @@ -1,7 +0,0 @@ -install_data( - 'hyprland-portals.conf', - install_dir: join_paths(get_option('datadir'), 'xdg-desktop-portal'), - install_tag: 'runtime', -) - -subdir('install') diff --git a/docs/meson.build b/docs/meson.build deleted file mode 100644 index 6ff51d1a..00000000 --- a/docs/meson.build +++ /dev/null @@ -1,2 +0,0 @@ -install_man('Hyprland.1') -install_man('hyprctl.1') diff --git a/example/hyprland.conf b/example/hyprland.conf index a1408dc3..15b0e4f7 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 ################# @@ -159,10 +159,23 @@ animations { # uncomment all if you wish to use that. # workspace = w[tv1], gapsout:0, gapsin:0 # workspace = f[1], gapsout:0, gapsin:0 -# windowrule = bordersize 0, floating:0, onworkspace:w[tv1] -# windowrule = rounding 0, floating:0, onworkspace:w[tv1] -# windowrule = bordersize 0, floating:0, onworkspace:f[1] -# windowrule = rounding 0, floating:0, onworkspace:f[1] +# windowrule { +# name = no-gaps-wtv1 +# match:float = false +# match:workspace = w[tv1] +# +# border_size = 0 +# rounding = 0 +# } +# +# windowrule { +# name = no-gaps-f1 +# match:float = false +# match:workspace = f[1] +# +# border_size = 0 +# rounding = 0 +# } # See https://wiki.hypr.land/Configuring/Dwindle-Layout/ for more dwindle { @@ -224,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 @@ -294,11 +307,35 @@ bindl = , XF86AudioPrev, exec, playerctl previous # See https://wiki.hypr.land/Configuring/Window-Rules/ for more # See https://wiki.hypr.land/Configuring/Workspace-Rules/ for workspace rules -# Example windowrule -# windowrule = float,class:^(kitty)$,title:^(kitty)$ +# Example windowrules that are useful -# Ignore maximize requests from apps. You'll probably like this. -windowrule = suppressevent maximize, class:.* +windowrule { + # Ignore maximize requests from all apps. You'll probably like this. + name = suppress-maximize-events + match:class = .* -# Fix some dragging issues with XWayland -windowrule = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0 + suppress_event = maximize +} + +windowrule { + # Fix some dragging issues with XWayland + name = fix-xwayland-drags + match:class = ^$ + match:title = ^$ + match:xwayland = true + match:float = true + match:fullscreen = false + match:pin = false + + 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 bb2801a9..d8e87d60 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/example/meson.build b/example/meson.build deleted file mode 100644 index a338644e..00000000 --- a/example/meson.build +++ /dev/null @@ -1,10 +0,0 @@ -install_data( - 'hyprland.conf', - install_dir: join_paths(get_option('datadir'), 'hypr'), - install_tag: 'runtime', -) -install_data( - 'hyprland.desktop', - install_dir: join_paths(get_option('datadir'), 'wayland-sessions'), - install_tag: 'runtime', -) diff --git a/flake.lock b/flake.lock index bd87048c..4a89c0fc 100644 --- a/flake.lock +++ b/flake.lock @@ -16,11 +16,11 @@ ] }, "locked": { - "lastModified": 1762356719, - "narHash": "sha256-qwd/xdoOya1m8FENle+4hWnydCtlXUWLAW/Auk6WL7s=", + "lastModified": 1772292445, + "narHash": "sha256-4F1Q7U313TKUDDovCC96m/Za4wZcJ3yqtu4eSrj8lk8=", "owner": "hyprwm", "repo": "aquamarine", - "rev": "6d0b3567584691bf9d8fedb5d0093309e2f979c7", + "rev": "1dbbba659c1cef0b0202ce92cadfe13bae550e8f", "type": "github" }, "original": { @@ -32,15 +32,15 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1747046372, - "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", - "owner": "edolstra", + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", + "owner": "NixOS", "repo": "flake-compat", - "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", "type": "github" }, "original": { - "owner": "edolstra", + "owner": "NixOS", "repo": "flake-compat", "type": "github" } @@ -105,11 +105,11 @@ ] }, "locked": { - "lastModified": 1762462052, - "narHash": "sha256-6roLYzcDf4V38RUMSqycsOwAnqfodL6BmhRkUtwIgdA=", + "lastModified": 1770511807, + "narHash": "sha256-suKmSbSk34uPOJDTg/GbPrKEJutzK08vj0VoTvAFBCA=", "owner": "hyprwm", "repo": "hyprgraphics", - "rev": "ffc999d980c7b3bca85d3ebd0a9fbadf984a8162", + "rev": "7c75487edd43a71b61adb01cae8326d277aab683", "type": "github" }, "original": { @@ -133,6 +133,9 @@ "hyprutils": [ "hyprutils" ], + "hyprwayland-scanner": [ + "hyprwayland-scanner" + ], "nixpkgs": [ "nixpkgs" ], @@ -141,11 +144,11 @@ ] }, "locked": { - "lastModified": 1762465111, - "narHash": "sha256-dS13YZdWjgGGLBjpT4FHB6xf8I/WiAU+mgNWXsZgDUs=", + "lastModified": 1767023960, + "narHash": "sha256-R2HgtVS1G3KSIKAQ77aOZ+Q0HituOmPgXW9nBNkpp3Q=", "owner": "hyprwm", "repo": "hyprland-guiutils", - "rev": "a415eba866a953f3096d661318f771aa0082eb98", + "rev": "c2e906261142f5dd1ee0bfc44abba23e2754c660", "type": "github" }, "original": { @@ -164,11 +167,11 @@ ] }, "locked": { - "lastModified": 1759610243, - "narHash": "sha256-+KEVnKBe8wz+a6dTLq8YDcF3UrhQElwsYJaVaHXJtoI=", + "lastModified": 1765214753, + "narHash": "sha256-P9zdGXOzToJJgu5sVjv7oeOGPIIwrd9hAUAP3PsmBBs=", "owner": "hyprwm", "repo": "hyprland-protocols", - "rev": "bd153e76f751f150a09328dbdeb5e4fab9d23622", + "rev": "3f3860b869014c00e8b9e0528c7b4ddc335c21ab", "type": "github" }, "original": { @@ -190,11 +193,11 @@ ] }, "locked": { - "lastModified": 1758927902, - "narHash": "sha256-LZgMds7M94+vuMql2bERQ6LiFFdhgsEFezE4Vn+Ys3A=", + "lastModified": 1771866172, + "narHash": "sha256-fYFoXhQLrm1rD8vSFKQBOEX4OGCuJdLt1amKfHd5GAw=", "owner": "hyprwm", "repo": "hyprlang", - "rev": "4dafa28d4f79877d67a7d1a654cddccf8ebf15da", + "rev": "0b219224910e7642eb0ed49f0db5ec3d008e3e41", "type": "github" }, "original": { @@ -221,7 +224,10 @@ "hyprland-guiutils", "hyprutils" ], - "hyprwayland-scanner": "hyprwayland-scanner", + "hyprwayland-scanner": [ + "hyprland-guiutils", + "hyprwayland-scanner" + ], "nixpkgs": [ "hyprland-guiutils", "nixpkgs" @@ -232,11 +238,11 @@ ] }, "locked": { - "lastModified": 1762463729, - "narHash": "sha256-2fYkU/mdz8WKY3dkDPlE/j6hTxIwqultsx4gMMsMns0=", + "lastModified": 1764592794, + "narHash": "sha256-7CcO+wbTJ1L1NBQHierHzheQGPWwkIQug/w+fhTAVuU=", "owner": "hyprwm", "repo": "hyprtoolkit", - "rev": "88483bdee5329ec985f0c8f834c519cd18cfe532", + "rev": "5cfe0743f0e608e1462972303778d8a0859ee63e", "type": "github" }, "original": { @@ -255,11 +261,11 @@ ] }, "locked": { - "lastModified": 1762387740, - "narHash": "sha256-gQ9zJ+pUI4o+Gh4Z6jhJll7jjCSwi8ZqJIhCE2oqwhQ=", + "lastModified": 1771271487, + "narHash": "sha256-41gEiUS0Pyw3L/ge1l8MXn61cK14VAhgWB/JV8s/oNI=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "926689ddb9c0a8787e58c02c765a62e32d63d1f7", + "rev": "340a792e3b3d482c4ae5f66d27a9096bdee6d76d", "type": "github" }, "original": { @@ -271,22 +277,18 @@ "hyprwayland-scanner": { "inputs": { "nixpkgs": [ - "hyprland-guiutils", - "hyprtoolkit", "nixpkgs" ], "systems": [ - "hyprland-guiutils", - "hyprtoolkit", "systems" ] }, "locked": { - "lastModified": 1755184602, - "narHash": "sha256-RCBQN8xuADB0LEgaKbfRqwm6CdyopE1xIEhNc67FAbw=", + "lastModified": 1770501770, + "narHash": "sha256-NWRM6+YxTRv+bT9yvlhhJ2iLae1B1pNH3mAL5wi2rlQ=", "owner": "hyprwm", "repo": "hyprwayland-scanner", - "rev": "b3b0f1f40ae09d4447c20608e5a4faf8bf3c492d", + "rev": "0bd8b6cde9ec27d48aad9e5b4deefb3746909d40", "type": "github" }, "original": { @@ -295,8 +297,11 @@ "type": "github" } }, - "hyprwayland-scanner_2": { + "hyprwire": { "inputs": { + "hyprutils": [ + "hyprutils" + ], "nixpkgs": [ "nixpkgs" ], @@ -305,26 +310,26 @@ ] }, "locked": { - "lastModified": 1755184602, - "narHash": "sha256-RCBQN8xuADB0LEgaKbfRqwm6CdyopE1xIEhNc67FAbw=", + "lastModified": 1771606233, + "narHash": "sha256-F3PLUqQ/TwgR70U+UeOqJnihJZ2EuunzojYC4g5xHr0=", "owner": "hyprwm", - "repo": "hyprwayland-scanner", - "rev": "b3b0f1f40ae09d4447c20608e5a4faf8bf3c492d", + "repo": "hyprwire", + "rev": "06c7f1f8c4194786c8400653c4efc49dc14c0f3a", "type": "github" }, "original": { "owner": "hyprwm", - "repo": "hyprwayland-scanner", + "repo": "hyprwire", "type": "github" } }, "nixpkgs": { "locked": { - "lastModified": 1762363567, - "narHash": "sha256-YRqMDEtSMbitIMj+JLpheSz0pwEr0Rmy5mC7myl17xs=", + "lastModified": 1772198003, + "narHash": "sha256-I45esRSssFtJ8p/gLHUZ1OUaaTaVLluNkABkk6arQwE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ae814fd3904b621d8ab97418f1d0f2eb0d3716f4", + "rev": "dd9b079222d43e1943b6ebd802f04fd959dc8e61", "type": "github" }, "original": { @@ -343,11 +348,11 @@ ] }, "locked": { - "lastModified": 1762441963, - "narHash": "sha256-j+rNQ119ffYUkYt2YYS6rnd6Jh/crMZmbqpkGLXaEt0=", + "lastModified": 1772024342, + "narHash": "sha256-+eXlIc4/7dE6EcPs9a2DaSY3fTA9AE526hGqkNID3Wg=", "owner": "cachix", "repo": "git-hooks.nix", - "rev": "8e7576e79b88c16d7ee3bbd112c8d90070832885", + "rev": "6e34e97ed9788b17796ee43ccdbaf871a5c2b476", "type": "github" }, "original": { @@ -365,7 +370,8 @@ "hyprland-protocols": "hyprland-protocols", "hyprlang": "hyprlang", "hyprutils": "hyprutils", - "hyprwayland-scanner": "hyprwayland-scanner_2", + "hyprwayland-scanner": "hyprwayland-scanner", + "hyprwire": "hyprwire", "nixpkgs": "nixpkgs", "pre-commit-hooks": "pre-commit-hooks", "systems": "systems", diff --git a/flake.nix b/flake.nix index cfc7cb19..6d695bfb 100644 --- a/flake.nix +++ b/flake.nix @@ -43,6 +43,7 @@ inputs.hyprgraphics.follows = "hyprgraphics"; inputs.hyprutils.follows = "hyprutils"; inputs.hyprlang.follows = "hyprlang"; + inputs.hyprwayland-scanner.follows = "hyprwayland-scanner"; }; hyprlang = { @@ -64,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"; @@ -80,108 +88,122 @@ }; }; - outputs = inputs @ { - self, - nixpkgs, - systems, - ... - }: let - inherit (nixpkgs) lib; - eachSystem = lib.genAttrs (import systems); - pkgsFor = eachSystem (system: - import nixpkgs { - localSystem = system; - overlays = with self.overlays; [ - hyprland-packages - hyprland-extras - ]; - }); - pkgsCrossFor = eachSystem (system: crossSystem: - import nixpkgs { - localSystem = system; - inherit crossSystem; - overlays = with self.overlays; [ - hyprland-packages - hyprland-extras - ]; - }); - pkgsDebugFor = eachSystem (system: - import nixpkgs { - localSystem = system; - overlays = with self.overlays; [ - hyprland-debug - ]; - }); - pkgsDebugCrossFor = eachSystem (system: crossSystem: - import nixpkgs { - localSystem = system; - inherit crossSystem; - overlays = with self.overlays; [ - hyprland-debug - ]; - }); - in { - overlays = import ./nix/overlays.nix {inherit self lib inputs;}; + outputs = + inputs@{ + self, + nixpkgs, + systems, + ... + }: + let + inherit (nixpkgs) lib; + eachSystem = lib.genAttrs (import systems); + pkgsFor = eachSystem ( + system: + import nixpkgs { + localSystem = system; + overlays = with self.overlays; [ + hyprland-packages + hyprland-extras + ]; + } + ); + pkgsCrossFor = eachSystem ( + system: crossSystem: + import nixpkgs { + localSystem = system; + inherit crossSystem; + overlays = with self.overlays; [ + hyprland-packages + hyprland-extras + ]; + } + ); + pkgsDebugFor = eachSystem ( + system: + import nixpkgs { + localSystem = system; + overlays = with self.overlays; [ + hyprland-debug + ]; + } + ); + pkgsDebugCrossFor = eachSystem ( + system: crossSystem: + import nixpkgs { + localSystem = system; + inherit crossSystem; + overlays = with self.overlays; [ + hyprland-debug + ]; + } + ); + in + { + overlays = import ./nix/overlays.nix { inherit self lib inputs; }; - checks = eachSystem (system: - (lib.filterAttrs - (n: _: (lib.hasPrefix "hyprland" n) && !(lib.hasSuffix "debug" n)) - self.packages.${system}) - // { - inherit (self.packages.${system}) xdg-desktop-portal-hyprland; - pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run { - src = ./.; - hooks = { - hyprland-treewide-formatter = { - enable = true; - entry = "${self.formatter.${system}}/bin/hyprland-treewide-formatter"; - pass_filenames = false; - excludes = ["subprojects"]; - always_run = true; + checks = eachSystem ( + system: + (lib.filterAttrs ( + n: _: (lib.hasPrefix "hyprland" n) && !(lib.hasSuffix "debug" n) + ) self.packages.${system}) + // { + inherit (self.packages.${system}) xdg-desktop-portal-hyprland; + pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run { + src = ./.; + hooks = { + hyprland-treewide-formatter = { + enable = true; + entry = "${self.formatter.${system}}/bin/hyprland-treewide-formatter"; + pass_filenames = false; + excludes = [ "subprojects" ]; + always_run = true; + }; }; }; - }; - } - // (import ./nix/tests inputs pkgsFor.${system})); + } + // (import ./nix/tests inputs pkgsFor.${system}) + ); - packages = eachSystem (system: { - default = self.packages.${system}.hyprland; - inherit - (pkgsFor.${system}) - # hyprland-packages - hyprland - hyprland-with-hyprtester - 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 db5ef615..7071ede9 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 00000000..3d26a102 --- /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/meson.build b/hyprctl/meson.build deleted file mode 100644 index d6769b84..00000000 --- a/hyprctl/meson.build +++ /dev/null @@ -1,27 +0,0 @@ -executable( - 'hyprctl', - 'main.cpp', - dependencies: [ - dependency('hyprutils', version: '>= 0.1.1'), - dependency('re2', required: true) - ], - install: true, -) - -install_data( - 'hyprctl.bash', - install_dir: join_paths(get_option('datadir'), 'bash-completion/completions'), - install_tag: 'runtime', - rename: 'hyprctl', -) -install_data( - 'hyprctl.fish', - install_dir: join_paths(get_option('datadir'), 'fish/vendor_completions.d'), - install_tag: 'runtime', -) -install_data( - 'hyprctl.zsh', - install_dir: join_paths(get_option('datadir'), 'zsh/site-functions'), - install_tag: 'runtime', - rename: '_hyprctl', -) 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 67e4f992..549d84bb 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 00000000..1d3a9e07 --- /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 00000000..93f4182a --- /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 00000000..167b0a8d --- /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 e15a17f5..0a33f3ed 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 81a0ef11..bf5764f3 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@ +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 diff --git a/hyprpm/CMakeLists.txt b/hyprpm/CMakeLists.txt index f2e0b223..7d5b8eda 100644 --- a/hyprpm/CMakeLists.txt +++ b/hyprpm/CMakeLists.txt @@ -11,9 +11,9 @@ set(CMAKE_CXX_STANDARD 23) pkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.7.0) -find_package(glaze QUIET) +find_package(glaze 6.0.1 QUIET) if (NOT glaze_FOUND) - set(GLAZE_VERSION v5.1.1) + set(GLAZE_VERSION v6.0.1) message(STATUS "glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent") include(FetchContent) FetchContent_Declare( @@ -21,6 +21,7 @@ if (NOT glaze_FOUND) GIT_REPOSITORY https://github.com/stephenberry/glaze.git GIT_TAG ${GLAZE_VERSION} GIT_SHALLOW TRUE + EXCLUDE_FROM_ALL ) FetchContent_MakeAvailable(glaze) endif() diff --git a/hyprpm/src/core/DataState.cpp b/hyprpm/src/core/DataState.cpp index 131146a1..64f3cfa0 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 c35ded06..d9872b90 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 00000000..3aaa8592 --- /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 e66031c9..a8c74084 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 9dea8bf4..6621a49f 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 e0ed1203..25878f54 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 c18d4748..e9dd4c85 100644 --- a/hyprpm/src/helpers/Sys.cpp +++ b/hyprpm/src/helpers/Sys.cpp @@ -35,9 +35,13 @@ static std::string validSubinsAsStr() { } static bool executableExistsInPath(const std::string& exe) { + return NSys::findInPath(exe).has_value(); +} + +std::optional NSys::findInPath(const std::string& exe) { const char* PATHENV = std::getenv("PATH"); if (!PATHENV) - return false; + return std::nullopt; CVarList paths(PATHENV, 0, ':', true); std::error_code ec; @@ -52,10 +56,10 @@ static bool executableExistsInPath(const std::string& exe) { if (ec) continue; if ((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none) - return true; + return candidate.string(); } - return false; + return std::nullopt; } static std::string subin() { diff --git a/hyprpm/src/helpers/Sys.hpp b/hyprpm/src/helpers/Sys.hpp index b44eb758..03d5a0de 100644 --- a/hyprpm/src/helpers/Sys.hpp +++ b/hyprpm/src/helpers/Sys.hpp @@ -1,11 +1,13 @@ #pragma once #include +#include namespace NSys { - bool isSuperuser(); - int getUID(); - int getEUID(); + bool isSuperuser(); + int getUID(); + int getEUID(); + std::optional findInPath(const std::string& exe); // NOLINTNEXTLINE namespace root { @@ -20,4 +22,4 @@ namespace NSys { // Do not use this unless absolutely necessary! std::string runAsSuperuserUnsafe(const std::string& cmd); }; -}; \ No newline at end of file +}; diff --git a/hyprpm/src/main.cpp b/hyprpm/src/main.cpp index 47a557e2..f5e14bbb 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 for important events (including both successes and fail events). -┣ --notify-fail | -nn → Send a hyprland notification for fail events only. -┣ --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, notifyFail = 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) { @@ -58,9 +59,13 @@ int main(int argc, char** argv, char** envp) { } else if (ARGS[i] == "--notify" || ARGS[i] == "-n") { notify = true; } else if (ARGS[i] == "--notify-fail" || ARGS[i] == "-nn") { - notifyFail = notify = true; + // TODO: Deprecated since v.053.0. Remove in version>0.56.0 + std::println(stderr, "{}", failureString("Deprececated flag.")); + 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") { @@ -89,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) { @@ -104,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; } @@ -124,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(); }); @@ -135,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); @@ -149,15 +156,16 @@ int main(int argc, char** argv, char** envp) { if (ret2 != LOADSTATE_OK) return 1; - } else if (notify) + } else { g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Couldn't update headers"); + } } else if (command[0] == "enable") { if (command.size() < 2) { std::println(stderr, "{}", failureString("Not enough args for enable.")); 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; } @@ -178,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; } @@ -194,19 +202,17 @@ int main(int argc, char** argv, char** envp) { auto ret = g_pPluginManager->ensurePluginsLoadState(force); if (ret != LOADSTATE_OK) { - if (notify) { - switch (ret) { - case LOADSTATE_FAIL: - case LOADSTATE_PARTIAL_FAIL: g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins"); break; - case LOADSTATE_HEADERS_OUTDATED: - g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins: Outdated headers. Please run hyprpm update manually."); - break; - default: break; - } + switch (ret) { + case LOADSTATE_FAIL: + case LOADSTATE_PARTIAL_FAIL: g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins"); break; + case LOADSTATE_HEADERS_OUTDATED: + g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins: Outdated headers. Please run hyprpm update manually."); + break; + default: break; } return 1; - } else if (notify && !notifyFail) { + } else if (notify) { g_pPluginManager->notify(ICON_OK, 0, 4000, "[hyprpm] Loaded plugins"); } } else if (command[0] == "purge-cache") { diff --git a/hyprpm/src/meson.build b/hyprpm/src/meson.build deleted file mode 100644 index fd914f9d..00000000 --- a/hyprpm/src/meson.build +++ /dev/null @@ -1,32 +0,0 @@ -globber = run_command('sh', '-c', 'find . -name "*.cpp" | sort', check: true) -src = globber.stdout().strip().split('\n') - -executable( - 'hyprpm', - src, - dependencies: [ - dependency('hyprutils', version: '>= 0.1.1'), - dependency('threads'), - dependency('tomlplusplus'), - dependency('glaze', method: 'cmake'), - ], - install: true, -) - -install_data( - '../hyprpm.bash', - install_dir: join_paths(get_option('datadir'), 'bash-completion/completions'), - install_tag: 'runtime', - rename: 'hyprpm', -) -install_data( - '../hyprpm.fish', - install_dir: join_paths(get_option('datadir'), 'fish/vendor_completions.d'), - install_tag: 'runtime', -) -install_data( - '../hyprpm.zsh', - install_dir: join_paths(get_option('datadir'), 'zsh/site-functions'), - install_tag: 'runtime', - rename: '_hyprpm', -) diff --git a/hyprtester/CMakeLists.txt b/hyprtester/CMakeLists.txt index 0b445b0a..f17f73b1 100644 --- a/hyprtester/CMakeLists.txt +++ b/hyprtester/CMakeLists.txt @@ -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 00000000..5f66be6b --- /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 140e4700..59120961 100644 --- a/hyprtester/clients/pointer-scroll.cpp +++ b/hyprtester/clients/pointer-scroll.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include diff --git a/hyprtester/clients/pointer-warp.cpp b/hyprtester/clients/pointer-warp.cpp index 2d3624d5..a57f99ae 100644 --- a/hyprtester/clients/pointer-warp.cpp +++ b/hyprtester/clients/pointer-warp.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include diff --git a/hyprtester/clients/shortcut-inhibitor.cpp b/hyprtester/clients/shortcut-inhibitor.cpp new file mode 100644 index 00000000..0c6b4341 --- /dev/null +++ b/hyprtester/clients/shortcut-inhibitor.cpp @@ -0,0 +1,297 @@ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +using Hyprutils::Math::Vector2D; +using namespace Hyprutils::Memory; + +struct SWlState { + wl_display* display; + CSharedPointer registry; + + // protocols + CSharedPointer wlCompositor; + CSharedPointer wlSeat; + CSharedPointer wlShm; + CSharedPointer xdgShell; + CSharedPointer inhibitManager; + + // shm/buffer stuff + CSharedPointer shmPool; + CSharedPointer shmBuf; + int shmFd; + size_t shmBufSize; + bool xrgb8888_support = false; + + // surface/toplevel stuff + CSharedPointer surf; + CSharedPointer xdgSurf; + CSharedPointer xdgToplevel; + Vector2D geom; + + // pointer + CSharedPointer pointer; + uint32_t enterSerial; + + // shortcut inhibiting + CSharedPointer inhibitor; +}; + +static bool debug, started, shouldExit; + +template +//NOLINTNEXTLINE +static void clientLog(std::format_string fmt, Args&&... args) { + std::string text = std::vformat(fmt.get(), std::make_format_args(args...)); + std::println("{}", text); + std::fflush(stdout); +} + +template +//NOLINTNEXTLINE +static void debugLog(std::format_string fmt, Args&&... args) { + std::string text = std::vformat(fmt.get(), std::make_format_args(args...)); + if (!debug) + return; + std::println("{}", text); + std::fflush(stdout); +} + +static bool bindRegistry(SWlState& state) { + state.registry = makeShared((wl_proxy*)wl_display_get_registry(state.display)); + + state.registry->setGlobal([&](CCWlRegistry* r, uint32_t id, const char* name, uint32_t version) { + const std::string NAME = name; + if (NAME == "wl_compositor") { + debugLog(" > binding to global: {} (version {}) with id {}", name, version, id); + state.wlCompositor = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_compositor_interface, 6)); + } else if (NAME == "wl_shm") { + debugLog(" > binding to global: {} (version {}) with id {}", name, version, id); + state.wlShm = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_shm_interface, 1)); + } else if (NAME == "wl_seat") { + debugLog(" > binding to global: {} (version {}) with id {}", name, version, id); + state.wlSeat = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_seat_interface, 9)); + } else if (NAME == "xdg_wm_base") { + debugLog(" > binding to global: {} (version {}) with id {}", name, version, id); + state.xdgShell = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &xdg_wm_base_interface, 1)); + } else if (NAME == "zwp_keyboard_shortcuts_inhibit_manager_v1") { + debugLog(" > binding to global: {} (version {}) with id {}", name, version, id); + state.inhibitManager = makeShared( + (wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1)); + } + }); + state.registry->setGlobalRemove([](CCWlRegistry* r, uint32_t id) { debugLog("Global {} removed", id); }); + + wl_display_roundtrip(state.display); + + if (!state.wlCompositor || !state.wlShm || !state.wlSeat || !state.xdgShell || !state.inhibitManager) { + clientLog("Failed to get protocols from Hyprland"); + return false; + } + + return true; +} + +static bool createShm(SWlState& state, Vector2D geom) { + if (!state.xrgb8888_support) + return false; + + size_t stride = geom.x * 4; + size_t size = geom.y * stride; + if (!state.shmPool) { + const char* name = "/wl-shm-shortcut-inhibitor"; + state.shmFd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (state.shmFd < 0) + return false; + + if (shm_unlink(name) < 0 || ftruncate(state.shmFd, size) < 0) { + close(state.shmFd); + return false; + } + + state.shmPool = makeShared(state.wlShm->sendCreatePool(state.shmFd, size)); + if (!state.shmPool->resource()) { + close(state.shmFd); + state.shmFd = -1; + state.shmPool.reset(); + return false; + } + state.shmBufSize = size; + } else if (size > state.shmBufSize) { + if (ftruncate(state.shmFd, size) < 0) { + close(state.shmFd); + state.shmFd = -1; + state.shmPool.reset(); + return false; + } + + state.shmPool->sendResize(size); + state.shmBufSize = size; + } + + auto buf = makeShared(state.shmPool->sendCreateBuffer(0, geom.x, geom.y, stride, WL_SHM_FORMAT_XRGB8888)); + if (!buf->resource()) + return false; + + if (state.shmBuf) { + state.shmBuf->sendDestroy(); + state.shmBuf.reset(); + } + + state.shmBuf = buf; + + return true; +} + +static bool setupToplevel(SWlState& state) { + state.wlShm->setFormat([&](CCWlShm* p, uint32_t format) { + if (format == WL_SHM_FORMAT_XRGB8888) + state.xrgb8888_support = true; + }); + + state.xdgShell->setPing([&](CCXdgWmBase* p, uint32_t serial) { state.xdgShell->sendPong(serial); }); + + state.surf = makeShared(state.wlCompositor->sendCreateSurface()); + if (!state.surf->resource()) + return false; + + state.xdgSurf = makeShared(state.xdgShell->sendGetXdgSurface(state.surf->resource())); + if (!state.xdgSurf->resource()) + return false; + + state.xdgToplevel = makeShared(state.xdgSurf->sendGetToplevel()); + if (!state.xdgToplevel->resource()) + return false; + + state.xdgToplevel->setClose([&](CCXdgToplevel* p) { exit(0); }); + + state.xdgToplevel->setConfigure([&](CCXdgToplevel* p, int32_t w, int32_t h, wl_array* arr) { + state.geom = {1280, 720}; + + if (!createShm(state, state.geom)) + exit(-1); + }); + + state.xdgSurf->setConfigure([&](CCXdgSurface* p, uint32_t serial) { + if (!state.shmBuf) + debugLog("xdgSurf configure but no buf made yet?"); + + state.xdgSurf->sendSetWindowGeometry(0, 0, state.geom.x, state.geom.y); + state.surf->sendAttach(state.shmBuf.get(), 0, 0); + state.surf->sendCommit(); + + state.xdgSurf->sendAckConfigure(serial); + + if (!started) { + started = true; + clientLog("started"); + } + }); + + state.xdgToplevel->sendSetTitle("shortcut-inhibitor test client"); + state.xdgToplevel->sendSetAppId("shortcut-inhibitor"); + + state.surf->sendAttach(nullptr, 0, 0); + state.surf->sendCommit(); + + return true; +} + +static bool setupSeat(SWlState& state) { + state.pointer = makeShared(state.wlSeat->sendGetPointer()); + if (!state.pointer->resource()) + return false; + + state.pointer->setEnter([&](CCWlPointer* p, uint32_t serial, wl_proxy* surf, wl_fixed_t x, wl_fixed_t y) { + debugLog("Got pointer enter event, serial {}, x {}, y {}", serial, x, y); + state.enterSerial = serial; + }); + + state.pointer->setLeave([&](CCWlPointer* p, uint32_t serial, wl_proxy* surf) { debugLog("Got pointer leave event, serial {}", serial); }); + + state.pointer->setMotion([&](CCWlPointer* p, uint32_t serial, wl_fixed_t x, wl_fixed_t y) { debugLog("Got pointer motion event, serial {}, x {}, y {}", serial, x, y); }); + + return true; +} + +static void parseRequest(SWlState& state, std::string req) { + if (req.starts_with("on")) { + state.inhibitor = makeShared(state.inhibitManager->sendInhibitShortcuts(state.surf->resource(), state.wlSeat->resource())); + + state.inhibitor->setActive([&](CCZwpKeyboardShortcutsInhibitorV1* inhibitorV1) { clientLog("inhibiting"); }); + state.inhibitor->setInactive([&](CCZwpKeyboardShortcutsInhibitorV1* inhibitorV1) { clientLog("inhibit disabled by compositor"); }); + } else if (req.starts_with("off")) { + state.inhibitor->sendDestroy(); + state.inhibitor.reset(); + shouldExit = true; + clientLog("inhibit disabled by request"); + } +} + +int main(int argc, char** argv) { + if (argc != 1 && argc != 2) + clientLog("Only the \"--debug\" switch is allowed, it turns on debug logs."); + + if (argc == 2 && std::string{argv[1]} == "--debug") + debug = true; + + SWlState state; + + // WAYLAND_DISPLAY env should be set to the correct one + state.display = wl_display_connect(nullptr); + if (!state.display) { + clientLog("Failed to connect to wayland display"); + return -1; + } + + if (!bindRegistry(state) || !setupSeat(state) || !setupToplevel(state)) + return -1; + + std::array readBuf; + readBuf.fill(0); + + wl_display_flush(state.display); + + struct pollfd fds[2] = {{.fd = wl_display_get_fd(state.display), .events = POLLIN | POLLOUT}, {.fd = STDIN_FILENO, .events = POLLIN}}; + while (!shouldExit && poll(fds, 2, 0) != -1) { + if (fds[0].revents & POLLIN) { + wl_display_flush(state.display); + + if (wl_display_prepare_read(state.display) == 0) { + wl_display_read_events(state.display); + wl_display_dispatch_pending(state.display); + } else + wl_display_dispatch(state.display); + + int ret = 0; + do { + ret = wl_display_dispatch_pending(state.display); + wl_display_flush(state.display); + } while (ret > 0); + } + + if (fds[1].revents & POLLIN) { + ssize_t bytesRead = read(fds[1].fd, readBuf.data(), 1023); + if (bytesRead == -1) + continue; + readBuf[bytesRead] = 0; + + parseRequest(state, std::string{readBuf.data()}); + } + } + + wl_display* display = state.display; + state = {}; + + wl_display_disconnect(display); + return 0; +} diff --git a/hyprtester/plugin/src/main.cpp b/hyprtester/plugin/src/main.cpp index 1d0b68dc..ce83c5b4 100644 --- a/hyprtester/plugin/src/main.cpp +++ b/hyprtester/plugin/src/main.cpp @@ -6,12 +6,16 @@ #define private public #include #include -#include -#include #include #include #include +#include +#include +#include +#include #include +#include +#include #undef private #include @@ -43,15 +47,16 @@ static SDispatchResult test(std::string in) { // Trigger a snap move event for the active window static SDispatchResult snapMove(std::string in) { - const auto PLASTWINDOW = g_pCompositor->m_lastWindow.lock(); + const auto PLASTWINDOW = Desktop::focusState()->window(); if (!PLASTWINDOW->m_isFloating) return {.success = false, .error = "Window must be floating"}; 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 {}; } @@ -210,7 +215,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, @@ -221,8 +226,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 @@ -231,9 +258,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; @@ -245,6 +272,85 @@ static SDispatchResult keybind(std::string in) { return {}; } +static Desktop::Rule::CWindowRuleEffectContainer::storageType windowRuleIDX = 0; + +// +static SDispatchResult addWindowRule(std::string in) { + windowRuleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule"); + + if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != windowRuleIDX) + return {.success = false, .error = "re-registering returned a different id?"}; + return {}; +} + +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(windowRuleIDX)) + return {.success = false, .error = "No rule"}; + + 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; @@ -254,7 +360,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_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 43944c3c..941788fd 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 00000000..a5680d4f --- /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 e1ba237f..b5fb68fb 100644 --- a/hyprtester/src/tests/clients/pointer-scroll.cpp +++ b/hyprtester/src/tests/clients/pointer-scroll.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -42,6 +43,7 @@ static bool startClient(SClient& client) { client.readFd = CFileDescriptor(pipeFds2[0]); client.proc->setStdoutFD(pipeFds2[1]); + const int COUNT_BEFORE = Tests::windowCount(); client.proc->runAsync(); close(pipeFds1[0]); @@ -62,9 +64,18 @@ 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 (getFromSocket(std::format("/dispatch setprop pid:{} noanim 1", client.proc->pid())) != "ok") { + 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); return false; } @@ -130,7 +141,7 @@ static bool test() { EXPECT(sendScroll(10), true); EXPECT(getLastDelta(client), 30); - EXPECT(getFromSocket("r/dispatch setprop active scrollmouse 4"), "ok"); + EXPECT(getFromSocket("r/dispatch setprop active scroll_mouse 4"), "ok"); EXPECT(sendScroll(10), true); EXPECT(getLastDelta(client), 40); diff --git a/hyprtester/src/tests/clients/pointer-warp.cpp b/hyprtester/src/tests/clients/pointer-warp.cpp index f37b94c3..be992566 100644 --- a/hyprtester/src/tests/clients/pointer-warp.cpp +++ b/hyprtester/src/tests/clients/pointer-warp.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -42,6 +43,7 @@ static bool startClient(SClient& client) { client.readFd = CFileDescriptor(pipeFds2[0]); client.proc->setStdoutFD(pipeFds2[1]); + const int COUNT_BEFORE = Tests::windowCount(); client.proc->runAsync(); close(pipeFds1[0]); @@ -62,9 +64,18 @@ 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 (getFromSocket(std::format("/dispatch setprop pid:{} noanim 1", client.proc->pid())) != "ok") { + 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); return false; } diff --git a/hyprtester/src/tests/clients/shortcut-inhibitor.cpp b/hyprtester/src/tests/clients/shortcut-inhibitor.cpp new file mode 100644 index 00000000..91c3376c --- /dev/null +++ b/hyprtester/src/tests/clients/shortcut-inhibitor.cpp @@ -0,0 +1,180 @@ +#include "../../shared.hpp" +#include "../../hyprctlCompat.hpp" +#include "../shared.hpp" +#include "tests.hpp" +#include "build.hpp" + +#include +#include + +#include +#include +#include +#include + +using namespace Hyprutils::OS; +using namespace Hyprutils::Memory; + +#define SP CSharedPointer + +struct SClient { + SP proc; + std::array readBuf; + CFileDescriptor readFd, writeFd; + struct pollfd fds; +}; + +static int ret = 0; + +static bool startClient(SClient& client) { + Tests::killAllWindows(); + client.proc = makeShared(binaryDir + "/shortcut-inhibitor", std::vector{}); + + client.proc->addEnv("WAYLAND_DISPLAY", WLDISPLAY); + + int pipeFds1[2], pipeFds2[2]; + if (pipe(pipeFds1) != 0 || pipe(pipeFds2) != 0) { + NLog::log("{}Unable to open pipe to client", Colors::RED); + return false; + } + + client.writeFd = CFileDescriptor(pipeFds1[1]); + client.proc->setStdinFD(pipeFds1[0]); + + client.readFd = CFileDescriptor(pipeFds2[0]); + client.proc->setStdoutFD(pipeFds2[1]); + + const int COUNT_BEFORE = Tests::windowCount(); + client.proc->runAsync(); + + close(pipeFds1[0]); + close(pipeFds2[1]); + + client.fds = {.fd = client.readFd.get(), .events = POLLIN}; + if (poll(&client.fds, 1, 1000) != 1 || !(client.fds.revents & POLLIN)) { + NLog::log("{}shortcut-inhibitor client failed poll", Colors::RED); + return false; + } + + client.readBuf.fill(0); + if (read(client.readFd.get(), client.readBuf.data(), client.readBuf.size() - 1) == -1) { + NLog::log("{}shortcut-inhibitor client read failed", Colors::RED); + return false; + } + + std::string ret = std::string{client.readBuf.data()}; + if (ret.find("started") == std::string::npos) { + NLog::log("{}Failed to start shortcut-inhibitor client, read {}", Colors::RED, ret); + return false; + } + + // wait for window to appear + int counter = 0; + while (Tests::processAlive(client.proc->pid()) && Tests::windowCount() == COUNT_BEFORE) { + counter++; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + if (counter > 50) { + NLog::log("{}shortcut-inhibitor client took too long to open", Colors::RED); + return false; + } + } + + if (!Tests::processAlive(client.proc->pid())) { + NLog::log("{}shortcut-inhibitor client not alive", Colors::RED); + return false; + } + + if (getFromSocket(std::format("/dispatch focuswindow pid:{}", client.proc->pid())) != "ok") { + NLog::log("{}Failed to focus shortcut-inhibitor client", Colors::RED, ret); + return false; + } + + std::string command = "on\n"; + if (write(client.writeFd.get(), command.c_str(), command.length()) == -1) { + NLog::log("{}shortcut-inhibitor client write failed", Colors::RED); + return false; + } + + client.readBuf.fill(0); + if (read(client.readFd.get(), client.readBuf.data(), client.readBuf.size() - 1) == -1) + return false; + + ret = std::string{client.readBuf.data()}; + if (ret.find("inhibiting") == std::string::npos) { + NLog::log("{}shortcut-inhibitor client didn't return inhibiting", Colors::RED); + return false; + } + + NLog::log("{}Started shortcut-inhibitor client", Colors::YELLOW); + + return true; +} + +static void stopClient(SClient& client) { + std::string cmd = "off\n"; + write(client.writeFd.get(), cmd.c_str(), cmd.length()); + + kill(client.proc->pid(), SIGKILL); + client.proc.reset(); +} + +static std::string flagFile = "/tmp/hyprtester-keybinds.txt"; + +static bool checkFlag() { + bool exists = std::filesystem::exists(flagFile); + std::filesystem::remove(flagFile); + return exists; +} + +static bool attemptCheckFlag(int attempts, int intervalMs) { + for (int i = 0; i < attempts; i++) { + if (checkFlag()) + return true; + + std::this_thread::sleep_for(std::chrono::milliseconds(intervalMs)); + } + + return false; +} + +static bool test() { + SClient client; + if (!startClient(client)) + return false; + + NLog::log("{}Testing keybinds", Colors::GREEN); + //basic keybind test + EXPECT(checkFlag(), false); + EXPECT(getFromSocket("/keyword bind SUPER,Y,exec,touch " + flagFile), "ok"); + OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29")); + EXPECT(attemptCheckFlag(20, 50), false); + OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29")); + EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok"); + + //keybind bypass flag test + EXPECT(checkFlag(), false); + EXPECT(getFromSocket("/keyword bindp SUPER,Y,exec,touch " + flagFile), "ok"); + OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29")); + EXPECT(attemptCheckFlag(20, 50), true); + OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29")); + EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok"); + + NLog::log("{}Testing gestures", Colors::GREEN); + //basic gesture test + OK(getFromSocket("/dispatch plugin:test:gesture right,3")); + EXPECT_NOT_CONTAINS(getFromSocket("/activewindow"), "floating: 1"); + + //gesture bypass flag test + OK(getFromSocket("/dispatch plugin:test:gesture right,2")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "floating: 1"); + + stopClient(client); + + NLog::log("{}Reloading the config", Colors::YELLOW); + OK(getFromSocket("/reload")); + + return !ret; +} + +REGISTER_CLIENT_TEST_FN(test); diff --git a/hyprtester/src/tests/main/dwindle.cpp b/hyprtester/src/tests/main/dwindle.cpp index a75646c8..234bfc33 100644 --- a/hyprtester/src/tests/main/dwindle.cpp +++ b/hyprtester/src/tests/main/dwindle.cpp @@ -16,6 +16,7 @@ static void testFloatClamp() { } OK(getFromSocket("/keyword dwindle:force_split 2")); + OK(getFromSocket("/keyword monitor HEADLESS-2, addreserved, 0, 20, 0, 20")); OK(getFromSocket("/dispatch focuswindow class:c")); OK(getFromSocket("/dispatch setfloating class:c")); OK(getFromSocket("/dispatch resizewindowpixel exact 1200 900,class:c")); @@ -24,7 +25,7 @@ static void testFloatClamp() { { auto str = getFromSocket("/clients"); - EXPECT_CONTAINS(str, "at: 718,178"); + EXPECT_CONTAINS(str, "at: 698,158"); EXPECT_CONTAINS(str, "size: 1200,900"); } @@ -33,6 +34,197 @@ static void testFloatClamp() { // clean up NLog::log("{}Killing all windows", Colors::YELLOW); Tests::killAllWindows(); + + OK(getFromSocket("/reload")); +} + +static void test13349() { + + // Test if dwindle properly uses a focal point to place a new window. + // exposed by #13349 as a regression from #12890 + + for (auto const& win : {"a", "b", "c"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + OK(getFromSocket("/dispatch focuswindow class:c")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 967,547"); + EXPECT_CONTAINS(str, "size: 931,511"); + } + + OK(getFromSocket("/dispatch movewindow l")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 497,22"); + EXPECT_CONTAINS(str, "size: 456,1036"); + } + + OK(getFromSocket("/dispatch movewindow r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 967,22"); + EXPECT_CONTAINS(str, "size: 456,1036"); + } + + // clean up + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + +static void testSplit() { + // Test various split methods + + Tests::spawnKitty("a"); + + // these must not crash + EXPECT_NOT(getFromSocket("/dispatch layoutmsg swapsplit"), "ok"); + EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio 1 exact"), "ok"); + + Tests::spawnKitty("b"); + + OK(getFromSocket("/dispatch focuswindow class:a")); + OK(getFromSocket("/dispatch layoutmsg splitratio -0.2")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 743,1036"); + } + + OK(getFromSocket("/dispatch layoutmsg splitratio 1.6 exact")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1495,1036"); + } + + EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio fhne exact"), "ok"); + EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio exact"), "ok"); + EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio -....9"), "ok"); + EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio ..9"), "ok"); + EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio"), "ok"); + + OK(getFromSocket("/dispatch layoutmsg togglesplit")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1876,823"); + } + + OK(getFromSocket("/dispatch layoutmsg swapsplit")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,859"); + EXPECT_CONTAINS(str, "size: 1876,199"); + } + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + +static void testRotatesplit() { + OK(getFromSocket("r/keyword general:gaps_in 0")); + OK(getFromSocket("r/keyword general:gaps_out 0")); + OK(getFromSocket("r/keyword general:border_size 0")); + + for (auto const& win : {"a", "b"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "at: 0,0"); + EXPECT_CONTAINS(str, "size: 960,1080"); + } + + // test 4 repeated rotations by 90 degrees + OK(getFromSocket("/dispatch layoutmsg rotatesplit")); + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "at: 0,0"); + EXPECT_CONTAINS(str, "size: 1920,540"); + } + + OK(getFromSocket("/dispatch layoutmsg rotatesplit")); + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "at: 960,0"); + EXPECT_CONTAINS(str, "size: 960,1080"); + } + + OK(getFromSocket("/dispatch layoutmsg rotatesplit")); + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "at: 0,540"); + EXPECT_CONTAINS(str, "size: 1920,540"); + } + + OK(getFromSocket("/dispatch layoutmsg rotatesplit")); + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "at: 0,0"); + EXPECT_CONTAINS(str, "size: 960,1080"); + } + + // test different angles + OK(getFromSocket("/dispatch layoutmsg rotatesplit 180")); + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "at: 960,0"); + EXPECT_CONTAINS(str, "size: 960,1080"); + } + + OK(getFromSocket("/dispatch layoutmsg rotatesplit 270")); + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "at: 0,540"); + EXPECT_CONTAINS(str, "size: 1920,540"); + } + + OK(getFromSocket("/dispatch layoutmsg rotatesplit 360")); + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "at: 0,0"); + EXPECT_CONTAINS(str, "size: 1920,540"); + } + + // test negative angles + OK(getFromSocket("/dispatch layoutmsg rotatesplit -90")); + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "at: 0,0"); + EXPECT_CONTAINS(str, "size: 960,1080"); + } + + OK(getFromSocket("/dispatch layoutmsg rotatesplit -180")); + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "at: 960,0"); + EXPECT_CONTAINS(str, "size: 960,1080"); + } + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + OK(getFromSocket("/reload")); } static bool test() { @@ -42,6 +234,15 @@ static bool test() { NLog::log("{}Testing float clamp", Colors::GREEN); testFloatClamp(); + NLog::log("{}Testing #13349", Colors::GREEN); + test13349(); + + NLog::log("{}Testing splits", Colors::GREEN); + testSplit(); + + NLog::log("{}Testing rotatesplit", Colors::GREEN); + testRotatesplit(); + // clean up NLog::log("Cleaning up", Colors::YELLOW); getFromSocket("/dispatch workspace 1"); diff --git a/hyprtester/src/tests/main/exec.cpp b/hyprtester/src/tests/main/exec.cpp index fd42cf06..a410494a 100644 --- a/hyprtester/src/tests/main/exec.cpp +++ b/hyprtester/src/tests/main/exec.cpp @@ -2,6 +2,7 @@ #include "../../shared.hpp" #include "../../hyprctlCompat.hpp" #include +#include #include #include #include @@ -15,40 +16,46 @@ using namespace Hyprutils::Memory; #define UP CUniquePointer #define SP CSharedPointer -static bool test() { +const static auto SLEEP_DURATIONS = std::array{1, 10}; + +static bool test() { NLog::log("{}Testing process spawning", Colors::GREEN); - // Note: POSIX sleep does not support fractional seconds, so - // can't sleep for less than 1 second. - OK(getFromSocket("/dispatch exec sleep 1")); + for (const auto duration : SLEEP_DURATIONS) { + // Note: POSIX sleep does not support fractional seconds, so + // can't sleep for less than 1 second. + OK(getFromSocket(std::format("/dispatch exec sleep {}", duration))); - // Ensure that sleep is our child - const std::string sleepPidS = Tests::execAndGet("pgrep sleep"); - pid_t sleepPid; - try { - sleepPid = std::stoull(sleepPidS); - } catch (...) { - NLog::log("{}Sleep was not spawned or several sleeps are running: pgrep returned '{}'", Colors::RED, sleepPidS); - return false; + // Ensure that sleep is our child + const std::string sleepPidS = Tests::execAndGet("pgrep sleep"); + pid_t sleepPid; + try { + sleepPid = std::stoull(sleepPidS); + } catch (...) { + NLog::log("{}Sleep was not spawned or several sleeps are running: pgrep returned '{}'", Colors::RED, sleepPidS); + continue; + } + + const std::string sleepParentComm = Tests::execAndGet("cat \"/proc/$(ps -o ppid:1= -p " + sleepPidS + ")/comm\""); + NLog::log("{}Expecting that sleep's parent is Hyprland", Colors::YELLOW); + EXPECT_CONTAINS(sleepParentComm, "Hyprland"); + + std::this_thread::sleep_for(std::chrono::seconds(duration)); + + // Ensure that sleep did not become a zombie + EXPECT(Tests::processAlive(sleepPid), false); + + // kill all + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + NLog::log("{}Expecting 0 windows", Colors::YELLOW); + EXPECT(Tests::windowCount(), 0); + + return !ret; } - const std::string sleepParentComm = Tests::execAndGet("cat \"/proc/$(ps -o ppid:1= -p " + sleepPidS + ")/comm\""); - NLog::log("{}Expecting that sleep's parent is Hyprland", Colors::YELLOW); - EXPECT_CONTAINS(sleepParentComm, "Hyprland"); - - std::this_thread::sleep_for(std::chrono::seconds(1)); - - // Ensure that sleep did not become a zombie - EXPECT(Tests::processAlive(sleepPid), false); - - // kill all - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - - NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); - - return !ret; + return false; } REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/gestures.cpp b/hyprtester/src/tests/main/gestures.cpp index 9b31cdb6..07cc4ca9 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 3cf15851..2f9c5062 100644 --- a/hyprtester/src/tests/main/groups.cpp +++ b/hyprtester/src/tests/main/groups.cpp @@ -127,6 +127,34 @@ static bool test() { ret = 1; } + // test movegroupwindow: focus should follow the moved window + NLog::log("{}Test movegroupwindow focus follows window", Colors::YELLOW); + try { + auto str = getFromSocket("/activewindow"); + auto activeBeforeMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16); + OK(getFromSocket("/dispatch movegroupwindow f")); + str = getFromSocket("/activewindow"); + auto activeAfterMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16); + EXPECT(activeAfterMove, activeBeforeMove); + } catch (...) { + NLog::log("{}Fail at getting prop", Colors::RED); + ret = 1; + } + + // and backwards + NLog::log("{}Test movegroupwindow backwards", Colors::YELLOW); + try { + auto str = getFromSocket("/activewindow"); + auto activeBeforeMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16); + OK(getFromSocket("/dispatch movegroupwindow b")); + str = getFromSocket("/activewindow"); + auto activeAfterMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16); + EXPECT(activeAfterMove, activeBeforeMove); + } catch (...) { + NLog::log("{}Fail at getting prop", Colors::RED); + ret = 1; + } + NLog::log("{}Disable autogrouping", Colors::YELLOW); OK(getFromSocket("/keyword group:auto_group false")); @@ -173,6 +201,99 @@ static bool test() { NLog::log("{}Expecting 0 windows", Colors::YELLOW); EXPECT(Tests::windowCount(), 0); + // test movewindoworgroup: direction should be respected when extracting from group + NLog::log("{}Test movewindoworgroup respects direction out of group", Colors::YELLOW); + OK(getFromSocket("/keyword group:groupbar:enabled 0")); + { + auto kittyE = Tests::spawnKitty(); + if (!kittyE) { + NLog::log("{}Error: kitty did not spawn", Colors::RED); + return false; + } + + // group kitty, and new windows should be auto-grouped + OK(getFromSocket("/dispatch togglegroup")); + + auto kittyF = Tests::spawnKitty(); + if (!kittyF) { + NLog::log("{}Error: kitty did not spawn", Colors::RED); + return false; + } + EXPECT(Tests::windowCount(), 2); + + // both windows should be grouped at the same position + { + auto str = getFromSocket("/clients"); + EXPECT_COUNT_STRING(str, "at: 22,22", 2); + } + + // move active window out of group to the right + NLog::log("{}Test movewindoworgroup r", Colors::YELLOW); + OK(getFromSocket("/dispatch movewindoworgroup r")); + + // the group should stay at x=22, the extracted window should be to the right + { + auto str = getFromSocket("/clients"); + EXPECT_COUNT_STRING(str, "at: 22,22", 1); + } + + // move it back into the group + OK(getFromSocket("/dispatch moveintogroup l")); + + // move active window out of group downward + NLog::log("{}Test movewindoworgroup d", Colors::YELLOW); + OK(getFromSocket("/dispatch movewindoworgroup d")); + + // the group should stay at y=22, the extracted window should be below + { + auto str = getFromSocket("/clients"); + EXPECT_COUNT_STRING(str, "at: 22,22", 1); + } + + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); + } + + // test that we deny a floated window getting auto-grouped into a tiled group. + NLog::log("{}Test that we deny a floated window getting auto-grouped into a tiled group.", Colors::GREEN); + + OK(getFromSocket("/keyword windowrule[kitty-tiled]:match:class kitty_tiled")); + OK(getFromSocket("/keyword windowrule[kitty-tiled]:tile yes")); + auto kittyProcE = Tests::spawnKitty("kitty_tiled"); + if (!kittyProcE) { + NLog::log("{}Error: kitty did not spawn", Colors::RED); + return false; + } + OK(getFromSocket("/dispatch togglegroup")); + + OK(getFromSocket("/keyword windowrule[kitty-floated]:match:class kitty_floated")); + OK(getFromSocket("/keyword windowrule[kitty-floated]:float yes")); + auto kittyProcF = Tests::spawnKitty("kitty_floated"); + if (!kittyProcF) { + NLog::log("{}Error: kitty did not spawn", Colors::RED); + return false; + } + + EXPECT(Tests::windowCount(), 2); + + { + auto clients = getFromSocket("/clients"); + auto classPos = clients.find("class: kitty_floated"); + if (classPos == std::string::npos) { + NLog::log("{}Could not find kitty_floated in clients output", Colors::RED); + ret = 1; + } else { + auto entryStart = clients.rfind("Window ", classPos); + auto entryEnd = clients.find("\n\n", classPos); + auto windowEntry = clients.substr(entryStart, entryEnd - entryStart); + EXPECT_CONTAINS(windowEntry, "floating: 1"); + EXPECT_CONTAINS(windowEntry, "grouped: 0"); + } + } + + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); + return !ret; } diff --git a/hyprtester/src/tests/main/hyprctl.cpp b/hyprtester/src/tests/main/hyprctl.cpp index 95f7caae..e5e6f1fc 100644 --- a/hyprtester/src/tests/main/hyprctl.cpp +++ b/hyprtester/src/tests/main/hyprctl.cpp @@ -56,82 +56,92 @@ static bool testGetprop() { return false; } - // animationstyle - EXPECT(getCommandStdOut("hyprctl getprop class:kitty animationstyle"), "(unset)"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty animationstyle -j"), R"({"animationstyle": ""})"); - getFromSocket("/dispatch setprop class:kitty animationstyle teststyle"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty animationstyle"), "teststyle"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty animationstyle -j"), R"({"animationstyle": "teststyle"})"); + // animation + EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation"), "(unset)"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation -j"), R"({"animation": ""})"); + getFromSocket("/dispatch setprop class:kitty animation teststyle"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation"), "teststyle"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation -j"), R"({"animation": "teststyle"})"); - // maxsize - EXPECT(getCommandStdOut("hyprctl getprop class:kitty maxsize"), "inf inf"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty maxsize -j"), R"({"maxsize": [null,null]})"); - getFromSocket("/dispatch setprop class:kitty maxsize 200 150"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty maxsize"), "200 150"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty maxsize -j"), R"({"maxsize": [200,150]})"); + // max_size + EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size"), "inf inf"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size -j"), R"({"max_size": [null,null]})"); + getFromSocket("/dispatch setprop class:kitty max_size 200 150"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size"), "200 150"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size -j"), R"({"max_size": [200,150]})"); - // minsize - EXPECT(getCommandStdOut("hyprctl getprop class:kitty minsize"), "20 20"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty minsize -j"), R"({"minsize": [20,20]})"); - getFromSocket("/dispatch setprop class:kitty minsize 100 50"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty minsize"), "100 50"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty minsize -j"), R"({"minsize": [100,50]})"); + // min_size + EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "20 20"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [20,20]})"); + getFromSocket("/dispatch setprop class:kitty min_size 100 50"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "100 50"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [100,50]})"); - // alpha - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alpha"), "1"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alpha -j"), R"({"alpha": 1})"); - getFromSocket("/dispatch setprop class:kitty alpha 0.3"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alpha"), "0.3"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alpha -j"), R"({"alpha": 0.3})"); + // 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 - // alphainactive - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactive"), "1"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactive -j"), R"({"alphainactive": 1})"); - getFromSocket("/dispatch setprop class:kitty alphainactive 0.5"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactive"), "0.5"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactive -j"), R"({"alphainactive": 0.5})"); + // opacity + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity"), "1"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity -j"), R"({"opacity": 1})"); + getFromSocket("/dispatch setprop class:kitty opacity 0.3"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity"), "0.3"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity -j"), R"({"opacity": 0.3})"); - // alphafullscreen - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreen"), "1"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreen -j"), R"({"alphafullscreen": 1})"); - getFromSocket("/dispatch setprop class:kitty alphafullscreen 0.75"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreen"), "0.75"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreen -j"), R"({"alphafullscreen": 0.75})"); + // opacity_inactive + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive"), "1"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive -j"), R"({"opacity_inactive": 1})"); + getFromSocket("/dispatch setprop class:kitty opacity_inactive 0.5"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive"), "0.5"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive -j"), R"({"opacity_inactive": 0.5})"); - // alphaoverride - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphaoverride"), "false"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphaoverride -j"), R"({"alphaoverride": false})"); - getFromSocket("/dispatch setprop class:kitty alphaoverride true"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphaoverride"), "true"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphaoverride -j"), R"({"alphaoverride": true})"); + // opacity_fullscreen + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen"), "1"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen -j"), R"({"opacity_fullscreen": 1})"); + getFromSocket("/dispatch setprop class:kitty opacity_fullscreen 0.75"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen"), "0.75"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen -j"), R"({"opacity_fullscreen": 0.75})"); - // alphainactiveoverride - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactiveoverride"), "false"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactiveoverride -j"), R"({"alphainactiveoverride": false})"); - getFromSocket("/dispatch setprop class:kitty alphainactiveoverride true"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactiveoverride"), "true"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactiveoverride -j"), R"({"alphainactiveoverride": true})"); + // opacity_override + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override"), "false"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override -j"), R"({"opacity_override": false})"); + getFromSocket("/dispatch setprop class:kitty opacity_override true"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override"), "true"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override -j"), R"({"opacity_override": true})"); - // alphafullscreenoverride - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreenoverride"), "false"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreenoverride -j"), R"({"alphafullscreenoverride": false})"); - getFromSocket("/dispatch setprop class:kitty alphafullscreenoverride true"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreenoverride"), "true"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreenoverride -j"), R"({"alphafullscreenoverride": true})"); + // opacity_inactive_override + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override"), "false"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override -j"), R"({"opacity_inactive_override": false})"); + getFromSocket("/dispatch setprop class:kitty opacity_inactive_override true"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override"), "true"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override -j"), R"({"opacity_inactive_override": true})"); - // activebordercolor - EXPECT(getCommandStdOut("hyprctl getprop class:kitty activebordercolor"), "ee33ccff ee00ff99 45deg"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty activebordercolor -j"), R"({"activebordercolor": "ee33ccff ee00ff99 45deg"})"); - getFromSocket("/dispatch setprop class:kitty activebordercolor rgb(abcdef)"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty activebordercolor"), "ffabcdef 0deg"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty activebordercolor -j"), R"({"activebordercolor": "ffabcdef 0deg"})"); + // opacity_fullscreen_override + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override"), "false"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override -j"), R"({"opacity_fullscreen_override": false})"); + getFromSocket("/dispatch setprop class:kitty opacity_fullscreen_override true"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override"), "true"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override -j"), R"({"opacity_fullscreen_override": true})"); + + // active_border_color + EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color"), "ee33ccff ee00ff99 45deg"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color -j"), R"({"active_border_color": "ee33ccff ee00ff99 45deg"})"); + getFromSocket("/dispatch setprop class:kitty active_border_color rgb(abcdef)"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color"), "ffabcdef 0deg"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color -j"), R"({"active_border_color": "ffabcdef 0deg"})"); // bool window properties - EXPECT(getCommandStdOut("hyprctl getprop class:kitty allowsinput"), "false"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty allowsinput -j"), R"({"allowsinput": false})"); - getFromSocket("/dispatch setprop class:kitty allowsinput true"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty allowsinput"), "true"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty allowsinput -j"), R"({"allowsinput": true})"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input"), "false"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input -j"), R"({"allows_input": false})"); + getFromSocket("/dispatch setprop class:kitty allows_input true"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input"), "true"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input -j"), R"({"allows_input": true})"); // int window properties EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding"), "10"); @@ -141,16 +151,16 @@ static bool testGetprop() { EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding -j"), R"({"rounding": 4})"); // float window properties - EXPECT(getCommandStdOut("hyprctl getprop class:kitty roundingpower"), "2"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty roundingpower -j"), R"({"roundingpower": 2})"); - getFromSocket("/dispatch setprop class:kitty roundingpower 1.25"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty roundingpower"), "1.25"); - EXPECT(getCommandStdOut("hyprctl getprop class:kitty roundingpower -j"), R"({"roundingpower": 1.25})"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power"), "2"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power -j"), R"({"rounding_power": 2})"); + getFromSocket("/dispatch setprop class:kitty rounding_power 1.25"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power"), "1.25"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power -j"), R"({"rounding_power": 1.25})"); // errors EXPECT(getCommandStdOut("hyprctl getprop"), "not enough args"); EXPECT(getCommandStdOut("hyprctl getprop class:kitty"), "not enough args"); - EXPECT(getCommandStdOut("hyprctl getprop class:nonexistantclass animationstyle"), "window not found"); + EXPECT(getCommandStdOut("hyprctl getprop class:nonexistantclass animation"), "window not found"); EXPECT(getCommandStdOut("hyprctl getprop class:kitty nonexistantprop"), "prop not found"); // kill all diff --git a/hyprtester/src/tests/main/keybinds.cpp b/hyprtester/src/tests/main/keybinds.cpp index 4e224749..a87a8b07 100644 --- a/hyprtester/src/tests/main/keybinds.cpp +++ b/hyprtester/src/tests/main/keybinds.cpp @@ -34,6 +34,17 @@ static bool checkFlag() { 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 std::string readKittyOutput() { std::string output = Tests::execAndGet("kitten @ --to unix:/tmp/hyprtester-kitty.sock get-text --extent all"); // chop off shell prompt @@ -75,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"); @@ -88,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"); @@ -105,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")); @@ -122,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")); @@ -141,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"); } @@ -158,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"); @@ -171,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")); @@ -194,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,10 +225,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"); } @@ -231,15 +242,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"); @@ -304,9 +317,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 @@ -336,7 +349,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); @@ -443,9 +456,68 @@ 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); + + EXPECT(checkFlag(), false); + EXPECT(getFromSocket("/keyword bindu SUPER,Y,exec,touch " + flagFile), "ok"); + EXPECT_CONTAINS(getFromSocket("/submap"), "default"); + + // keybind works on default submap + OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29")); + OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29")); + EXPECT(attemptCheckFlag(30, 5), true); + + // keybind works on submap1 + getFromSocket("/dispatch plugin:test:keybind 1,7,30"); + getFromSocket("/dispatch plugin:test:keybind 0,7,30"); + EXPECT_CONTAINS(getFromSocket("/submap"), "submap1"); + OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29")); + OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29")); + EXPECT(attemptCheckFlag(30, 5), true); + + // reset to default submap + getFromSocket("/dispatch plugin:test:keybind 1,0,33"); + getFromSocket("/dispatch plugin:test:keybind 0,0,33"); + EXPECT_CONTAINS(getFromSocket("/submap"), "default"); + + EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok"); +} + static bool test() { NLog::log("{}Testing keybinds", Colors::GREEN); + clearFlag(); + testBind(); testBindKey(); testLongPress(); @@ -462,8 +534,9 @@ static bool test() { testShortcutLongPressKeyRelease(); testShortcutRepeat(); testShortcutRepeatKeyRelease(); - testSubmap(); + testSubmapUniversal(); + testBindsAfterScroll(); clearFlag(); return !ret; diff --git a/hyprtester/src/tests/main/layer.cpp b/hyprtester/src/tests/main/layer.cpp new file mode 100644 index 00000000..73e30ba6 --- /dev/null +++ b/hyprtester/src/tests/main/layer.cpp @@ -0,0 +1,53 @@ +#include "../../Log.hpp" +#include "../shared.hpp" +#include "tests.hpp" +#include "../../shared.hpp" +#include "../../hyprctlCompat.hpp" +#include +#include + +static int ret = 0; + +using namespace Hyprutils::OS; +using namespace Hyprutils::Memory; + +static bool spawnLayer(const std::string& namespace_) { + NLog::log("{}Spawning kitty layer {}", Colors::YELLOW, namespace_); + if (!Tests::spawnLayerKitty(namespace_)) { + NLog::log("{}Error: {} layer did not spawn", Colors::RED, namespace_); + return false; + } + return true; +} + +static bool test() { + NLog::log("{}Testing plugin layerrules", Colors::GREEN); + + if (!spawnLayer("rule-layer")) + return false; + + OK(getFromSocket("/dispatch plugin:test:add_layer_rule")); + OK(getFromSocket("/reload")); + + OK(getFromSocket("/keyword layerrule match:namespace rule-layer, plugin_rule effect")); + + if (!spawnLayer("rule-layer")) + return false; + + if (!spawnLayer("norule-layer")) + return false; + + OK(getFromSocket("/dispatch plugin:test:check_layer_rule")); + + OK(getFromSocket("/reload")); + + NLog::log("{}Killing all layers", Colors::YELLOW); + Tests::killAllLayers(); + + NLog::log("{}Expecting 0 layers", Colors::YELLOW); + EXPECT(Tests::layerCount(), 0); + + return !ret; +} + +REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/layout.cpp b/hyprtester/src/tests/main/layout.cpp new file mode 100644 index 00000000..186d7034 --- /dev/null +++ b/hyprtester/src/tests/main/layout.cpp @@ -0,0 +1,127 @@ +#include "../shared.hpp" +#include "../../shared.hpp" +#include "../../hyprctlCompat.hpp" +#include "tests.hpp" + +static int ret = 0; + +static void swar() { + OK(getFromSocket("/keyword layout:single_window_aspect_ratio 1 1")); + + Tests::spawnKitty(); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 442,22"); + EXPECT_CONTAINS(str, "size: 1036,1036"); + } + + Tests::spawnKitty(); + + OK(getFromSocket("/dispatch killwindow activewindow")); + + Tests::waitUntilWindowsN(1); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 442,22"); + EXPECT_CONTAINS(str, "size: 1036,1036"); + } + + // don't use swar on maximized + OK(getFromSocket("/dispatch fullscreen 1")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1876,1036"); + } + + // clean up + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + +// Don't crash when focus after global geometry changes +static void testCrashOnGeomUpdate() { + Tests::spawnKitty(); + Tests::spawnKitty(); + Tests::spawnKitty(); + + // move the layout + OK(getFromSocket("/keyword monitor HEADLESS-2,1920x1080@60,1000x0,1")); + + // shouldnt crash + OK(getFromSocket("/dispatch movefocus r")); + + OK(getFromSocket("/reload")); + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + +// Test if size + pos is preserved after fs cycle +static void testPosPreserve() { + Tests::spawnKitty(); + + OK(getFromSocket("/dispatch setfloating class:kitty")); + OK(getFromSocket("/dispatch resizewindowpixel exact 1337 69, class:kitty")); + OK(getFromSocket("/dispatch movewindowpixel exact 420 420, class:kitty")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 420,420"); + EXPECT_CONTAINS(str, "size: 1337,69"); + } + + OK(getFromSocket("/dispatch fullscreen")); + OK(getFromSocket("/dispatch fullscreen")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "size: 1337,69"); + } + + OK(getFromSocket("/dispatch movewindow r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 581,420"); + EXPECT_CONTAINS(str, "size: 1337,69"); + } + + OK(getFromSocket("/dispatch fullscreen")); + OK(getFromSocket("/dispatch fullscreen")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 581,420"); + EXPECT_CONTAINS(str, "size: 1337,69"); + } + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + +static bool test() { + NLog::log("{}Testing layout generic", Colors::GREEN); + + // setup + OK(getFromSocket("/dispatch workspace 10")); + + // test + NLog::log("{}Testing `single_window_aspect_ratio`", Colors::GREEN); + swar(); + + testCrashOnGeomUpdate(); + testPosPreserve(); + + // clean up + NLog::log("Cleaning up", Colors::YELLOW); + OK(getFromSocket("/dispatch workspace 1")); + OK(getFromSocket("/reload")); + + return !ret; +} + +REGISTER_TEST_FN(test); diff --git a/hyprtester/src/tests/main/master.cpp b/hyprtester/src/tests/main/master.cpp index 9cd20e83..9aaa4cf0 100644 --- a/hyprtester/src/tests/main/master.cpp +++ b/hyprtester/src/tests/main/master.cpp @@ -3,7 +3,53 @@ #include "../../hyprctlCompat.hpp" #include "tests.hpp" -static int ret = 0; +static int ret = 0; + +// reqs 1 master 3 slaves +static void testOrientations() { + OK(getFromSocket("/keyword master:orientation top")); + + // top + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1876"); + } + + // cycle = top, right, bottom, center, left + + // right + OK(getFromSocket("/dispatch layoutmsg orientationnext")); + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 873,22"); + EXPECT_CONTAINS(str, "size: 1025,1036"); + } + + // bottom + OK(getFromSocket("/dispatch layoutmsg orientationnext")); + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,495"); + EXPECT_CONTAINS(str, "size: 1876"); + } + + // center + OK(getFromSocket("/dispatch layoutmsg orientationnext")); + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 450,22"); + EXPECT_CONTAINS(str, "size: 1020,1036"); + } + + // left + OK(getFromSocket("/dispatch layoutmsg orientationnext")); + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1025,1036"); + } +} static void focusMasterPrevious() { // setup @@ -44,11 +90,74 @@ static void focusMasterPrevious() { OK(getFromSocket("/dispatch layoutmsg focusmaster previous")); EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master"); + testOrientations(); + // clean up NLog::log("{}Killing all windows", Colors::YELLOW); Tests::killAllWindows(); } +static void testFsBehavior() { + // Master will re-send data to fullscreen / maximized windows, which can interfere with misc:on_focus_under_fullscreen + // check that it doesn't. + + for (auto const& win : {"master", "slave1", "slave2"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + OK(getFromSocket("/dispatch focuswindow class:master")); + OK(getFromSocket("/dispatch fullscreen 1")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1876,1036"); + EXPECT_CONTAINS(str, "class: master"); + } + + OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 1")); + + Tests::spawnKitty("new_master"); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1876,1036"); + EXPECT_CONTAINS(str, "class: new_master"); + EXPECT_CONTAINS(str, "fullscreen: 1"); + } + + OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0")); + + Tests::spawnKitty("ignored"); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1876,1036"); + EXPECT_CONTAINS(str, "class: new_master"); + EXPECT_CONTAINS(str, "fullscreen: 1"); + } + + OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2")); + + Tests::spawnKitty("vaxwashere"); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: vaxwashere"); + EXPECT_CONTAINS(str, "fullscreen: 0"); + } + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + static bool test() { NLog::log("{}Testing Master layout", Colors::GREEN); @@ -60,6 +169,9 @@ static bool test() { NLog::log("{}Testing `focusmaster previous` layoutmsg", Colors::GREEN); focusMasterPrevious(); + NLog::log("{}Testing fs behavior", Colors::GREEN); + testFsBehavior(); + // clean up NLog::log("Cleaning up", Colors::YELLOW); OK(getFromSocket("/dispatch workspace 1")); diff --git a/hyprtester/src/tests/main/misc.cpp b/hyprtester/src/tests/main/misc.cpp index d5865f20..471eef7a 100644 --- a/hyprtester/src/tests/main/misc.cpp +++ b/hyprtester/src/tests/main/misc.cpp @@ -18,6 +18,121 @@ using namespace Hyprutils::Memory; #define UP CUniquePointer #define SP CSharedPointer +// Uncomment once test vm can run hyprland-dialog +// static void testAnrDialogs() { +// NLog::log("{}Testing ANR dialogs", Colors::YELLOW); +// +// OK(getFromSocket("/keyword misc:enable_anr_dialog true")); +// OK(getFromSocket("/keyword misc:anr_missed_pings 1")); +// +// NLog::log("{}ANR dialog: regular workspaces", Colors::YELLOW); +// { +// OK(getFromSocket("/dispatch workspace 2")); +// +// auto kitty = Tests::spawnKitty("bad_kitty"); +// +// if (!kitty) { +// ret = 1; +// return; +// } +// +// { +// auto str = getFromSocket("/activewindow"); +// EXPECT_CONTAINS(str, "workspace: 2"); +// } +// +// OK(getFromSocket("/dispatch workspace 1")); +// +// ::kill(kitty->pid(), SIGSTOP); +// Tests::waitUntilWindowsN(2); +// +// { +// auto str = getFromSocket("/activeworkspace"); +// EXPECT_CONTAINS(str, "windows: 0"); +// } +// +// { +// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog")) +// auto str = getFromSocket("/activewindow"); +// EXPECT_CONTAINS(str, "workspace: 2"); +// } +// } +// +// Tests::killAllWindows(); +// +// NLog::log("{}ANR dialog: named workspaces", Colors::YELLOW); +// { +// OK(getFromSocket("/dispatch workspace name:yummy")); +// +// auto kitty = Tests::spawnKitty("bad_kitty"); +// +// if (!kitty) { +// ret = 1; +// return; +// } +// +// { +// auto str = getFromSocket("/activewindow"); +// EXPECT_CONTAINS(str, "yummy"); +// } +// +// OK(getFromSocket("/dispatch workspace 1")); +// +// ::kill(kitty->pid(), SIGSTOP); +// Tests::waitUntilWindowsN(2); +// +// { +// auto str = getFromSocket("/activeworkspace"); +// EXPECT_CONTAINS(str, "windows: 0"); +// } +// +// { +// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog")) +// auto str = getFromSocket("/activewindow"); +// EXPECT_CONTAINS(str, "yummy"); +// } +// } +// +// Tests::killAllWindows(); +// +// NLog::log("{}ANR dialog: special workspaces", Colors::YELLOW); +// { +// OK(getFromSocket("/dispatch workspace special:apple")); +// +// auto kitty = Tests::spawnKitty("bad_kitty"); +// +// if (!kitty) { +// ret = 1; +// return; +// } +// +// { +// auto str = getFromSocket("/activewindow"); +// EXPECT_CONTAINS(str, "special:apple"); +// } +// +// OK(getFromSocket("/dispatch togglespecialworkspace apple")); +// OK(getFromSocket("/dispatch workspace 1")); +// +// ::kill(kitty->pid(), SIGSTOP); +// Tests::waitUntilWindowsN(2); +// +// { +// auto str = getFromSocket("/activeworkspace"); +// EXPECT_CONTAINS(str, "windows: 0"); +// } +// +// { +// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog")) +// auto str = getFromSocket("/activewindow"); +// EXPECT_CONTAINS(str, "special:apple"); +// } +// } +// +// OK(getFromSocket("/reload")); +// Tests::killAllWindows(); +// } + static bool test() { NLog::log("{}Testing config: misc:", Colors::GREEN); @@ -53,7 +168,7 @@ static bool test() { NLog::log("{}Testing new_window_takes_over_fullscreen", Colors::YELLOW); - OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 0")); + OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0")); Tests::spawnKitty("kitty_A"); @@ -73,7 +188,16 @@ static bool test() { EXPECT_CONTAINS(str, "kitty_A"); } - OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 1")); + OK(getFromSocket("/dispatch focuswindow class:kitty_B")); + + { + // should be ignored as per focus_under_fullscreen 0 + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "fullscreen: 2"); + EXPECT_CONTAINS(str, "kitty_A"); + } + + OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 1")); Tests::spawnKitty("kitty_C"); @@ -83,7 +207,7 @@ static bool test() { EXPECT_CONTAINS(str, "kitty_C"); } - OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 2")); + OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2")); Tests::spawnKitty("kitty_D"); @@ -93,7 +217,7 @@ static bool test() { EXPECT_CONTAINS(str, "kitty_D"); } - OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 0")); + OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0")); Tests::killAllWindows(); @@ -138,6 +262,7 @@ static bool test() { Tests::spawnKitty("kitty_A"); Tests::spawnKitty("kitty_B"); + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); OK(getFromSocket("/dispatch fullscreen 0 set")); { diff --git a/hyprtester/src/tests/main/scroll.cpp b/hyprtester/src/tests/main/scroll.cpp new file mode 100644 index 00000000..8bb950dd --- /dev/null +++ b/hyprtester/src/tests/main/scroll.cpp @@ -0,0 +1,228 @@ +#include "../shared.hpp" +#include "../../shared.hpp" +#include "../../hyprctlCompat.hpp" +#include "tests.hpp" + +static int ret = 0; + +static void testFocusCycling() { + for (auto const& win : {"a", "b", "c", "d"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + OK(getFromSocket("/dispatch focuswindow class:a")); + + OK(getFromSocket("/dispatch movefocus r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: b"); + } + + OK(getFromSocket("/dispatch movefocus r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: c"); + } + + OK(getFromSocket("/dispatch movefocus r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: d"); + } + + OK(getFromSocket("/dispatch movewindow l")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: d"); + } + + OK(getFromSocket("/dispatch movefocus u")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: c"); + } + + // clean up + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + +static void testFocusWrapping() { + for (auto const& win : {"a", "b", "c", "d"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + // set wrap_focus to true + OK(getFromSocket("/keyword scrolling:wrap_focus true")); + + OK(getFromSocket("/dispatch focuswindow class:a")); + + OK(getFromSocket("/dispatch layoutmsg focus l")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: d"); + } + + OK(getFromSocket("/dispatch layoutmsg focus r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: a"); + } + + // set wrap_focus to false + OK(getFromSocket("/keyword scrolling:wrap_focus false")); + + OK(getFromSocket("/dispatch focuswindow class:a")); + + OK(getFromSocket("/dispatch layoutmsg focus l")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: a"); + } + + OK(getFromSocket("/dispatch focuswindow class:d")); + + OK(getFromSocket("/dispatch layoutmsg focus r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: d"); + } + + // clean up + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + +static void testSwapcolWrapping() { + for (auto const& win : {"a", "b", "c", "d"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + // set wrap_swapcol to true + OK(getFromSocket("/keyword scrolling:wrap_swapcol true")); + + OK(getFromSocket("/dispatch focuswindow class:a")); + + OK(getFromSocket("/dispatch layoutmsg swapcol l")); + OK(getFromSocket("/dispatch layoutmsg focus l")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: c"); + } + + // clean up + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + for (auto const& win : {"a", "b", "c", "d"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + OK(getFromSocket("/dispatch focuswindow class:d")); + OK(getFromSocket("/dispatch layoutmsg swapcol r")); + OK(getFromSocket("/dispatch layoutmsg focus r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: b"); + } + + // clean up + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + for (auto const& win : {"a", "b", "c", "d"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + // set wrap_swapcol to false + OK(getFromSocket("/keyword scrolling:wrap_swapcol false")); + + OK(getFromSocket("/dispatch focuswindow class:a")); + + OK(getFromSocket("/dispatch layoutmsg swapcol l")); + OK(getFromSocket("/dispatch layoutmsg focus r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: b"); + } + + OK(getFromSocket("/dispatch focuswindow class:d")); + + OK(getFromSocket("/dispatch layoutmsg swapcol r")); + OK(getFromSocket("/dispatch layoutmsg focus l")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: c"); + } + + // clean up + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + +static bool test() { + NLog::log("{}Testing Scroll layout", Colors::GREEN); + + // setup + OK(getFromSocket("/dispatch workspace name:scroll")); + OK(getFromSocket("/keyword general:layout scrolling")); + + // test + NLog::log("{}Testing focus cycling", Colors::GREEN); + testFocusCycling(); + + // test + NLog::log("{}Testing focus wrap", Colors::GREEN); + testFocusWrapping(); + + // test + NLog::log("{}Testing swapcol wrap", Colors::GREEN); + testSwapcolWrapping(); + + // clean up + NLog::log("Cleaning up", Colors::YELLOW); + OK(getFromSocket("/dispatch workspace 1")); + OK(getFromSocket("/reload")); + + return !ret; +} + +REGISTER_TEST_FN(test); diff --git a/hyprtester/src/tests/main/tags.cpp b/hyprtester/src/tests/main/tags.cpp index 22bedcde..c345fe71 100644 --- a/hyprtester/src/tests/main/tags.cpp +++ b/hyprtester/src/tests/main/tags.cpp @@ -21,21 +21,24 @@ static bool testTags() { NLog::log("{}Testing testTag tags", Colors::YELLOW); - OK(getFromSocket("/keyword windowrule tag +testTag, class:tagged")); - OK(getFromSocket("/keyword windowrule noshadow, tag:negative:testTag")); - OK(getFromSocket("/keyword windowrule noborder, tag:testTag")); + OK(getFromSocket("/keyword windowrule[tag-test-1]:tag +testTag")); + OK(getFromSocket("/keyword windowrule[tag-test-1]:match:class tagged")); + OK(getFromSocket("/keyword windowrule[tag-test-2]:match:tag negative:testTag")); + OK(getFromSocket("/keyword windowrule[tag-test-2]:no_shadow true")); + OK(getFromSocket("/keyword windowrule[tag-test-3]:match:tag testTag")); + OK(getFromSocket("/keyword windowrule[tag-test-3]:no_dim true")); EXPECT(Tests::windowCount(), 2); OK(getFromSocket("/dispatch focuswindow class:tagged")); - NLog::log("{}Testing tagged window for noborder & noshadow", Colors::YELLOW); + NLog::log("{}Testing tagged window for no_dim 0 & no_shadow", Colors::YELLOW); EXPECT_CONTAINS(getFromSocket("/activewindow"), "testTag"); - EXPECT_CONTAINS(getFromSocket("/getprop activewindow noborder"), "true"); - EXPECT_CONTAINS(getFromSocket("/getprop activewindow noshadow"), "false"); - NLog::log("{}Testing untagged window for noborder & noshadow", Colors::YELLOW); + EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_dim"), "true"); + EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_shadow"), "false"); + NLog::log("{}Testing untagged window for no_dim & no_shadow", Colors::YELLOW); OK(getFromSocket("/dispatch focuswindow class:untagged")); EXPECT_NOT_CONTAINS(getFromSocket("/activewindow"), "testTag"); - EXPECT_CONTAINS(getFromSocket("/getprop activewindow noborder"), "false"); - EXPECT_CONTAINS(getFromSocket("/getprop activewindow noshadow"), "true"); + EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_shadow"), "true"); + EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_dim"), "false"); Tests::killAllWindows(); EXPECT(Tests::windowCount(), 0); diff --git a/hyprtester/src/tests/main/window.cpp b/hyprtester/src/tests/main/window.cpp index 6cfa061c..beac0298 100644 --- a/hyprtester/src/tests/main/window.cpp +++ b/hyprtester/src/tests/main/window.cpp @@ -1,8 +1,13 @@ +#include #include -#include #include +#include +#include +#include +#include #include #include +#include #include "../../shared.hpp" #include "../../hyprctlCompat.hpp" @@ -11,15 +16,37 @@ static int ret = 0; -static bool spawnKitty(const std::string& class_) { +static bool spawnKitty(const std::string& class_, const std::vector& args = {}) { NLog::log("{}Spawning {}", Colors::YELLOW, class_); - if (!Tests::spawnKitty(class_)) { + if (!Tests::spawnKitty(class_, args)) { NLog::log("{}Error: {} did not spawn", Colors::RED, class_); return false; } return true; } +/// Spawns a kitty and creates a file and returns its name. The removal of the file triggers +/// activation of the spawned kitty window. +/// +/// On failure, returns an empty string, possibly leaving a temporary file. +static std::string spawnKittyActivating(const std::string& class_ = "kitty_activating") { + // `XXXXXX` is what `mkstemp` expects to find in the string + std::string tmpFilename = (std::filesystem::temp_directory_path() / "XXXXXX").string(); + int fd = mkstemp(tmpFilename.data()); + if (fd < 0) { + NLog::log("{}Error: could not create tmp file: errno {}", Colors::RED, errno); + return ""; + } + (void)close(fd); + bool ok = + spawnKitty(class_, {"-o", "allow_remote_control=yes", "--", "/bin/sh", "-c", "while [ -f \"" + tmpFilename + "\" ]; do :; done; kitten @ focus-window; sleep infinity"}); + if (!ok) { + NLog::log("{}Error: failed to spawn kitty", Colors::RED); + return ""; + } + return tmpFilename; +} + static std::string getWindowAttribute(const std::string& winInfo, const std::string& attr) { auto pos = winInfo.find(attr); if (pos == std::string::npos) { @@ -66,9 +93,9 @@ static void testSwapWindow() { { getFromSocket("/dispatch focuswindow class:kitty_A"); auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:"); - NLog::log("{}Testing kitty_A {}, swapwindow with direction 'l'", Colors::YELLOW, pos); + NLog::log("{}Testing kitty_A {}, swapwindow with direction 'r'", Colors::YELLOW, pos); - OK(getFromSocket("/dispatch swapwindow l")); + OK(getFromSocket("/dispatch swapwindow r")); OK(getFromSocket("/dispatch focuswindow class:kitty_B")); EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", pos)); @@ -131,6 +158,506 @@ static void testSwapWindow() { EXPECT(Tests::windowCount(), 0); } +static void testGroupRules() { + NLog::log("{}Testing group window rules", Colors::YELLOW); + + OK(getFromSocket("/keyword general:border_size 8")); + OK(getFromSocket("/keyword workspace w[tv1], bordersize:0")); + OK(getFromSocket("/keyword workspace f[1], bordersize:0")); + OK(getFromSocket("/keyword windowrule match:workspace w[tv1], border_size 0")); + OK(getFromSocket("/keyword windowrule match:workspace f[1], border_size 0")); + + if (!Tests::spawnKitty("kitty_A")) { + ret = 1; + return; + } + + { + auto str = getFromSocket("/getprop active border_size"); + EXPECT_CONTAINS(str, "0"); + } + + if (!Tests::spawnKitty("kitty_B")) { + ret = 1; + return; + } + + { + auto str = getFromSocket("/getprop active border_size"); + EXPECT_CONTAINS(str, "8"); + } + + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + OK(getFromSocket("/dispatch togglegroup")); + OK(getFromSocket("/dispatch focuswindow class:kitty_B")); + OK(getFromSocket("/dispatch moveintogroup l")); + + { + auto str = getFromSocket("/getprop active border_size"); + EXPECT_CONTAINS(str, "0"); + } + + OK(getFromSocket("/dispatch changegroupactive f")); + + { + auto str = getFromSocket("/getprop active border_size"); + EXPECT_CONTAINS(str, "0"); + } + + if (!Tests::spawnKitty("kitty_C")) { + ret = 1; + return; + } + + OK(getFromSocket("/dispatch moveoutofgroup r")); + + { + auto str = getFromSocket("/getprop active border_size"); + EXPECT_CONTAINS(str, "8"); + } + + OK(getFromSocket("/reload")); + Tests::killAllWindows(); +} + +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(); + 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; +} + +/// Tests behavior of a window being focused when on that window's workspace +/// another fullscreen window exists. +static bool testWindowFocusOnFullscreenConflict() { + if (!spawnKitty("kitty_A")) + return false; + if (!spawnKitty("kitty_B")) + return false; + + OK(getFromSocket("/keyword misc:focus_on_activate true")); + + // Unfullscreen on conflict + { + OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2")); + + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + OK(getFromSocket("/dispatch fullscreen 0 set")); + EXPECT(isActiveWindow("kitty_A", '2'), true); + + // Dispatch-focus the same window + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + EXPECT(isActiveWindow("kitty_A", '2'), true); + + // Dispatch-focus a different window + OK(getFromSocket("/dispatch focuswindow class:kitty_B")); + EXPECT(isActiveWindow("kitty_B", '0'), true); + + // Make a window that will request focus + const std::string removeToActivate = spawnKittyActivating(); + if (removeToActivate.empty()) + return false; + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + OK(getFromSocket("/dispatch fullscreen 0 set")); + EXPECT(isActiveWindow("kitty_A", '2'), true); + std::filesystem::remove(removeToActivate); + EXPECT(waitForActiveWindow("kitty_activating", '0'), true); + OK(getFromSocket("/dispatch forcekillactive")); + Tests::waitUntilWindowsN(2); + } + + // Take over on conflict + { + OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 1")); + + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + OK(getFromSocket("/dispatch fullscreen 0 set")); + EXPECT(isActiveWindow("kitty_A", '2'), true); + + // Dispatch-focus the same window + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + EXPECT(isActiveWindow("kitty_A", '2'), true); + + // Dispatch-focus a different window + OK(getFromSocket("/dispatch focuswindow class:kitty_B")); + EXPECT(isActiveWindow("kitty_B", '2'), true); + OK(getFromSocket("/dispatch fullscreenstate 0 0")); + + // Make a window that will request focus + const std::string removeToActivate = spawnKittyActivating(); + if (removeToActivate.empty()) + return false; + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + OK(getFromSocket("/dispatch fullscreen 0 set")); + EXPECT(isActiveWindow("kitty_A", '2'), true); + std::filesystem::remove(removeToActivate); + EXPECT(waitForActiveWindow("kitty_activating", '2'), true); + OK(getFromSocket("/dispatch forcekillactive")); + Tests::waitUntilWindowsN(2); + } + + // Keep the old focus on conflict + { + OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0")); + + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + OK(getFromSocket("/dispatch fullscreen 0 set")); + EXPECT(isActiveWindow("kitty_A", '2'), true); + + // Dispatch-focus the same window + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + EXPECT(isActiveWindow("kitty_A", '2'), true); + + // Make a window that will request focus - the setting is treated normally + const std::string removeToActivate = spawnKittyActivating(); + if (removeToActivate.empty()) + return false; + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + OK(getFromSocket("/dispatch fullscreen 0 set")); + EXPECT(isActiveWindow("kitty_A", '2'), true); + std::filesystem::remove(removeToActivate); + EXPECT(waitForActiveWindow("kitty_A", '2'), 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 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 test() { NLog::log("{}Testing windows", Colors::GREEN); @@ -161,13 +688,13 @@ static bool test() { const int MONITOR_H = 1080; const float totalAvailableHeight = MONITOR_H - (GAPSOUT * 2); - const int HEIGHT = std::round(totalAvailableHeight) - BORDERS; + 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::round(boxWidth - gapLeft - gapRight - BORDERS); + return std::floor(boxWidth - gapLeft - gapRight - BORDERS); }; double geomBoxWidthA_R1 = (availableWidthForSplit * INITIAL_RATIO / 2.0) + GAPSOUT + (GAPSIN / 2.0); @@ -197,12 +724,38 @@ static bool test() { if (!spawnKitty("kitty_B")) return false; - NLog::log("{}Expecting kitty_B size: {},{}", Colors::YELLOW, WIDTH2, HEIGHT); - 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 size: {},{}", Colors::YELLOW, WIDTH_A_FINAL, HEIGHT); - EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", 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]}), 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")); } @@ -212,29 +765,20 @@ static bool test() { getFromSocket("/dispatch exec xeyes"); NLog::log("{}Keep checking if xeyes spawned", Colors::YELLOW); - int counter = 0; - while (Tests::windowCount() != 3) { - counter++; - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - if (counter > 50) { - EXPECT(Tests::windowCount(), 3); - return !ret; - } - } + Tests::waitUntilWindowsN(3); NLog::log("{}Expecting 3 windows", Colors::YELLOW); 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 @@ -246,12 +790,45 @@ static bool test() { testSwapWindow(); + getFromSocket("/dispatch workspace 1"); + + if (!testWindowFocusOnFullscreenConflict()) { + ret = 1; + return false; + } + + NLog::log("{}Testing spawning a floating window over a fullscreen window", Colors::YELLOW); + { + if (!spawnKitty("kitty_A")) + return false; + OK(getFromSocket("/dispatch fullscreen 0 set")); + EXPECT(Tests::windowCount(), 1); + + OK(getFromSocket("/dispatch exec [float] kitty")); + Tests::waitUntilWindowsN(2); + + OK(getFromSocket("/dispatch focuswindow class:^kitty$")); + const auto focused1 = getFromSocket("/activewindow"); + EXPECT_CONTAINS(focused1, "class: kitty\n"); + + OK(getFromSocket("/dispatch killwindow activewindow")); + Tests::waitUntilWindowsN(1); + + // The old window should be focused again + const auto focused2 = getFromSocket("/activewindow"); + EXPECT_CONTAINS(focused2, "class: kitty_A\n"); + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + } + NLog::log("{}Testing minsize/maxsize rules for tiled windows", Colors::YELLOW); { // Enable the config for testing, test max/minsize for tiled windows and centering OK(getFromSocket("/keyword misc:size_limits_tiled 1")); - OK(getFromSocket("/keyword windowrule maxsize 1500 500, class:kitty_maxsize")); - OK(getFromSocket("/keyword windowrule minsize 1200 500, class:kitty_maxsize")); + 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; @@ -259,10 +836,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); @@ -279,8 +856,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")); @@ -297,29 +935,194 @@ static bool test() { EXPECT_CONTAINS(str, "floating: 1"); EXPECT_CONTAINS(str, std::format("size: {},{}", SIZE, SIZE)); EXPECT_NOT_CONTAINS(str, "pinned: 1"); - OK(getFromSocket("/keyword windowrule plugin:someplugin:variable, class:wr_kitty")); - OK(getFromSocket("/keyword windowrule plugin:someplugin:variable 10, class:wr_kitty")); - OK(getFromSocket("/keyword windowrule workspace 1, class:wr_kitty")); - OK(getFromSocket("/keyword windowrule workspace special:magic, class:magic_kitty")); + } - if (!spawnKitty("magic_kitty")) - return false; - EXPECT_CONTAINS(getFromSocket("/activewindow"), "special:magic"); + OK(getFromSocket("/keyword windowrule[wr-kitty-stuff]:opacity 0.5 0.5 override")); + + { + auto str = getFromSocket("/getprop active opacity"); + EXPECT_CONTAINS(str, "0.5"); + } + + OK(getFromSocket("/keyword windowrule[special-magic-kitty]:match:class magic_kitty")); + OK(getFromSocket("/keyword windowrule[special-magic-kitty]:workspace special:magic")); + + if (!spawnKitty("magic_kitty")) + return false; + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "special:magic"); EXPECT_NOT_CONTAINS(str, "workspace: 9"); } - NLog::log("{}Testing faulty rules", Colors::YELLOW); - { - const auto PARAM = "Invalid parameter"; - const auto RULE = "Invalid value"; - const auto NORULE = "no rules provided"; - EXPECT_CONTAINS(getFromSocket("/keyword windowrule notarule, class:wr_kitty"), RULE) - EXPECT_CONTAINS(getFromSocket("/keyword windowrule class:wr_kitty"), NORULE) - EXPECT_CONTAINS(getFromSocket("/keyword windowrule float, class:wr_kitty, size"), PARAM) - EXPECT_CONTAINS(getFromSocket("/keyword windowrule float, classI:wr_kitty"), PARAM) - EXPECT_CONTAINS(getFromSocket("/keyword windowrule workspace:, class:wr_kitty"), NORULE) + if (auto str = getFromSocket("/monitors"); str.contains("magic)")) { + OK(getFromSocket("/dispatch togglespecialworkspace magic")); } + 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; + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "floating: 1"); + } + + OK(getFromSocket("/reload")); + Tests::killAllWindows(); + + // test rules that overlap effects but don't overlap props + OK(getFromSocket("/keyword windowrule match:class overlap_kitty, border_size 0")); + OK(getFromSocket("/keyword windowrule match:fullscreen false, border_size 10")); + + if (!spawnKitty("overlap_kitty")) + return false; + + { + auto str = getFromSocket("/getprop active border_size"); + EXPECT_CONTAINS(str, "10"); + } + + OK(getFromSocket("/reload")); + Tests::killAllWindows(); + + // test persistent_size between floating window launches + OK(getFromSocket("/keyword windowrule match:class persistent_size_kitty, persistent_size true, float true")); + + if (!spawnKitty("persistent_size_kitty")) + return false; + + OK(getFromSocket("/dispatch resizeactive exact 600 400")) + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "size: 600,400"); + EXPECT_CONTAINS(str, "floating: 1"); + } + + Tests::killAllWindows(); + + if (!spawnKitty("persistent_size_kitty")) + return false; + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "size: 600,400"); + EXPECT_CONTAINS(str, "floating: 1"); + } + + OK(getFromSocket("/reload")); + Tests::killAllWindows(); + + OK(getFromSocket("/keyword general:border_size 0")); + OK(getFromSocket("/keyword windowrule match:float true, border_size 10")); + + if (!spawnKitty("border_kitty")) + return false; + + { + auto str = getFromSocket("/getprop active border_size"); + EXPECT_CONTAINS(str, "0"); + } + + OK(getFromSocket("/dispatch togglefloating")); + + { + auto str = getFromSocket("/getprop active border_size"); + EXPECT_CONTAINS(str, "10"); + } + + OK(getFromSocket("/dispatch togglefloating")); + + { + auto str = getFromSocket("/getprop active border_size"); + EXPECT_CONTAINS(str, "0"); + } + + OK(getFromSocket("/reload")); + Tests::killAllWindows(); + + // test expression rules + OK(getFromSocket("/keyword windowrule match:class expr_kitty, float yes, size monitor_w*0.5 monitor_h*0.5, min_size monitor_w*0.25 monitor_h*0.25, " + "max_size monitor_w*0.75 monitor_h*0.75, move 20+(monitor_w*0.1) monitor_h*0.5")); + + if (!spawnKitty("expr_kitty")) + return false; + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "floating: 1"); + EXPECT_CONTAINS(str, "at: 212,540"); + EXPECT_CONTAINS(str, "size: 960,540"); + + auto min = getFromSocket("/getprop active min_size"); + EXPECT_CONTAINS(min, "480"); + EXPECT_CONTAINS(min, "270"); + + auto max = getFromSocket("/getprop active max_size"); + EXPECT_CONTAINS(max, "1440"); + EXPECT_CONTAINS(max, "810"); + } + + OK(getFromSocket("/reload")); + Tests::killAllWindows(); + + OK(getFromSocket("/dispatch plugin:test:add_window_rule")); + OK(getFromSocket("/reload")); + + OK(getFromSocket("/keyword windowrule match:class plugin_kitty, plugin_rule effect")); + + if (!spawnKitty("plugin_kitty")) + return false; + + OK(getFromSocket("/dispatch plugin:test:check_window_rule")); + + OK(getFromSocket("/reload")); + Tests::killAllWindows(); + + OK(getFromSocket("/dispatch plugin:test:add_window_rule")); + OK(getFromSocket("/reload")); + + OK(getFromSocket("/keyword windowrule[test-plugin-rule]:match:class plugin_kitty")); + OK(getFromSocket("/keyword windowrule[test-plugin-rule]:plugin_rule effect")); + + if (!spawnKitty("plugin_kitty")) + return false; + + OK(getFromSocket("/dispatch plugin:test:check_window_rule")); + + OK(getFromSocket("/reload")); + Tests::killAllWindows(); + + testGroupRules(); + testMaximizeSize(); + testFloatingFocusOnFullscreen(); + testBringActiveToTopMouseMovement(); + testGroupFallbackFocus(); + testInitialFloatSize(); + testWindowRuleFocusOnActivate(); + testPinnedWorkspacesValid(); + testWindowRuleWorkspaceEmpty(); + NLog::log("{}Reloading config", Colors::YELLOW); OK(getFromSocket("/reload")); diff --git a/hyprtester/src/tests/main/workspaces.cpp b/hyprtester/src/tests/main/workspaces.cpp index def35d08..122cd619 100644 --- a/hyprtester/src/tests/main/workspaces.cpp +++ b/hyprtester/src/tests/main/workspaces.cpp @@ -20,6 +20,379 @@ 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 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); @@ -27,7 +400,7 @@ static bool test() { // test on workspace "window" NLog::log("{}Switching to workspace 1", Colors::YELLOW); - OK(getFromSocket("/dispatch workspace 1")); + getFromSocket("/dispatch workspace 1"); NLog::log("{}Checking persistent no-mon", Colors::YELLOW); OK(getFromSocket("r/keyword workspace 966,persistent:1")); @@ -354,101 +727,19 @@ static bool test() { EXPECT_CONTAINS(str, "class: kitty_B"); } + // kill all + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + testMultimonBAF(); + testMultimonFocus(); + // destroy the headless output OK(getFromSocket("/output remove HEADLESS-3")); - // kill all - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - - 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(); + testSpecialWorkspaceFullscreen(); + testAsymmetricGaps(); + testDynamicWsEffects(); NLog::log("{}Expecting 0 windows", Colors::YELLOW); EXPECT(Tests::windowCount(), 0); diff --git a/hyprtester/src/tests/shared.cpp b/hyprtester/src/tests/shared.cpp index 8cdd648e..f6e2fce9 100644 --- a/hyprtester/src/tests/shared.cpp +++ b/hyprtester/src/tests/shared.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "../shared.hpp" #include "../hyprctlCompat.hpp" @@ -39,6 +40,38 @@ CUniquePointer Tests::spawnKitty(const std::string& class_, const std: return kitty; } +CUniquePointer Tests::spawnLayerKitty(const std::string& namespace_, const std::vector args) { + std::vector programArgs = args; + if (!namespace_.empty()) { + programArgs.insert(programArgs.begin(), "--class"); + programArgs.insert(programArgs.begin() + 1, namespace_); + } + + programArgs.insert(programArgs.begin(), "+kitten"); + programArgs.insert(programArgs.begin() + 1, "panel"); + + CUniquePointer kitty = makeUnique("kitty", programArgs); + kitty->addEnv("WAYLAND_DISPLAY", WLDISPLAY); + kitty->runAsync(); + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + // wait while the layer spawns + int counter = 0; + while (processAlive(kitty->pid()) && countOccurrences(getFromSocket("/layers"), std::format("pid: {}", kitty->pid())) == 0) { + counter++; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + if (counter > 50) + return nullptr; + } + + if (!processAlive(kitty->pid())) + return nullptr; + + return kitty; +} + bool Tests::processAlive(pid_t pid) { errno = 0; int ret = kill(pid, 0); @@ -96,6 +129,38 @@ void Tests::waitUntilWindowsN(int n) { } } +int Tests::layerCount() { + return countOccurrences(getFromSocket("/layers"), "namespace: "); +} + +bool Tests::killAllLayers() { + auto str = getFromSocket("/layers"); + auto pos = str.find("pid: "); + while (pos != std::string::npos) { + auto pid = stoi(str.substr(pos + 5, str.find('\n', pos))); + kill(pid, 15); + + // we need to wait for a bit because for some reason otherwise we'll end up + // with layers with pid -1 if they are all removed at the same time + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + pos = str.find("pid: ", pos + 5); + } + + int counter = 0; + while (Tests::layerCount() != 0) { + counter++; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + if (counter > 50) { + std::println("{}Timed out waiting for layers to close", Colors::RED); + return false; + } + } + + return true; +} + std::string Tests::execAndGet(const std::string& cmd) { CProcess proc("/bin/sh", {"-c", cmd}); @@ -105,3 +170,14 @@ std::string Tests::execAndGet(const std::string& cmd) { return proc.stdOut(); } + +bool Tests::writeFile(const std::string& name, const std::string& contents) { + std::ofstream of(name, std::ios::trunc); + if (!of.good()) + return false; + + of << contents; + of.close(); + + return true; +} diff --git a/hyprtester/src/tests/shared.hpp b/hyprtester/src/tests/shared.hpp index fe28a69d..bf875f8b 100644 --- a/hyprtester/src/tests/shared.hpp +++ b/hyprtester/src/tests/shared.hpp @@ -9,10 +9,14 @@ //NOLINTNEXTLINE namespace Tests { Hyprutils::Memory::CUniquePointer spawnKitty(const std::string& class_ = "", const std::vector args = {}); + Hyprutils::Memory::CUniquePointer spawnLayerKitty(const std::string& namespace_ = "", const std::vector args = {}); bool processAlive(pid_t pid); int windowCount(); int countOccurrences(const std::string& in, const std::string& what); bool killAllWindows(); void waitUntilWindowsN(int n); + int layerCount(); + bool killAllLayers(); std::string execAndGet(const std::string& cmd); + bool writeFile(const std::string& name, const std::string& contents); }; diff --git a/hyprtester/test.conf b/hyprtester/test.conf index 047001c2..ab4f8ee3 100644 --- a/hyprtester/test.conf +++ b/hyprtester/test.conf @@ -179,6 +179,17 @@ master { new_status = master } +scrolling { + fullscreen_on_one_column = true + column_width = 0.5 + focus_fit_method = 1 + follow_focus = true + follow_min_visible = 1 + explicit_column_widths = 0.25, 0.333, 0.5, 0.667, 0.75, 1.0 + wrap_focus = true + wrap_swapcol = true +} + # https://wiki.hyprland.org/Configuring/Variables/#misc misc { force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers @@ -239,7 +250,7 @@ bind = $mainMod, E, exec, $fileManager bind = $mainMod, V, togglefloating, bind = $mainMod, R, exec, $menu bind = $mainMod, P, pseudo, # dwindle -bind = $mainMod, J, togglesplit, # dwindle +bind = $mainMod, J, layoutmsg, togglesplit, # dwindle # Move focus with mainMod + arrow keys bind = $mainMod, left, movefocus, l @@ -318,28 +329,70 @@ submap = reset ### WINDOWS AND WORKSPACES ### ############################## -# See https://wiki.hyprland.org/Configuring/Window-Rules/ for more -# See https://wiki.hyprland.org/Configuring/Workspace-Rules/ for workspace rules +windowrule { + # Ignore maximize requests from apps. You'll probably like this. + name = suppress-maximize-events + match:class = .* -# Example windowrule v1 -# windowrule = float, ^(kitty)$ + suppress_event = maximize +} -# Example windowrule v2 -# windowrulev2 = float,class:^(kitty)$,title:^(kitty)$ +windowrule { + # Fix some dragging issues with XWayland + name = fix-xwayland-drags + match:class = ^$ + match:title = ^$ + match:xwayland = true + match:float = true + match:fullscreen = false + match:pin = false -# Ignore maximize requests from apps. You'll probably like this. -windowrulev2 = suppressevent maximize, class:.* + no_focus = true +} -# Fix some dragging issues with XWayland -windowrulev2 = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0 - -# Workspace "windows" is a smart gaps one workspace = n[s:window] w[tv1], gapsout:0, gapsin:0 workspace = n[s:window] f[1], gapsout:0, gapsin:0 -windowrulev2 = bordersize 0, floating:0, onworkspace:n[s:window] w[tv1] -windowrulev2 = rounding 0, floating:0, onworkspace:n[s:window] w[tv1] -windowrulev2 = bordersize 0, floating:0, onworkspace:n[s:window] f[1] -windowrulev2 = rounding 0, floating:0, onworkspace:n[s:window] f[1] + +windowrule { + name = smart-gaps-1 + match:float = false + match:workspace = n[s:window] w[tv1] + + border_size = 0 + rounding = 0 +} + +windowrule { + name = smart-gaps-2 + match:float = false + match:workspace = n[s:window] f[1] + + border_size = 0 + rounding = 0 +} + +windowrule { + name = wr-kitty-stuff + match:class = wr_kitty + + float = true + size = 200 200 + pin = false +} + +windowrule { + name = tagged-kitty-floats + match:tag = tag_kitty + + float = true +} + +windowrule { + name = static-kitty-tag + match:class = tag_kitty + + tag = +tag_kitty +} gesture = 3, left, dispatcher, exec, kitty gesture = 3, right, float @@ -357,6 +410,4 @@ gesture = 5, right, dispatcher, sendshortcut, , t, activewindow gesture = 4, right, dispatcher, sendshortcut, , return, activewindow gesture = 4, left, dispatcher, movecursortocorner, 1 -windowrule = float, pin, class:wr_kitty -windowrule = size 200 200, class:wr_kitty -windowrule = unset pin, class:wr_kitty +gesturep = 2, right, float diff --git a/meson.build b/meson.build deleted file mode 100644 index 710da6be..00000000 --- a/meson.build +++ /dev/null @@ -1,151 +0,0 @@ -project( - 'Hyprland', - 'cpp', - 'c', - version: run_command('cat', join_paths(meson.project_source_root(), 'VERSION'), check: true).stdout().strip(), - default_options: [ - 'warning_level=2', - 'default_library=static', - 'optimization=3', - 'buildtype=release', - 'debug=false', - 'b_lto=false', - 'cpp_std=c++26', - ], - meson_version: '>= 1.1.0', -) - -datarootdir = '-DDATAROOTDIR="' + get_option('prefix') / get_option('datadir') + '"' -add_project_arguments( - [ - '-Wno-unused-parameter', - '-Wno-unused-value', - '-Wno-missing-field-initializers', - '-Wno-narrowing', - '-Wno-pointer-arith', - datarootdir, - '-DHYPRLAND_VERSION="' + meson.project_version() + '"', - ], - language: 'cpp', -) - -cpp_compiler = meson.get_compiler('cpp') -if cpp_compiler.check_header('execinfo.h') - add_project_arguments('-DHAS_EXECINFO', language: 'cpp') -endif - -aquamarine = dependency('aquamarine', version: '>=0.9.3') -hyprcursor = dependency('hyprcursor', version: '>=0.1.7') -hyprgraphics = dependency('hyprgraphics', version: '>=0.1.6') -hyprlang = dependency('hyprlang', version: '>=0.3.2') -hyprutils = dependency('hyprutils', version: '>=0.8.2') - -aq_ver_list = aquamarine.version().split('.') -git = find_program('git', required: false) - -if git.found() - git_hash = run_command(git, 'rev-parse', 'HEAD').stdout().strip() - git_branch = run_command(git, 'branch', '--show-current').stdout().strip() - git_message = run_command(git, 'show', '-s', '--format=%s').stdout().strip() - git_date = run_command(git, 'show', '-s', '--format=%cd', '--date=local').stdout().strip() - git_dirty = run_command(git, 'diff-index', '--quiet', 'HEAD', '--', check: false).returncode() != 0 ? 'dirty' : 'clean' - git_tag = run_command(git, 'describe', '--tags').stdout().strip() - git_commits = run_command(git, 'rev-list', '--count', 'HEAD').stdout().strip() -else - git_hash = 'unknown' - git_branch = 'unknown' - git_message = 'unknown' - git_date = 'unknown' - git_dirty = 'unknown' - git_tag = 'unknown' - git_commits = '0' -endif - -cfg = configuration_data() -cfg.set('GIT_COMMIT_HASH', git_hash) -cfg.set('GIT_BRANCH', git_branch) -cfg.set('GIT_COMMIT_MESSAGE', git_message) -cfg.set('GIT_COMMIT_DATE', git_date) -cfg.set('GIT_DIRTY', git_dirty) -cfg.set('GIT_TAG', git_tag) -cfg.set('GIT_COMMITS', git_commits) -cfg.set('AQUAMARINE_VERSION', aquamarine.version()) -cfg.set('AQUAMARINE_VERSION_MAJOR', aq_ver_list[0]) -cfg.set('AQUAMARINE_VERSION_MINOR', aq_ver_list[1]) -cfg.set('AQUAMARINE_VERSION_PATCH', aq_ver_list[2]) -cfg.set('HYPRLANG_VERSION', hyprlang.version()) -cfg.set('HYPRUTILS_VERSION', hyprutils.version()) -cfg.set('HYPRCURSOR_VERSION', hyprcursor.version()) -cfg.set('HYPRGRAPHICS_VERSION', hyprgraphics.version()) - -version_h = configure_file( - input: 'src/version.h.in', - output: 'version.h', - configuration: cfg -) - -install_headers(version_h, subdir: 'src') - -xcb_dep = dependency('xcb', required: get_option('xwayland')) -xcb_composite_dep = dependency('xcb-composite', required: get_option('xwayland')) -xcb_errors_dep = dependency('xcb-errors', required: get_option('xwayland')) -xcb_icccm_dep = dependency('xcb-icccm', required: get_option('xwayland')) -xcb_render_dep = dependency('xcb-render', required: get_option('xwayland')) -xcb_res_dep = dependency('xcb-res', required: get_option('xwayland')) -xcb_xfixes_dep = dependency('xcb-xfixes', required: get_option('xwayland')) -gio_dep = dependency('gio-2.0', required: true) - -if not xcb_dep.found() - add_project_arguments('-DNO_XWAYLAND', language: 'cpp') -endif - -backtrace_dep = cpp_compiler.find_library('execinfo', required: false) -epoll_dep = dependency('epoll-shim', required: false) -inotify_dep = dependency('libinotify', required: false) -re2 = dependency('re2', required: true) - -systemd_option = get_option('systemd') -systemd = dependency('systemd', required: systemd_option) -systemd_option.enable_auto_if(systemd.found()) -if (systemd_option.enabled()) - add_project_arguments('-DUSES_SYSTEMD', language: 'cpp') - subdir('systemd') -endif - -if get_option('buildtype') == 'debug' - add_project_arguments('-DHYPRLAND_DEBUG', language: 'cpp') -endif - -run_command('sh', '-c', 'scripts/generateShaderIncludes.sh', check: true) - -globber = run_command('find', 'src', '-name', '*.h*', '-o', '-name', '*.inc', check: true) -headers = globber.stdout().strip().split('\n') -foreach file : headers - install_headers(file, subdir: 'hyprland', preserve_path: true) -endforeach -install_headers(version_h, subdir: 'src') - -tracy = dependency('tracy', static: true, required: get_option('tracy_enable')) -if get_option('tracy_enable') and get_option('buildtype') != 'debugoptimized' - warning('Profiling builds should set -- buildtype = debugoptimized') -endif - -subdir('protocols') -subdir('src') -subdir('hyprctl') -subdir('assets') -subdir('example') -subdir('docs') -if get_option('hyprpm').enabled() - subdir('hyprpm/src') -endif - -pkg_install_dir = join_paths(get_option('datadir'), 'pkgconfig') -import('pkgconfig').generate( - name: 'Hyprland', - filebase: 'hyprland', - url: 'https://github.com/hyprwm/Hyprland', - description: 'Hyprland header files', - install_dir: pkg_install_dir, - subdirs: ['', 'hyprland/protocols', 'hyprland'], -) diff --git a/meson_options.txt b/meson_options.txt deleted file mode 100644 index e50b4cce..00000000 --- a/meson_options.txt +++ /dev/null @@ -1,5 +0,0 @@ -option('xwayland', type: 'feature', value: 'auto', description: 'Enable support for X11 applications') -option('systemd', type: 'feature', value: 'auto', description: 'Enable systemd integration') -option('uwsm', type: 'feature', value: 'enabled', description: 'Enable uwsm integration (only if systemd is enabled)') -option('hyprpm', type: 'feature', value: 'enabled', description: 'Enable hyprpm') -option('tracy_enable', type: 'boolean', value: false , description: 'Enable profiling') diff --git a/nix/default.nix b/nix/default.nix index 0fe57191..bc5ba309 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,13 +21,22 @@ hyprlang, hyprutils, hyprwayland-scanner, + hyprwire, + lcms2, libGL, libdrm, libexecinfo, libinput, + libxcb, + libxcb-errors, + libxcb-render-util, + libxcb-wm, + libxdmcp, + libxcursor, libxkbcommon, libuuid, libgbm, + muparser, pango, pciutils, re2, @@ -35,9 +46,9 @@ wayland, wayland-protocols, wayland-scanner, - xorg, xwayland, debug ? false, + withTests ? false, enableXWayland ? true, withSystemd ? lib.meta.availableOn stdenv.hostPlatform systemd, wrapRuntimeDeps ? true, @@ -45,18 +56,30 @@ commit, revCount, date, - withHyprtester ? false, # deprecated flags enableNvidiaPatches ? false, nvidiaPatches ? false, hidpiXWayland ? false, legacyRenderer ? false, + withHyprtester ? false, }: 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 [ @@ -68,11 +91,13 @@ in assert assertMsg (!nvidiaPatches) "The option `nvidiaPatches` has been removed."; assert assertMsg (!enableNvidiaPatches) "The option `enableNvidiaPatches` has been removed."; - assert assertMsg (!hidpiXWayland) "The option `hidpiXWayland` has been removed. Please refer https://wiki.hypr.land/Configuring/XWayland"; + assert assertMsg (!hidpiXWayland) + "The option `hidpiXWayland` has been removed. Please refer https://wiki.hypr.land/Configuring/XWayland"; assert assertMsg (!legacyRenderer) "The option `legacyRenderer` has been removed. Legacy renderer is no longer supported."; + assert assertMsg (!withHyprtester) "The option `withHyprtester` has been removed. Hyprtester is always built now."; customStdenv.mkDerivation (finalAttrs: { pname = "hyprland${optionalString debug "-debug"}"; - inherit version; + inherit version withTests; src = fs.toSource { root = ../.; @@ -80,37 +105,50 @@ in fs.intersection # allows non-flake builds to only include files tracked by git (fs.gitTracked ../.) - (fs.unions (flatten [ - ../assets/hyprland-portals.conf - ../assets/install - ../hyprctl - ../hyprland.pc.in - ../LICENSE - ../protocols - ../src - ../systemd - ../VERSION - (fs.fileFilter (file: file.hasExt "1") ../docs) - (fs.fileFilter (file: file.hasExt "conf" || file.hasExt "desktop") ../example) - (fs.fileFilter (file: file.hasExt "sh") ../scripts) - (fs.fileFilter (file: file.name == "CMakeLists.txt") ../.) - (optional withHyprtester ../hyprtester) - ])); + ( + fs.unions (flatten [ + ../assets/hyprland-portals.conf + ../assets/install + ../hyprctl + ../hyprland.pc.in + ../hyprpm + ../LICENSE + ../protocols + ../src + ../start + ../systemd + ../VERSION + (fs.fileFilter (file: file.hasExt "1") ../docs) + (fs.fileFilter (file: file.hasExt "conf" || file.hasExt "in") ../example) + (fs.fileFilter (file: file.hasExt "sh") ../scripts) + (fs.fileFilter (file: file.name == "CMakeLists.txt") ../.) + (optional withTests [ + ../tests + ../hyprtester + ]) + ]) + ); }; postPatch = '' # Fix hardcoded paths to /usr installation sed -i "s#/usr#$out#" src/render/OpenGL.cpp - # Remove extra @PREFIX@ to fix pkg-config paths + # Remove extra @PREFIX@ to fix some paths sed -i "s#@PREFIX@/##g" hyprland.pc.in + sed -i "s#@PREFIX@/##g" example/hyprland.desktop.in ''; - COMMITS = revCount; - DATE = date; - DIRTY = optionalString (commit == "") "dirty"; - HASH = commit; - 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 @@ -118,6 +156,7 @@ in nativeBuildInputs = [ hyprwayland-scanner + hyprwire makeWrapper cmake pkg-config @@ -134,18 +173,24 @@ in aquamarine cairo git - glaze + glaze-hyprland + glslang + gtest hyprcursor hyprgraphics hyprland-protocols hyprlang hyprutils + hyprwire + lcms2 libdrm + libgbm libGL libinput libuuid + libxcursor libxkbcommon - libgbm + muparser pango pciutils re2 @@ -154,16 +199,15 @@ in wayland wayland-protocols wayland-scanner - xorg.libXcursor ] (optionals customStdenv.hostPlatform.isBSD [epoll-shim]) (optionals customStdenv.hostPlatform.isMusl [libexecinfo]) (optionals enableXWayland [ - xorg.libxcb - xorg.libXdmcp - xorg.xcbutilerrors - xorg.xcbutilrenderutil - xorg.xcbutilwm + libxcb + libxcb-errors + libxcb-render-util + libxcb-wm + libxdmcp xwayland ]) (optional withSystemd systemd) @@ -180,14 +224,14 @@ in dontStrip = debug; cmakeFlags = mapAttrsToList cmakeBool { + "BUILT_WITH_NIX" = true; "NO_XWAYLAND" = !enableXWayland; "LEGACY_RENDERER" = legacyRenderer; "NO_SYSTEMD" = !withSystemd; "CMAKE_DISABLE_PRECOMPILE_HEADERS" = true; - "NO_UWSM" = true; - "NO_HYPRPM" = true; + "NO_UWSM" = !withSystemd; "TRACY_ENABLE" = false; - "BUILD_HYPRTESTER" = withHyprtester; + "WITH_TESTS" = withTests; }; preConfigure = '' @@ -199,19 +243,26 @@ in postInstall = '' ${optionalString wrapRuntimeDeps '' wrapProgram $out/bin/Hyprland \ - --suffix PATH : ${makeBinPath [ - binutils - hyprland-guiutils - pciutils - pkgconf - ]} + --suffix PATH : ${ + makeBinPath [ + binutils + hyprland-guiutils + pciutils + pkgconf + ] + } + ''} + + ${optionalString withTests '' + install hyprtester/pointer-warp -t $out/bin + install hyprtester/pointer-scroll -t $out/bin + install hyprtester/shortcut-inhibitor -t $out/bin + install hyprland_gtests -t $out/bin + install hyprtester/child-window -t $out/bin ''} - '' + optionalString withHyprtester '' - install hyprtester/pointer-warp -t $out/bin - install hyprtester/pointer-scroll -t $out/bin ''; - passthru.providedSessions = ["hyprland"]; + passthru.providedSessions = ["hyprland"] ++ optionals withSystemd ["hyprland-uwsm"]; meta = { homepage = "https://github.com/hyprwm/Hyprland"; diff --git a/nix/formatter.nix b/nix/formatter.nix index 66721c2c..ac340ae2 100644 --- a/nix/formatter.nix +++ b/nix/formatter.nix @@ -2,7 +2,7 @@ writeShellApplication, deadnix, statix, - alejandra, + nixfmt, llvmPackages_19, fd, }: @@ -11,7 +11,7 @@ writeShellApplication { runtimeInputs = [ deadnix statix - alejandra + nixfmt llvmPackages_19.clang-tools fd ]; @@ -24,14 +24,14 @@ writeShellApplication { nix_format() { if [ "$*" = 0 ]; then fd '.*\.nix' . -E "$excludes" -x statix fix -- {} \; - fd '.*\.nix' . -E "$excludes" -X deadnix -e -- {} \; -X alejandra {} \; + fd '.*\.nix' . -E "$excludes" -X deadnix -e -- {} \; -X nixfmt {} \; elif [ -d "$1" ]; then fd '.*\.nix' "$1" -E "$excludes" -i -x statix fix -- {} \; - fd '.*\.nix' "$1" -E "$excludes" -i -X deadnix -e -- {} \; -X alejandra {} \; + fd '.*\.nix' "$1" -E "$excludes" -i -X deadnix -e -- {} \; -X nixfmt {} \; else statix fix -- "$1" deadnix -e "$1" - alejandra "$1" + nixfmt "$1" fi } diff --git a/nix/hm-module.nix b/nix/hm-module.nix index e3c788d0..948b8217 100644 --- a/nix/hm-module.nix +++ b/nix/hm-module.nix @@ -1,13 +1,15 @@ -self: { - config, +self: +{ lib, pkgs, ... -}: let +}: +let inherit (pkgs.stdenv.hostPlatform) system; package = self.packages.${system}.default; -in { +in +{ config = { wayland.windowManager.hyprland.package = lib.mkDefault package; }; diff --git a/nix/lib.nix b/nix/lib.nix index ca3aadee..54d23440 100644 --- a/nix/lib.nix +++ b/nix/lib.nix @@ -1,4 +1,5 @@ -lib: let +lib: +let inherit (lib) attrNames filterAttrs @@ -17,7 +18,7 @@ lib: let This function takes a nested attribute set and converts it into Hyprland-compatible configuration syntax, supporting top, bottom, and regular command sections. - + Commands are flattened using the `flattenAttrs` function, and attributes are formatted as `key = value` pairs. Lists are expanded as duplicate keys to match Hyprland's expected format. @@ -81,44 +82,51 @@ lib: let ::: */ - toHyprlang = { - topCommandsPrefixes ? ["$" "bezier"], - bottomCommandsPrefixes ? [], - }: attrs: let - toHyprlang' = attrs: let - # Specially configured `toKeyValue` generator with support for duplicate keys - # and a legible key-value separator. - mkCommands = generators.toKeyValue { - mkKeyValue = generators.mkKeyValueDefault {} " = "; - listsAsDuplicateKeys = true; - indent = ""; # No indent, since we don't have nesting - }; + toHyprlang = + { + topCommandsPrefixes ? [ + "$" + "bezier" + ], + bottomCommandsPrefixes ? [ ], + }: + attrs: + let + toHyprlang' = + attrs: + let + # Specially configured `toKeyValue` generator with support for duplicate keys + # and a legible key-value separator. + mkCommands = generators.toKeyValue { + mkKeyValue = generators.mkKeyValueDefault { } " = "; + listsAsDuplicateKeys = true; + indent = ""; # No indent, since we don't have nesting + }; - # Flatten the attrset, combining keys in a "path" like `"a:b:c" = "x"`. - # Uses `flattenAttrs` with a colon separator. - commands = flattenAttrs (p: k: "${p}:${k}") attrs; + # Flatten the attrset, combining keys in a "path" like `"a:b:c" = "x"`. + # Uses `flattenAttrs` with a colon separator. + commands = flattenAttrs (p: k: "${p}:${k}") attrs; - # General filtering function to check if a key starts with any prefix in a given list. - filterCommands = list: n: - foldl (acc: prefix: acc || hasPrefix prefix n) false list; + # General filtering function to check if a key starts with any prefix in a given list. + filterCommands = list: n: foldl (acc: prefix: acc || hasPrefix prefix n) false list; - # Partition keys into top commands and the rest - result = partition (filterCommands topCommandsPrefixes) (attrNames commands); - topCommands = filterAttrs (n: _: builtins.elem n result.right) commands; - remainingCommands = removeAttrs commands result.right; + # Partition keys into top commands and the rest + result = partition (filterCommands topCommandsPrefixes) (attrNames commands); + topCommands = filterAttrs (n: _: builtins.elem n result.right) commands; + remainingCommands = removeAttrs commands result.right; - # Partition remaining commands into bottom commands and regular commands - result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong; - bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands; - regularCommands = removeAttrs remainingCommands result2.right; + # Partition remaining commands into bottom commands and regular commands + result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong; + bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands; + regularCommands = removeAttrs remainingCommands result2.right; + in + # Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands. + concatMapStrings mkCommands [ + topCommands + regularCommands + bottomCommands + ]; in - # Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands. - concatMapStrings mkCommands [ - topCommands - regularCommands - bottomCommands - ]; - in toHyprlang' attrs; /** @@ -131,7 +139,7 @@ lib: let Configuration: * `pred` - A function `(string -> string -> string)` defining how keys should be concatenated. - + # Inputs Structured function argument: @@ -139,7 +147,7 @@ lib: let : pred (required) : A function that determines how parent and child keys should be combined into a single key. It takes a `prefix` (parent key) and `key` (current key) and returns the joined key. - + Value: : The nested attribute set to be flattened. @@ -174,26 +182,21 @@ lib: let ``` ::: - */ - flattenAttrs = pred: attrs: let - flattenAttrs' = prefix: attrs: - builtins.foldl' ( - acc: key: let - value = attrs.${key}; - newKey = - if prefix == "" - then key - else pred prefix key; - in - acc - // ( - if builtins.isAttrs value - then flattenAttrs' newKey value - else {"${newKey}" = value;} - ) - ) {} (builtins.attrNames attrs); - in + flattenAttrs = + pred: attrs: + let + flattenAttrs' = + prefix: attrs: + builtins.foldl' ( + acc: key: + let + value = attrs.${key}; + newKey = if prefix == "" then key else pred prefix key; + in + acc // (if builtins.isAttrs value then flattenAttrs' newKey value else { "${newKey}" = value; }) + ) { } (builtins.attrNames attrs); + in flattenAttrs' "" attrs; in { diff --git a/nix/module.nix b/nix/module.nix index 91705347..32263943 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -1,18 +1,21 @@ -inputs: { +inputs: +{ config, lib, pkgs, ... -}: let +}: +let inherit (pkgs.stdenv.hostPlatform) system; selflib = import ./lib.nix lib; cfg = config.programs.hyprland; -in { +in +{ options = { programs.hyprland = { plugins = lib.mkOption { type = with lib.types; listOf (either package path); - default = []; + default = [ ]; description = '' List of Hyprland plugins to use. Can either be packages or absolute plugin paths. @@ -20,23 +23,25 @@ in { }; settings = lib.mkOption { - type = with lib.types; let - valueType = - nullOr (oneOf [ - bool - int - float - str - path - (attrsOf valueType) - (listOf valueType) - ]) - // { - description = "Hyprland configuration value"; - }; - in + type = + with lib.types; + let + valueType = + nullOr (oneOf [ + bool + int + float + str + path + (attrsOf valueType) + (listOf valueType) + ]) + // { + description = "Hyprland configuration value"; + }; + in valueType; - default = {}; + default = { }; description = '' Hyprland configuration written in Nix. Entries with the same key should be written as lists. Variables' and colors' names should be @@ -92,8 +97,15 @@ in { topPrefixes = lib.mkOption { type = with lib.types; listOf str; - default = ["$" "bezier"]; - example = ["$" "bezier" "source"]; + default = [ + "$" + "bezier" + ]; + example = [ + "$" + "bezier" + "source" + ]; description = '' List of prefix of attributes to put at the top of the config. ''; @@ -101,8 +113,8 @@ in { bottomPrefixes = lib.mkOption { type = with lib.types; listOf str; - default = []; - example = ["source"]; + default = [ ]; + example = [ "source" ]; description = '' List of prefix of attributes to put at the bottom of the config. ''; @@ -117,35 +129,36 @@ in { }; } (lib.mkIf cfg.enable { - environment.etc."xdg/hypr/hyprland.conf" = let - shouldGenerate = cfg.extraConfig != "" || cfg.settings != {} || cfg.plugins != []; + environment.etc."xdg/hypr/hyprland.conf" = + let + shouldGenerate = cfg.extraConfig != "" || cfg.settings != { } || cfg.plugins != [ ]; - pluginsToHyprlang = plugins: - selflib.toHyprlang { - topCommandsPrefixes = cfg.topPrefixes; - bottomCommandsPrefixes = cfg.bottomPrefixes; - } - { - "exec-once" = let - mkEntry = entry: - if lib.types.package.check entry - then "${entry}/lib/lib${entry.pname}.so" - else entry; - hyprctl = lib.getExe' config.programs.hyprland.package "hyprctl"; - in - map (p: "${hyprctl} plugin load ${mkEntry p}") cfg.plugins; - }; - in - lib.mkIf shouldGenerate { - text = - lib.optionalString (cfg.plugins != []) - (pluginsToHyprlang cfg.plugins) - + lib.optionalString (cfg.settings != {}) - (selflib.toHyprlang { + pluginsToHyprlang = + _plugins: + selflib.toHyprlang + { topCommandsPrefixes = cfg.topPrefixes; bottomCommandsPrefixes = cfg.bottomPrefixes; } - cfg.settings) + { + "exec-once" = + let + mkEntry = + entry: if lib.types.package.check entry then "${entry}/lib/lib${entry.pname}.so" else entry; + hyprctl = lib.getExe' config.programs.hyprland.package "hyprctl"; + in + map (p: "${hyprctl} plugin load ${mkEntry p}") cfg.plugins; + }; + in + lib.mkIf shouldGenerate { + text = + lib.optionalString (cfg.plugins != [ ]) (pluginsToHyprlang cfg.plugins) + + lib.optionalString (cfg.settings != { }) ( + selflib.toHyprlang { + topCommandsPrefixes = cfg.topPrefixes; + bottomCommandsPrefixes = cfg.bottomPrefixes; + } cfg.settings + ) + lib.optionalString (cfg.extraConfig != "") cfg.extraConfig; }; }) diff --git a/nix/overlays.nix b/nix/overlays.nix index 7f6bf2ae..0d157701 100644 --- a/nix/overlays.nix +++ b/nix/overlays.nix @@ -2,20 +2,27 @@ self, lib, inputs, -}: let - mkDate = longDate: (lib.concatStringsSep "-" [ - (builtins.substring 0 4 longDate) - (builtins.substring 4 2 longDate) - (builtins.substring 6 2 longDate) - ]); +}: +let + mkDate = + longDate: + (lib.concatStringsSep "-" [ + (builtins.substring 0 4 longDate) + (builtins.substring 4 2 longDate) + (builtins.substring 6 2 longDate) + ]); ver = lib.removeSuffix "\n" (builtins.readFile ../VERSION); -in { +in +{ # Contains what a user is most likely to care about: # Hyprland itself, XDPH and the Share Picker. - default = lib.composeManyExtensions (with self.overlays; [ - hyprland-packages - hyprland-extras - ]); + default = lib.composeManyExtensions ( + with self.overlays; + [ + hyprland-packages + hyprland-extras + ] + ); # Packages for variations of Hyprland, dependencies included. hyprland-packages = lib.composeManyExtensions [ @@ -28,45 +35,50 @@ in { inputs.hyprlang.overlays.default inputs.hyprutils.overlays.default inputs.hyprwayland-scanner.overlays.default + inputs.hyprwire.overlays.default self.overlays.udis86 + self.overlays.glaze # Hyprland packages themselves - (final: _prev: let - date = mkDate (self.lastModifiedDate or "19700101"); - version = "${ver}+date=${date}_${self.shortRev or "dirty"}"; - in { - hyprland = final.callPackage ./default.nix { - stdenv = final.gcc15Stdenv; - commit = self.rev or ""; - revCount = self.sourceInfo.revCount or ""; - inherit date version; - }; - hyprland-unwrapped = final.hyprland.override {wrapRuntimeDeps = false;}; + ( + final: _prev: + let + date = mkDate (self.lastModifiedDate or "19700101"); + version = "${ver}+date=${date}_${self.shortRev or "dirty"}"; + in + { + hyprland = final.callPackage ./default.nix { + stdenv = final.gcc15Stdenv; + commit = self.rev or ""; + revCount = self.sourceInfo.revCount or ""; + inherit date version; + }; + hyprland-unwrapped = final.hyprland.override { wrapRuntimeDeps = false; }; - hyprland-with-hyprtester = final.hyprland.override {withHyprtester = true;}; + hyprland-with-tests = final.hyprland.override { withTests = true; }; - # deprecated packages - hyprland-legacy-renderer = - builtins.trace '' + 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; + '' final.hyprland; - hyprland-nvidia = - builtins.trace '' + hyprland-nvidia = builtins.trace '' hyprland-nvidia was removed. Please use the hyprland package. Nvidia patches are no longer needed. - '' - final.hyprland; + '' final.hyprland; - hyprland-hidpi = - builtins.trace '' + hyprland-hidpi = builtins.trace '' hyprland-hidpi was removed. Please use the hyprland package. For more information, refer to https://wiki.hypr.land/Configuring/XWayland. - '' - final.hyprland; - }) + '' final.hyprland; + } + ) ]; # Debug @@ -74,10 +86,10 @@ in { # Dependencies self.overlays.hyprland-packages - (final: prev: { - aquamarine = prev.aquamarine.override {debug = true;}; - hyprutils = prev.hyprutils.override {debug = true;}; - hyprland-debug = prev.hyprland.override {debug = true;}; + (_final: prev: { + aquamarine = prev.aquamarine.override { debug = true; }; + hyprutils = prev.hyprutils.override { debug = true; }; + hyprland-debug = prev.hyprland.override { debug = true; }; }) ]; @@ -91,15 +103,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 d7c00061..25c4077b 100644 --- a/nix/tests/default.nix +++ b/nix/tests/default.nix @@ -1,73 +1,86 @@ -inputs: pkgs: let +inputs: pkgs: +let flake = inputs.self.packages.${pkgs.stdenv.hostPlatform.system}; - hyprland = flake.hyprland-with-hyprtester; -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') + # 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]}'") @@ -76,6 +89,7 @@ in { # 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") diff --git a/protocols/frog-color-management-v1.xml b/protocols/frog-color-management-v1.xml deleted file mode 100644 index aab235a7..00000000 --- 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/meson.build b/protocols/meson.build deleted file mode 100644 index 33663fa3..00000000 --- a/protocols/meson.build +++ /dev/null @@ -1,119 +0,0 @@ -wayland_protos = dependency( - 'wayland-protocols', - version: '>=1.45', - fallback: 'wayland-protocols', - default_options: ['tests=false'], -) - -hyprland_protos = dependency( - 'hyprland-protocols', - version: '>=0.6.4', - fallback: 'hyprland-protocols', -) - -wayland_protocol_dir = wayland_protos.get_variable('pkgdatadir') -hyprland_protocol_dir = hyprland_protos.get_variable('pkgdatadir') - -hyprwayland_scanner_dep = dependency('hyprwayland-scanner', version: '>=0.3.10', native: true) -hyprwayland_scanner = find_program( - hyprwayland_scanner_dep.get_variable('hyprwayland_scanner'), - native: true, -) - -protocols = [ - 'wlr-gamma-control-unstable-v1.xml', - 'wlr-foreign-toplevel-management-unstable-v1.xml', - 'wlr-output-power-management-unstable-v1.xml', - 'input-method-unstable-v2.xml', - 'virtual-keyboard-unstable-v1.xml', - 'wlr-virtual-pointer-unstable-v1.xml', - 'wlr-output-management-unstable-v1.xml', - 'kde-server-decoration.xml', - 'wlr-layer-shell-unstable-v1.xml', - 'wayland-drm.xml', - 'wlr-data-control-unstable-v1.xml', - 'wlr-screencopy-unstable-v1.xml', - 'xx-color-management-v4.xml', - 'frog-color-management-v1.xml', - hyprland_protocol_dir / 'protocols/hyprland-global-shortcuts-v1.xml', - hyprland_protocol_dir / 'protocols/hyprland-toplevel-export-v1.xml', - hyprland_protocol_dir / 'protocols/hyprland-toplevel-mapping-v1.xml', - hyprland_protocol_dir / 'protocols/hyprland-focus-grab-v1.xml', - hyprland_protocol_dir / 'protocols/hyprland-ctm-control-v1.xml', - hyprland_protocol_dir / 'protocols/hyprland-surface-v1.xml', - hyprland_protocol_dir / 'protocols/hyprland-lock-notify-v1.xml', - wayland_protocol_dir / 'staging/tearing-control/tearing-control-v1.xml', - wayland_protocol_dir / 'staging/fractional-scale/fractional-scale-v1.xml', - wayland_protocol_dir / 'unstable/xdg-output/xdg-output-unstable-v1.xml', - wayland_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml', - wayland_protocol_dir / 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml', - wayland_protocol_dir / 'unstable/relative-pointer/relative-pointer-unstable-v1.xml', - wayland_protocol_dir / 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml', - wayland_protocol_dir / 'staging/alpha-modifier/alpha-modifier-v1.xml', - wayland_protocol_dir / 'staging/ext-foreign-toplevel-list/ext-foreign-toplevel-list-v1.xml', - wayland_protocol_dir / 'unstable/pointer-gestures/pointer-gestures-unstable-v1.xml', - wayland_protocol_dir / 'unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml', - wayland_protocol_dir / 'unstable/text-input/text-input-unstable-v3.xml', - wayland_protocol_dir / 'unstable/text-input/text-input-unstable-v1.xml', - wayland_protocol_dir / 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml', - wayland_protocol_dir / 'staging/xdg-activation/xdg-activation-v1.xml', - wayland_protocol_dir / 'staging/ext-idle-notify/ext-idle-notify-v1.xml', - wayland_protocol_dir / 'staging/ext-session-lock/ext-session-lock-v1.xml', - wayland_protocol_dir / 'stable/tablet/tablet-v2.xml', - wayland_protocol_dir / 'stable/presentation-time/presentation-time.xml', - wayland_protocol_dir / 'stable/xdg-shell/xdg-shell.xml', - wayland_protocol_dir / 'unstable/primary-selection/primary-selection-unstable-v1.xml', - wayland_protocol_dir / 'staging/xwayland-shell/xwayland-shell-v1.xml', - wayland_protocol_dir / 'stable/viewporter/viewporter.xml', - wayland_protocol_dir / 'stable/linux-dmabuf/linux-dmabuf-v1.xml', - wayland_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml', - wayland_protocol_dir / 'staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml', - wayland_protocol_dir / 'staging/xdg-dialog/xdg-dialog-v1.xml', - wayland_protocol_dir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml', - wayland_protocol_dir / 'staging/security-context/security-context-v1.xml', - wayland_protocol_dir / 'staging/content-type/content-type-v1.xml', - wayland_protocol_dir / 'staging/color-management/color-management-v1.xml', - wayland_protocol_dir / 'staging/xdg-toplevel-tag/xdg-toplevel-tag-v1.xml', - wayland_protocol_dir / 'staging/xdg-system-bell/xdg-system-bell-v1.xml', - wayland_protocol_dir / 'staging/ext-workspace/ext-workspace-v1.xml', - wayland_protocol_dir / 'staging/ext-data-control/ext-data-control-v1.xml', - wayland_protocol_dir / 'staging/pointer-warp/pointer-warp-v1.xml', - wayland_protocol_dir / 'staging/fifo/fifo-v1.xml', - wayland_protocol_dir / 'staging/commit-timing/commit-timing-v1.xml', -] - -wl_protocols = [] -foreach protocol : protocols - wl_protocols += custom_target( - protocol.underscorify(), - input: protocol, - install: true, - install_dir: [false, join_paths(get_option('includedir'), 'hyprland/protocols')], - output: ['@BASENAME@.cpp', '@BASENAME@.hpp'], - command: [hyprwayland_scanner, '@INPUT@', '@OUTDIR@'], - ) -endforeach - -# wayland.xml generation -wayland_scanner = dependency('wayland-scanner', native: true) -wayland_scanner_datadir = wayland_scanner.get_variable('pkgdatadir') - -wayland_xml = wayland_scanner_datadir / 'wayland.xml' -wayland_protocol = custom_target( - wayland_xml.underscorify(), - input: wayland_xml, - install: true, - install_dir: [false, join_paths(get_option('includedir'), 'hyprland/protocols')], - output: ['@BASENAME@.cpp', '@BASENAME@.hpp'], - command: [hyprwayland_scanner, '--wayland-enums', '@INPUT@', '@OUTDIR@'], -) - -lib_server_protos = static_library( - 'server_protos', - wl_protocols + wayland_protocol, -) - -server_protos = declare_dependency( - link_with: lib_server_protos, - sources: wl_protocols + wayland_protocol, -) diff --git a/protocols/xx-color-management-v4.xml b/protocols/xx-color-management-v4.xml deleted file mode 100644 index 23ff716e..00000000 --- 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 20c78e9d..c9419031 100755 --- a/scripts/generateShaderIncludes.sh +++ b/scripts/generateShaderIncludes.sh @@ -15,7 +15,7 @@ echo 'static const std::map SHADERS = {' >> ./src/rend for filename in `ls ${SHADERS_SRC}`; do echo "-- ${filename}" - { echo 'R"#('; cat ${SHADERS_SRC}/${filename}; echo ')#"'; } > ./src/render/shaders/${filename}.inc + { echo -n 'R"#('; cat ${SHADERS_SRC}/${filename}; echo ')#"'; } > ./src/render/shaders/${filename}.inc echo "{\"${filename}\"," >> ./src/render/shaders/Shaders.hpp echo "#include \"./${filename}.inc\"" >> ./src/render/shaders/Shaders.hpp echo "}," >> ./src/render/shaders/Shaders.hpp diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 2dc798e4..b0fc1545 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -2,8 +2,12 @@ #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" @@ -16,6 +20,7 @@ #include "managers/ANRManager.hpp" #include "managers/eventLoop/EventLoopManager.hpp" #include "managers/permissions/DynamicPermissionManager.hpp" +#include "managers/screenshare/ScreenshareManager.hpp" #include #include #include @@ -26,11 +31,12 @@ #include #include #include "debug/HyprCtl.hpp" -#include "debug/CrashReporter.hpp" +#include "debug/crash/CrashReporter.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" @@ -40,7 +46,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" @@ -56,15 +63,18 @@ #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 "helpers/MonitorFrameScheduler.hpp" +#include "i18n/Engine.hpp" +#include "layout/LayoutManager.hpp" +#include "layout/target/WindowTarget.hpp" +#include "event/EventBus.hpp" #include #include @@ -83,7 +93,7 @@ using enum NContentType::eContentType; using namespace NColorManagement; 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(); @@ -97,11 +107,6 @@ static void handleUnrecoverableSignal(int sig) { signal(SIGABRT, SIG_DFL); signal(SIGSEGV, SIG_DFL); - if (g_pHookSystem && g_pHookSystem->m_currentEventPlugin) { - longjmp(g_pHookSystem->m_hookFaultJumpBuf, 1); - return; - } - // Kill the program if the crash-reporter is caught in a deadlock. signal(SIGALRM, [](int _) { char const* msg = "\nCrashReporter exceeded timeout, forcefully exiting\n"; @@ -110,7 +115,7 @@ static void handleUnrecoverableSignal(int sig) { }); alarm(15); - NCrashReporter::createAndSaveCrash(sig); + CrashReporter::createAndSaveCrash(sig); abort(); } @@ -122,28 +127,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; } @@ -153,13 +146,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() { @@ -167,7 +160,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 { @@ -227,27 +220,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(); } @@ -289,7 +282,6 @@ static bool filterGlobals(const wl_client* client, const wl_global* global, void // void CCompositor::initServer(std::string socketName, int socketFd) { if (m_onlyConfigVerification) { - g_pHookSystem = makeUnique(); g_pKeybindManager = makeUnique(); g_pAnimationManager = makeUnique(); g_pConfigManager = makeUnique(); @@ -307,7 +299,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); } @@ -315,14 +307,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; @@ -339,9 +333,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!"); } @@ -350,19 +345,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) { @@ -374,16 +370,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) { @@ -391,9 +387,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++) { @@ -401,22 +397,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)"); } @@ -438,7 +434,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 @@ -446,45 +442,49 @@ 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); @@ -493,7 +493,7 @@ void CCompositor::initAllSignals() { g_pConfigManager->m_wantsMonitorReload = true; g_pCursorManager->syncGsettings(); } else { - Debug::log(LOG, "Session got deactivated!"); + Log::logger->log(Log::DEBUG, "Session got deactivated!"); m_sessionActive = false; } @@ -517,7 +517,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 " @@ -529,7 +529,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); @@ -540,16 +540,18 @@ void CCompositor::cleanup() { if (!m_wlDisplay) return; + if (m_watchdogWriteFd.isValid()) + 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 @@ -559,17 +561,11 @@ void CCompositor::cleanup() { // still in a normal working state. g_pPluginSystem->unloadAllPlugins(); - m_lastFocus.reset(); - m_lastWindow.reset(); - m_workspaces.clear(); m_windows.clear(); for (auto const& m : m_monitors) { g_pHyprOpenGL->destroyMonitorResources(m); - - m->m_output->state->setEnabled(false); - m->m_state.commit(); } g_pXWayland.reset(); @@ -592,11 +588,10 @@ void CCompositor::cleanup() { g_pHyprRenderer.reset(); g_pHyprOpenGL.reset(); g_pConfigManager.reset(); - g_pLayoutManager.reset(); + g_layoutManager.reset(); g_pHyprError.reset(); g_pConfigManager.reset(); g_pKeybindManager.reset(); - g_pHookSystem.reset(); g_pXWaylandManager.reset(); g_pPointerManager.reset(); g_pSeatManager.reset(); @@ -604,6 +599,7 @@ void CCompositor::cleanup() { g_pEventLoopManager.reset(); g_pVersionKeeperMgr.reset(); g_pDonationNagManager.reset(); + g_pWelcomeManager.reset(); g_pANRManager.reset(); g_pConfigWatcher.reset(); g_pAsyncResourceGatherer.reset(); @@ -616,103 +612,106 @@ 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!"); + Log::logger->log(Log::DEBUG, "Creating the ConfigManager!"); g_pConfigManager = makeUnique(); - Debug::log(LOG, "Creating the CHyprError!"); + Log::logger->log(Log::DEBUG, "Creating the CHyprError!"); g_pHyprError = makeUnique(); - 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(); - 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 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!"); + Log::logger->log(Log::DEBUG, "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!"); + Log::logger->log(Log::DEBUG, "Creating the HyprDebugOverlay!"); g_pDebugOverlay = makeUnique(); - Debug::log(LOG, "Creating the HyprNotificationOverlay!"); + Log::logger->log(Log::DEBUG, "Creating the HyprNotificationOverlay!"); g_pHyprNotificationOverlay = makeUnique(); - Debug::log(LOG, "Creating the PluginSystem!"); + Log::logger->log(Log::DEBUG, "Creating the PluginSystem!"); g_pPluginSystem = makeUnique(); g_pConfigManager->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(); @@ -747,7 +746,7 @@ void CCompositor::prepareFallbackOutput() { } if (!headless) { - Debug::log(WARN, "No headless in prepareFallbackOutput?!"); + Log::logger->log(Log::WARN, "No headless in prepareFallbackOutput?!"); return; } @@ -764,7 +763,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 " @@ -774,7 +773,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(); @@ -783,18 +782,21 @@ 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()) + write(m_watchdogWriteFd.get(), "vax", 3); // 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(); } @@ -831,7 +833,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; } @@ -857,7 +859,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(); } @@ -869,9 +871,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; }); } } @@ -881,27 +883,30 @@ bool CCompositor::monitorExists(PHLMONITOR pMonitor) { } PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t properties, PHLWINDOW pIgnoreWindow) { - const auto PMONITOR = getMonitorFromVector(pos); + 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"); 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 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; - if (w->m_isFloating && w->m_isMapped && !w->isHidden() && !w->m_X11ShouldntFocus && w->m_pinned && !w->m_windowData.noFocus.valueOrDefault() && w != pIgnoreWindow && - !isShadowedByModal(w)) { + if (w->m_isFloating && w->m_isMapped && !w->isHidden() && !w->m_X11ShouldntFocus && w->m_pinned && !w->m_ruleApplicator->noFocus().valueOrDefault() && + 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())) @@ -938,7 +943,7 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper continue; } - if (w->m_isFloating && w->m_isMapped && w->m_workspace->isVisible() && !w->isHidden() && !w->m_pinned && !w->m_windowData.noFocus.valueOrDefault() && + if (w->m_isFloating && w->m_isMapped && w->m_workspace->isVisible() && !w->isHidden() && !w->m_pinned && !w->m_ruleApplicator->noFocus().valueOrDefault() && w != pIgnoreWindow && (!aboveFullscreen || w->m_createdOverFullscreen) && !isShadowedByModal(w)) { // OR windows should add focus to parent if (w->m_X11ShouldntFocus && !w->isX11OverrideRedirect()) @@ -950,7 +955,7 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper if (w->m_isX11 && w->isX11OverrideRedirect() && !w->m_xwaylandSurface->wantsFocus()) { // Override Redirect - return g_pCompositor->m_lastWindow.lock(); // we kinda trick everything here. + return Desktop::focusState()->window(); // we kinda trick everything here. // TODO: this is wrong, we should focus the parent, but idk how to get it considering it's nullptr in most cases. } @@ -967,21 +972,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) @@ -999,7 +1014,7 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper continue; if (!w->m_isX11 && !w->m_isFloating && w->m_isMapped && w->workspaceID() == WSPID && !w->isHidden() && !w->m_X11ShouldntFocus && - !w->m_windowData.noFocus.valueOrDefault() && w != pIgnoreWindow && !isShadowedByModal(w)) { + !w->m_ruleApplicator->noFocus().valueOrDefault() && w != pIgnoreWindow && !isShadowedByModal(w)) { if (w->hasPopupAt(pos)) return w; } @@ -1015,9 +1030,11 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper if (!w->m_workspace) continue; - if (!w->m_isFloating && w->m_isMapped && w->workspaceID() == WSPID && !w->isHidden() && !w->m_X11ShouldntFocus && !w->m_windowData.noFocus.valueOrDefault() && + 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()) + box.expand(BORDER_GRAB_AREA); if (box.containsPoint(pos)) return w; } @@ -1053,10 +1070,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; @@ -1078,7 +1095,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)) @@ -1114,205 +1131,10 @@ PHLMONITOR CCompositor::getRealMonitorFromOutput(SP out) { return nullptr; } -void CCompositor::focusWindow(PHLWINDOW pWindow, SP pSurface, bool preserveFocusHistory) { - - static auto PFOLLOWMOUSE = CConfigValue("input:follow_mouse"); - static auto PSPECIALFALLTHROUGH = CConfigValue("input:special_fallthrough"); - 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"); - return; - } - - if (!pWindow || !pWindow->priorityFocus()) { - if (g_pSessionLockManager->isSessionLocked()) { - Debug::log(LOG, "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"); - return; - } - } - - if (pWindow && pWindow->m_isX11 && pWindow->isX11OverrideRedirect() && !pWindow->m_xwaylandSurface->wantsFocus()) - return; - - g_pLayoutManager->getCurrentLayout()->bringWindowToTop(pWindow); - - if (!pWindow || !validMapped(pWindow)) { - - if (m_lastWindow.expired() && !pWindow) - return; - - const auto PLASTWINDOW = m_lastWindow.lock(); - m_lastWindow.reset(); - - if (PLASTWINDOW && PLASTWINDOW->m_isMapped) { - updateWindowAnimatedDecorationValues(PLASTWINDOW); - - g_pXWaylandManager->activateWindow(PLASTWINDOW, false); - } - - g_pSeatManager->setKeyboardFocus(nullptr); - - g_pEventManager->postEvent(SHyprIPCEvent{"activewindow", ","}); - g_pEventManager->postEvent(SHyprIPCEvent{"activewindowv2", ""}); - - EMIT_HOOK_EVENT("activeWindow", PHLWINDOW{nullptr}); - - g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(nullptr); - - m_lastFocus.reset(); - - g_pInputManager->recheckIdleInhibitorStatus(); - return; - } - - if (pWindow->m_windowData.noFocus.valueOrDefault()) { - Debug::log(LOG, "Ignoring focus to nofocus window!"); - return; - } - - if (m_lastWindow.lock() == pWindow && g_pSeatManager->m_state.keyboardFocus == pSurface && g_pSeatManager->m_state.keyboardFocus) - return; - - if (pWindow->m_pinned) - pWindow->m_workspace = m_lastMonitor->m_activeWorkspace; - - const auto PMONITOR = pWindow->m_monitor.lock(); - - if (!pWindow->m_workspace || !pWindow->m_workspace->isVisible()) { - const auto PWORKSPACE = pWindow->m_workspace; - // This is to fix incorrect feedback on the focus history. - PWORKSPACE->m_lastFocusedWindow = pWindow; - if (m_lastMonitor->m_activeWorkspace) - PWORKSPACE->rememberPrevWorkspace(m_lastMonitor->m_activeWorkspace); - if (PWORKSPACE->m_isSpecialWorkspace) - m_lastMonitor->changeWorkspace(PWORKSPACE, false, true); // if special ws, open on current monitor - else if (PMONITOR) - PMONITOR->changeWorkspace(PWORKSPACE, false, true); - // changeworkspace already calls focusWindow - return; - } - - const auto PLASTWINDOW = m_lastWindow.lock(); - m_lastWindow = pWindow; - - /* If special fallthrough is enabled, this behavior will be disabled, as I have no better idea of nicely tracking which - window focuses are "via keybinds" and which ones aren't. */ - if (PMONITOR && PMONITOR->m_activeSpecialWorkspace && PMONITOR->m_activeSpecialWorkspace != pWindow->m_workspace && !pWindow->m_pinned && !*PSPECIALFALLTHROUGH) - PMONITOR->setSpecialWorkspace(nullptr); - - // we need to make the PLASTWINDOW not equal to m_pLastWindow so that RENDERDATA is correct for an unfocused window - if (PLASTWINDOW && PLASTWINDOW->m_isMapped) { - PLASTWINDOW->updateDynamicRules(); - - updateWindowAnimatedDecorationValues(PLASTWINDOW); - - if (!pWindow->m_isX11 || !pWindow->isX11OverrideRedirect()) - g_pXWaylandManager->activateWindow(PLASTWINDOW, false); - } - - m_lastWindow = PLASTWINDOW; - - const auto PWINDOWSURFACE = pSurface ? pSurface : pWindow->m_wlSurface->resource(); - - focusSurface(PWINDOWSURFACE, pWindow); - - g_pXWaylandManager->activateWindow(pWindow, true); // sets the m_pLastWindow - - pWindow->updateDynamicRules(); - pWindow->onFocusAnimUpdate(); - - updateWindowAnimatedDecorationValues(pWindow); - - if (pWindow->m_isUrgent) - pWindow->m_isUrgent = false; - - // Send an event - 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); - - g_pInputManager->recheckIdleInhibitorStatus(); - - if (!preserveFocusHistory) { - // move to front of the window history - const auto HISTORYPIVOT = std::ranges::find_if(m_windowFocusHistory, [&](const auto& other) { return other.lock() == pWindow; }); - if (HISTORYPIVOT == m_windowFocusHistory.end()) - Debug::log(ERR, "BUG THIS: {} has no pivot in history", pWindow); - else - std::rotate(m_windowFocusHistory.begin(), HISTORYPIVOT, HISTORYPIVOT + 1); - } - - if (*PFOLLOWMOUSE == 0) - g_pInputManager->sendMotionEventsToFocused(); - - if (pWindow->m_groupData.pNextWindow) - pWindow->deactivateGroupMembers(); -} - -void CCompositor::focusSurface(SP pSurface, PHLWINDOW pWindowOwner) { - - if (g_pSeatManager->m_state.keyboardFocus == pSurface || (pWindowOwner && g_pSeatManager->m_state.keyboardFocus == pWindowOwner->m_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())); - return; - } - - const auto PLASTSURF = m_lastFocus.lock(); - - // Unfocus last surface if should - if (m_lastFocus && !pWindowOwner) - g_pXWaylandManager->activateSurface(m_lastFocus.lock(), false); - - if (!pSurface) { - g_pSeatManager->setKeyboardFocus(nullptr); - g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = ","}); - g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = ""}); - EMIT_HOOK_EVENT("keyboardFocus", SP{nullptr}); - m_lastFocus.reset(); - return; - } - - if (g_pSeatManager->m_keyboard) - g_pSeatManager->setKeyboardFocus(pSurface); - - if (pWindowOwner) - Debug::log(LOG, "Set keyboard focus to surface {:x}, with {}", rc(pSurface.get()), pWindowOwner); - else - Debug::log(LOG, "Set keyboard focus to surface {:x}", rc(pSurface.get())); - - g_pXWaylandManager->activateSurface(pSurface, true); - m_lastFocus = pSurface; - - EMIT_HOOK_EVENT("keyboardFocus", pSurface); - - const auto SURF = CWLSurface::fromResource(pSurface); - const auto OLDSURF = CWLSurface::fromResource(PLASTSURF); - - if (OLDSURF && OLDSURF->constraint()) - OLDSURF->constraint()->deactivate(); - - if (SURF && SURF->constraint()) - SURF->constraint()->activate(); -} - 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); @@ -1320,7 +1142,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(); } } } @@ -1332,8 +1154,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_aboveLockscreen || !ls->m_aboveLockscreenInteractable))) + 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); @@ -1357,7 +1178,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) { @@ -1389,15 +1215,15 @@ PHLWINDOW CCompositor::getUrgentWindow() { } bool CCompositor::isWindowActive(PHLWINDOW pWindow) { - if (m_lastWindow.expired() && !m_lastFocus) + if (!Desktop::focusState()->window() && !Desktop::focusState()->surface()) return false; if (!pWindow->m_isMapped) return false; - const auto PSURFACE = pWindow->m_wlSurface->resource(); + const auto PSURFACE = pWindow->wlSurface()->resource(); - return PSURFACE == m_lastFocus || pWindow == m_lastWindow.lock(); + return PSURFACE == Desktop::focusState()->surface() || pWindow == Desktop::focusState()->window(); } void CCompositor::changeWindowZOrder(PHLWINDOW pWindow, bool top) { @@ -1477,7 +1303,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; } } @@ -1514,7 +1340,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; } @@ -1546,8 +1372,8 @@ void CCompositor::addToFadingOutSafe(PHLWINDOW pWindow) { m_windowsFadingOut.emplace_back(pWindow); } -PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, char dir) { - if (!isDirection(dir)) +PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, Math::eDirection dir) { + if (dir == Math::DIRECTION_DEFAULT) return nullptr; const auto PMONITOR = pWindow->m_monitor.lock(); @@ -1558,11 +1384,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 @@ -1576,6 +1405,35 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks PHLWINDOW leaderWindow = nullptr; if (!useVectorAngles) { + // helper to check if two rectangles are adjacent along an axis, considering slight overlaps. + // returns true if: STICKS (delta <= 2) OR rectangles overlap but no more than 50% of the smaller dimension. + static auto isAdjacent = [](const double aMin, const double aMax, const double bMin, const double bMax) -> bool { + constexpr double STICK_THRESHOLD = 2.0; + constexpr double MAX_OVERLAP_RATIO = 0.5; + + const double aEdge = aMin; + const double bEdge = bMax; + const double delta = aEdge - bEdge; + + // old STICKS check for 2px + if (std::abs(delta) < STICK_THRESHOLD) + return true; + + if (delta >= 0) + return false; + + const double overlap = -delta; + const double sizeA = aMax - aMin; + const double sizeB = bMax - bMin; + + // reject if one rectangle fully contains the other + if ((bMin <= aMin && bMax >= aMax) || (aMin <= bMin && aMax >= bMax)) + return false; + + // accept if overlap is at most 50% of the smaller dimension + return overlap <= std::min(sizeA, sizeB) * MAX_OVERLAP_RATIO; + }; + for (auto const& w : m_windows) { if (w == ignoreWindow || !w->m_workspace || !w->m_isMapped || w->isHidden() || (!w->isFullscreen() && w->m_isFloating) || !w->m_workspace->isVisible()) continue; @@ -1597,44 +1455,38 @@ 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 */) { if (intersectLength > 0) { // get idx - int windowIDX = -1; - for (size_t i = 0; i < g_pCompositor->m_windowFocusHistory.size(); ++i) { - if (g_pCompositor->m_windowFocusHistory[i].lock() == w) { + int windowIDX = -1; + 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 = g_pCompositor->m_windowFocusHistory.size() - windowIDX; - if (windowIDX > leaderValue) { leaderValue = windowIDX; leaderWindow = w; @@ -1648,12 +1500,8 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks } } } else { - if (dir == 'u') - dir = 't'; - if (dir == 'd') - dir = 'b'; - - static const std::unordered_map VECTORS = {{'r', {1, 0}}, {'t', {0, -1}}, {'b', {0, 1}}, {'l', {-1, 0}}}; + static const std::unordered_map VECTORS = { + {Math::DIRECTION_RIGHT, {1, 0}}, {Math::DIRECTION_UP, {0, -1}}, {Math::DIRECTION_DOWN, {0, 1}}, {Math::DIRECTION_LEFT, {-1, 0}}}; // auto vectorAngles = [](const Vector2D& a, const Vector2D& b) -> double { @@ -1714,7 +1562,7 @@ static bool isFloatingMatches(WINDOWPTR w, std::optional floating) { template static bool isWindowAvailableForCycle(WINDOWPTR pWindow, WINDOWPTR w, bool focusableOnly, std::optional floating, bool anyWorkspace = false) { return isFloatingMatches(w, floating) && - (w != pWindow && isWorkspaceMatches(pWindow, w, anyWorkspace) && w->m_isMapped && !w->isHidden() && (!focusableOnly || !w->m_windowData.noFocus.valueOrDefault())); + (w != pWindow && isWorkspaceMatches(pWindow, w, anyWorkspace) && w->m_isMapped && !w->isHidden() && (!focusableOnly || !w->m_ruleApplicator->noFocus().valueOrDefault())); } template @@ -1738,8 +1586,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(m_windowFocusHistory | std::views::reverse, cur), m_windowFocusHistory.rend(), m_windowFocusHistory.rbegin(), FINDER) : - getWeakWindowPred(std::ranges::find(m_windowFocusHistory, cur), m_windowFocusHistory.end(), m_windowFocusHistory.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) { @@ -1782,7 +1631,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; } @@ -1795,48 +1644,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) { - return getMonitorInDirection(m_lastMonitor.lock(), 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; @@ -1853,7 +1691,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c const auto POSB = m->m_position; const auto SIZEB = m->m_size; switch (dir) { - case 'l': + case Math::DIRECTION_LEFT: if (STICKS(POSA.x, POSB.x + SIZEB.x)) { const auto INTERSECTLEN = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); if (INTERSECTLEN > longestIntersect) { @@ -1862,7 +1700,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c } } break; - case 'r': + case Math::DIRECTION_RIGHT: if (STICKS(POSA.x + SIZEA.x, POSB.x)) { const auto INTERSECTLEN = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); if (INTERSECTLEN > longestIntersect) { @@ -1871,8 +1709,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c } } break; - case 't': - case 'u': + case Math::DIRECTION_UP: if (STICKS(POSA.y, POSB.y + SIZEB.y)) { const auto INTERSECTLEN = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); if (INTERSECTLEN > longestIntersect) { @@ -1881,8 +1718,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c } } break; - case 'b': - case 'd': + case Math::DIRECTION_DOWN: if (STICKS(POSA.y + SIZEA.y, POSB.y)) { const auto INTERSECTLEN = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); if (INTERSECTLEN > longestIntersect) { @@ -1891,6 +1727,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c } } break; + default: break; } } @@ -1905,103 +1742,10 @@ void CCompositor::updateAllWindowsAnimatedDecorationValues() { if (!w->m_isMapped) continue; - updateWindowAnimatedDecorationValues(w); + w->updateDecorationValues(); } } -void CCompositor::updateWindowAnimatedDecorationValues(PHLWINDOW pWindow) { - // optimization - 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 == pWindow->m_realBorderColor) - return; - - pWindow->m_realBorderColorPrevious = pWindow->m_realBorderColor; - pWindow->m_realBorderColor = grad; - pWindow->m_borderFadeAnimationProgress->setValueAndWarp(0.f); - *pWindow->m_borderFadeAnimationProgress = 1.f; - }; - - const bool IS_SHADOWED_BY_MODAL = pWindow->m_xdgSurface && pWindow->m_xdgSurface->m_toplevel && pWindow->m_xdgSurface->m_toplevel->anyChildModal(); - - // border - const auto RENDERDATA = g_pLayoutManager->getCurrentLayout()->requestRenderHints(pWindow); - if (RENDERDATA.isBorderGradient) - setBorderColor(*RENDERDATA.borderGradient); - else { - const bool GROUPLOCKED = pWindow->m_groupData.pNextWindow.lock() ? pWindow->getGroupHead()->m_groupData.locked : false; - if (pWindow == m_lastWindow) { - const auto* const ACTIVECOLOR = - !pWindow->m_groupData.pNextWindow.lock() ? (!pWindow->m_groupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); - setBorderColor(pWindow->m_windowData.activeBorderColor.valueOr(*ACTIVECOLOR)); - } else { - const auto* const INACTIVECOLOR = !pWindow->m_groupData.pNextWindow.lock() ? (!pWindow->m_groupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) : - (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); - setBorderColor(pWindow->m_windowData.inactiveBorderColor.valueOr(*INACTIVECOLOR)); - } - } - - // opacity - const auto PWORKSPACE = pWindow->m_workspace; - if (pWindow->isEffectiveInternalFSMode(FSMODE_FULLSCREEN)) { - *pWindow->m_activeInactiveAlpha = pWindow->m_windowData.alphaFullscreen.valueOrDefault().applyAlpha(*PFULLSCREENALPHA); - } else { - if (pWindow == m_lastWindow) - *pWindow->m_activeInactiveAlpha = pWindow->m_windowData.alpha.valueOrDefault().applyAlpha(*PACTIVEALPHA); - else - *pWindow->m_activeInactiveAlpha = pWindow->m_windowData.alphaInactive.valueOrDefault().applyAlpha(*PINACTIVEALPHA); - } - - // dim - float goalDim = 1.F; - if (pWindow == m_lastWindow.lock() || pWindow->m_windowData.noDim.valueOrDefault() || !*PDIMENABLED) - goalDim = 0; - else - goalDim = *PDIMSTRENGTH; - - if (IS_SHADOWED_BY_MODAL && *PDIMMODAL) - goalDim += (1.F - goalDim) / 2.F; - - *pWindow->m_dimPercent = goalDim; - - // shadow - if (!pWindow->isX11OverrideRedirect() && !pWindow->m_X11DoesntWantBorders) { - if (pWindow == m_lastWindow) - *pWindow->m_realShadowColor = CHyprColor(*PSHADOWCOL); - else - *pWindow->m_realShadowColor = CHyprColor(*PSHADOWCOLINACTIVE != -1 ? *PSHADOWCOLINACTIVE : *PSHADOWCOL); - } else { - pWindow->m_realShadowColor->setValueAndWarp(CHyprColor(0, 0, 0, 0)); // no shadow - } - - pWindow->updateWindowDecos(); -} - MONITORID CCompositor::getNextAvailableMonitorID(std::string const& name) { // reuse ID if it's already in the map, and the monitor with that ID is not being used by another monitor if (m_monitorIDMap.contains(name) && !std::ranges::any_of(m_realMonitors, [&](auto m) { return m->m_id == m_monitorIDMap[name]; })) @@ -2039,7 +1783,7 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor // additionally, move floating and fs windows manually if (w->m_isFloating) - *w->m_realPosition = w->m_realPosition->goal() - pMonitorA->m_position + pMonitorB->m_position; + w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-pMonitorA->m_position + pMonitorB->m_position)); if (w->isFullscreen()) { *w->m_realPosition = pMonitorB->m_position; @@ -2064,7 +1808,7 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor // additionally, move floating and fs windows manually if (w->m_isFloating) - *w->m_realPosition = w->m_realPosition->goal() - pMonitorB->m_position + pMonitorA->m_position; + w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-pMonitorB->m_position + pMonitorA->m_position)); if (w->isFullscreen()) { *w->m_realPosition = pMonitorA->m_position; @@ -2078,43 +1822,46 @@ 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); g_pDesktopAnimationManager->setFullscreenFadeAnimation( PWORKSPACEA, PWORKSPACEA->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT); - if (pMonitorA->m_id == g_pCompositor->m_lastMonitor->m_id || pMonitorB->m_id == g_pCompositor->m_lastMonitor->m_id) { - const auto LASTWIN = pMonitorA->m_id == g_pCompositor->m_lastMonitor->m_id ? PWORKSPACEB->getLastFocusedWindow() : PWORKSPACEA->getLastFocusedWindow(); - g_pCompositor->focusWindow(LASTWIN ? LASTWIN : - (g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING))); + 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(), + Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING)), + Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE); - const auto PNEWWORKSPACE = pMonitorA->m_id == g_pCompositor->m_lastMonitor->m_id ? PWORKSPACEB : PWORKSPACEA; + 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 g_pCompositor->m_lastMonitor.lock(); + 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 @@ -2124,7 +1871,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; } @@ -2133,7 +1880,7 @@ PHLMONITOR CCompositor::getMonitorFromString(const std::string& name) { int currentPlace = 0; for (int i = 0; i < sc(m_monitors.size()); i++) { - if (m_monitors[i] == m_lastMonitor) { + if (m_monitors[i] == Desktop::focusState()->monitor()) { currentPlace = i; break; } @@ -2148,7 +1895,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); } @@ -2160,14 +1907,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 { @@ -2193,7 +1940,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(); @@ -2222,19 +1969,20 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo }()) 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) { @@ -2250,25 +1998,26 @@ 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(); } } - if (SWITCHINGISACTIVE && POLDMON == g_pCompositor->m_lastMonitor) { // if it was active, preserve its' status. If it wasn't, don't. - Debug::log(LOG, "moveWorkspaceToMonitor: SWITCHINGISACTIVE, active {} -> {}", pMonitor->activeWorkspaceID(), pWorkspace->m_id); + if (SWITCHINGISACTIVE && POLDMON == Desktop::focusState()->monitor()) { // if it was active, preserve its' status. If it wasn't, don't. + Log::logger->log(Log::DEBUG, "moveWorkspaceToMonitor: SWITCHINGISACTIVE, active {} -> {}", pMonitor->activeWorkspaceID(), pWorkspace->m_id); if (valid(pMonitor->m_activeWorkspace)) { pMonitor->m_activeWorkspace->m_visible = false; @@ -2278,7 +2027,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo if (*PHIDESPECIALONWORKSPACECHANGE) pMonitor->setSpecialWorkspace(nullptr); - setActiveMonitor(pMonitor); + Desktop::focusState()->rawMonitorFocus(pMonitor); auto oldWorkspace = pMonitor->m_activeWorkspace; pMonitor->m_activeWorkspace = pWorkspace; @@ -2288,7 +2037,8 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo pWorkspace->m_events.activeChanged.emit(); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitor->m_id); + g_layoutManager->recalculateMonitor(pMonitor); + g_pHyprRenderer->damageMonitor(pMonitor); g_pDesktopAnimationManager->startAnimation(pWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); pWorkspace->m_visible = true; @@ -2301,7 +2051,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo // finalize if (POLDMON) { - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(POLDMON->m_id); + g_layoutManager->recalculateMonitor(POLDMON); if (valid(POLDMON->m_activeWorkspace)) g_pDesktopAnimationManager->setFullscreenFadeAnimation(POLDMON->m_activeWorkspace, POLDMON->m_activeWorkspace->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : @@ -2316,7 +2066,8 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo // event g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspace", .data = pWorkspace->m_name + "," + pMonitor->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspacev2", .data = std::format("{},{},{}", pWorkspace->m_id, pWorkspace->m_name, pMonitor->m_name)}); - EMIT_HOOK_EVENT("moveWorkspace", (std::vector{pWorkspace, pMonitor})); + + Event::bus()->m_events.workspace.moveToMonitor.emit(pWorkspace, pMonitor); } bool CCompositor::workspaceIDOutOfBounds(const WORKSPACEID& id) { @@ -2339,21 +2090,22 @@ void CCompositor::changeWindowFullscreenModeClient(const PHLWINDOW PWINDOW, cons sc(ON ? sc(PWINDOW->m_fullscreenState.client) | sc(MODE) : (sc(PWINDOW->m_fullscreenState.client) & sc(~MODE)))); } +// TODO: move fs functions to Desktop:: void CCompositor::setWindowFullscreenInternal(const PHLWINDOW PWINDOW, const eFullscreenMode MODE) { - if (PWINDOW->m_windowData.syncFullscreen.valueOrDefault()) - setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = MODE, .client = MODE}); + if (PWINDOW->m_ruleApplicator->syncFullscreen().valueOrDefault()) + 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_windowData.syncFullscreen.valueOrDefault()) - setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = MODE, .client = MODE}); + if (PWINDOW->m_ruleApplicator->syncFullscreen().valueOrDefault()) + 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"); @@ -2388,37 +2140,45 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, SFullscreenS } // TODO: update the state on syncFullscreen changes - if (!CHANGEINTERNAL && PWINDOW->m_windowData.syncFullscreen.valueOrDefault()) + if (!CHANGEINTERNAL && PWINDOW->m_ruleApplicator->syncFullscreen().valueOrDefault()) return; PWINDOW->m_fullscreenState.client = state.client; g_pXWaylandManager->setWindowFullscreen(PWINDOW, state.client & FSMODE_FULLSCREEN); if (!CHANGEINTERNAL) { - PWINDOW->updateDynamicRules(); - updateWindowAnimatedDecorationValues(PWINDOW); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID()); + PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT | + Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE); + PWINDOW->updateDecorationValues(); + g_layoutManager->recalculateMonitor(PMONITOR); return; } - g_pLayoutManager->getCurrentLayout()->fullscreenRequestForWindow(PWINDOW, CURRENT_EFFECTIVE_MODE, EFFECTIVE_MODE); + PWORKSPACE->m_fullscreenMode = EFFECTIVE_MODE; + PWORKSPACE->m_hasFullscreenWindow = EFFECTIVE_MODE != FSMODE_NONE; + + g_layoutManager->fullscreenRequestForTarget(PWINDOW->layoutTarget(), CURRENT_EFFECTIVE_MODE, EFFECTIVE_MODE); PWINDOW->m_fullscreenState.internal = state.internal; - PWORKSPACE->m_fullscreenMode = EFFECTIVE_MODE; - PWORKSPACE->m_hasFullscreenWindow = EFFECTIVE_MODE != FSMODE_NONE; g_pEventManager->postEvent(SHyprIPCEvent{.event = "fullscreen", .data = std::to_string(sc(EFFECTIVE_MODE) != FSMODE_NONE)}); - EMIT_HOOK_EVENT("fullscreen", PWINDOW); + Event::bus()->m_events.window.fullscreen.emit(PWINDOW); - PWINDOW->updateDynamicRules(); - updateWindowAnimatedDecorationValues(PWINDOW); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID()); + 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); - // make all windows on the same workspace under the fullscreen window + PWINDOW->updateDecorationValues(); + g_layoutManager->recalculateMonitor(PMONITOR); + + // 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); @@ -2476,16 +2236,16 @@ PHLWINDOW CCompositor::getWindowByRegex(const std::string& regexp_) { auto regexp = trim(regexp_); if (regexp.starts_with("active")) - return m_lastWindow.lock(); + return Desktop::focusState()->window(); else if (regexp.starts_with("floating") || regexp.starts_with("tiled")) { // first floating on the current ws - if (!valid(m_lastWindow)) + if (!Desktop::focusState()->window()) return nullptr; const bool FLOAT = regexp.starts_with("floating"); for (auto const& w : m_windows) { - if (!w->m_isMapped || w->m_isFloating != FLOAT || w->m_workspace != m_lastWindow->m_workspace || w->isHidden()) + if (!w->m_isMapped || w->m_isFloating != FLOAT || w->m_workspace != Desktop::focusState()->window()->m_workspace || w->isHidden()) continue; return w; @@ -2521,7 +2281,7 @@ PHLWINDOW CCompositor::getWindowByRegex(const std::string& regexp_) { } for (auto const& w : g_pCompositor->m_windows) { - if (!w->m_isMapped || (w->isHidden() && !g_pLayoutManager->getCurrentLayout()->isWindowReachable(w))) + if (!w->m_isMapped) continue; switch (mode) { @@ -2551,7 +2311,7 @@ PHLWINDOW CCompositor::getWindowByRegex(const std::string& regexp_) { } case MODE_TAG_REGEX: { bool tagMatched = false; - for (auto const& t : w->m_tags.getTags()) { + for (auto const& t : w->m_ruleApplicator->m_tagKeeper.getTags()) { if (RE2::FullMatch(t, regexCheck)) { tagMatched = true; break; @@ -2591,16 +2351,14 @@ void CCompositor::warpCursorTo(const Vector2D& pos, bool force) { if (*PNOWARPS && !force) { const auto PMONITORNEW = getMonitorFromVector(pos); - if (PMONITORNEW != m_lastMonitor) - setActiveMonitor(PMONITORNEW); + Desktop::focusState()->rawMonitorFocus(PMONITORNEW); return; } g_pPointerManager->warpTo(pos); const auto PMONITORNEW = getMonitorFromVector(pos); - if (PMONITORNEW != m_lastMonitor) - setActiveMonitor(PMONITORNEW); + Desktop::focusState()->rawMonitorFocus(PMONITORNEW); } void CCompositor::closeWindow(PHLWINDOW pWindow) { @@ -2612,12 +2370,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) { @@ -2639,7 +2397,7 @@ Vector2D CCompositor::parseWindowVectorArgsRelative(const std::string& args, con if (!args.contains(' ') && !args.contains('\t')) return relativeTo; - const auto PMONITOR = m_lastMonitor; + const auto PMONITOR = Desktop::focusState()->monitor(); bool xIsPercent = false; bool yIsPercent = false; @@ -2666,7 +2424,7 @@ Vector2D CCompositor::parseWindowVectorArgsRelative(const std::string& args, con } if (!isNumber(x) || !isNumber(y)) { - Debug::log(ERR, "parseWindowVectorArgsRelative: args not numbers"); + Log::logger->log(Log::ERR, "parseWindowVectorArgsRelative: args not numbers"); return relativeTo; } @@ -2696,7 +2454,7 @@ PHLWORKSPACE CCompositor::createNewWorkspace(const WORKSPACEID& id, const MONITO 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; } @@ -2707,27 +2465,6 @@ PHLWORKSPACE CCompositor::createNewWorkspace(const WORKSPACEID& id, const MONITO return PWORKSPACE; } -void CCompositor::setActiveMonitor(PHLMONITOR pMonitor) { - if (m_lastMonitor == pMonitor) - return; - - if (!pMonitor) { - m_lastMonitor.reset(); - return; - } - - const auto PWORKSPACE = pMonitor->m_activeWorkspace; - - const auto WORKSPACE_ID = PWORKSPACE ? std::to_string(PWORKSPACE->m_id) : std::to_string(WORKSPACE_INVALID); - const auto WORKSPACE_NAME = PWORKSPACE ? PWORKSPACE->m_name : "?"; - - 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); - m_lastMonitor = pMonitor->m_self; -} - bool CCompositor::isWorkspaceSpecial(const WORKSPACEID& id) { return id >= SPECIAL_WORKSPACE_START && id <= -2; } @@ -2760,29 +2497,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( - std::format("Your XDG_CURRENT_DESKTOP environment seems to be managed externally, and the current value is {}.\nThis might cause issues unless it's intentional.", - CURRENT_DESKTOP_ENV ? CURRENT_DESKTOP_ENV : "unset"), - CHyprColor{}, 15000, ICON_WARNING); + 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( - "Your system does not have hyprland-guiutils installed. This is a runtime dependency for some dialogs. Consider installing it.", CHyprColor{}, 15000, ICON_WARNING); - } + if (!NFsUtils::executableExistsInPath("hyprland-dialog")) + g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_NO_GUIUTILS), CHyprColor{}, 15000, ICON_WARNING); } if (g_pHyprOpenGL->m_failedAssetsNo > 0) { - g_pHyprNotificationOverlay->addNotification(std::format("Hyprland failed to load {} essential asset{}, blame your distro's packager for doing a bad job at packaging!", - g_pHyprOpenGL->m_failedAssetsNo, g_pHyprOpenGL->m_failedAssetsNo > 1 ? "s" : ""), + 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 (!m_watchdogWriteFd.isValid() && !*PNOWATCHDOG) + g_pHyprNotificationOverlay->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; + g_pConfigManager->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) { @@ -2803,59 +2586,30 @@ void CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWor setWindowFullscreenInternal(pWindow, FSMODE_NONE); const PHLWINDOW pFirstWindowOnWorkspace = pWorkspace->getFirstWindow(); - const int visibleWindowsOnWorkspace = pWorkspace->getWindows(std::nullopt, true); + const int visibleWindowsOnWorkspace = pWorkspace->getWindows(true, std::nullopt, true); const auto POSTOMON = pWindow->m_realPosition->goal() - (pWindow->m_monitor ? pWindow->m_monitor->m_position : Vector2D{}); const auto PWORKSPACEMONITOR = pWorkspace->m_monitor.lock(); - if (!pWindow->m_isFloating) - g_pLayoutManager->getCurrentLayout()->onWindowRemovedTiling(pWindow); - pWindow->moveToWorkspace(pWorkspace); pWindow->m_monitor = pWorkspace->m_monitor; static auto PGROUPONMOVETOWORKSPACE = CConfigValue("group:group_on_movetoworkspace"); - if (*PGROUPONMOVETOWORKSPACE && visibleWindowsOnWorkspace == 1 && pFirstWindowOnWorkspace && pFirstWindowOnWorkspace != pWindow && - pFirstWindowOnWorkspace->m_groupData.pNextWindow.lock() && pWindow->canBeGroupedInto(pFirstWindowOnWorkspace)) { - - pWindow->m_isFloating = pFirstWindowOnWorkspace->m_isFloating; // match the floating state. Needed to group tiled into floated and vice versa. - if (!pWindow->m_groupData.pNextWindow.expired()) { - PHLWINDOW next = pWindow->m_groupData.pNextWindow.lock(); - while (next != pWindow) { - next->m_isFloating = pFirstWindowOnWorkspace->m_isFloating; // match the floating state of group members - next = next->m_groupData.pNextWindow.lock(); - } - } - - static auto USECURRPOS = CConfigValue("group:insert_after_current"); - (*USECURRPOS ? pFirstWindowOnWorkspace : pFirstWindowOnWorkspace->getGroupTail())->insertWindowToGroup(pWindow); - - pFirstWindowOnWorkspace->setGroupCurrent(pWindow); - pWindow->updateWindowDecos(); - g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow); - - if (!pWindow->getDecorationByType(DECORATION_GROUPBAR)) - pWindow->addWindowDeco(makeUnique(pWindow)); - + if (*PGROUPONMOVETOWORKSPACE && visibleWindowsOnWorkspace == 1 && pFirstWindowOnWorkspace && pFirstWindowOnWorkspace != pWindow && pFirstWindowOnWorkspace->m_group && + pWindow->canBeGroupedInto(pFirstWindowOnWorkspace->m_group)) { + pFirstWindowOnWorkspace->m_group->add(pWindow); } else { - if (!pWindow->m_isFloating) - g_pLayoutManager->getCurrentLayout()->onWindowCreatedTiling(pWindow); - if (pWindow->m_isFloating) - *pWindow->m_realPosition = POSTOMON + PWORKSPACEMONITOR->m_position; + pWindow->layoutTarget()->setPositionGlobal(CBox{POSTOMON + PWORKSPACEMONITOR->m_position, pWindow->layoutTarget()->position().size()}); } pWindow->updateToplevel(); - pWindow->updateDynamicRules(); + 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); @@ -2876,7 +2630,7 @@ PHLWINDOW CCompositor::getForceFocus() { if (!w->m_isMapped || w->isHidden() || !w->m_workspace || !w->m_workspace->isVisible()) continue; - if (!w->m_stayFocused) + if (!w->m_ruleApplicator->stayFocused().valueOrDefault()) continue; return w; @@ -2904,11 +2658,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(std::format("Your monitor layout is set up incorrectly. Monitor {} overlaps with other monitor(s) in the " - "layout.\nPlease see the wiki (Monitors page) for more. This will cause issues.", - m->m_name), - CHyprColor{}, 15000, ICON_WARNING); + Log::logger->log(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); break; } @@ -2924,14 +2676,14 @@ void CCompositor::arrangeMonitors() { 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}) { // 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.offset); m->moveTo(m->m_activeMonitorRule.offset); arranged.push_back(m); @@ -3006,7 +2758,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); } @@ -3015,7 +2767,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); @@ -3026,10 +2778,17 @@ void CCompositor::arrangeMonitors() { } PROTO::xdgOutput->updateAllOutputs(); + Event::bus()->m_events.monitor.layoutChanged.emit(); #ifndef NO_XWAYLAND - CBox box = g_pCompositor->calculateX11WorkArea(); - 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 } @@ -3037,21 +2796,21 @@ 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); m_unsafeState = true; - setActiveMonitor(m_unsafeOutput.lock()); + Desktop::focusState()->rawMonitorFocus(m_unsafeOutput.lock()); } void CCompositor::leaveUnsafeState() { if (!m_unsafeState) return; - Debug::log(LOG, "Leaving unsafe state"); + Log::logger->log(Log::DEBUG, "Leaving unsafe state"); m_unsafeState = false; @@ -3077,9 +2836,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; } @@ -3090,9 +2849,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; } @@ -3148,7 +2907,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; @@ -3156,7 +2915,7 @@ void CCompositor::onNewMonitor(SP output) { PNEWMONITOR->m_id = FALLBACK ? MONITOR_INVALID : g_pCompositor->getNextAvailableMonitorID(output->name); PNEWMONITOR->m_isUnsafeFallback = FALLBACK; - EMIT_HOOK_EVENT("newMonitor", PNEWMONITOR); + Event::bus()->m_events.monitor.newMon.emit(PNEWMONITOR); if (!FALLBACK) PNEWMONITOR->onConnect(false); @@ -3187,28 +2946,48 @@ 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 DEFAULT_IMAGE_DESCRIPTION; } - 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 DEFAULT_IMAGE_DESCRIPTION; + } + + 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) { - if (!m_lastMonitor) + if (!Desktop::focusState()->monitor()) return; std::vector persistentFound; @@ -3241,19 +3020,19 @@ void CCompositor::ensurePersistentWorkspacesPresent(const std::vectorlog(Log::ERR, "ensurePersistentWorkspacesPresent: couldn't resolve id for workspace {}", rule.workspaceString); continue; } PWORKSPACE = getWorkspaceByID(id); if (!PMONITOR) - PMONITOR = m_lastMonitor.lock(); + PMONITOR = Desktop::focusState()->monitor(); if (!PWORKSPACE) PWORKSPACE = createNewWorkspace(id, PMONITOR->m_id, wsname, false); } if (!PMONITOR) { - Debug::log(ERR, "ensurePersistentWorkspacesPresent: couldn't resolve monitor for {}, skipping", rule.monitor); + Log::logger->log(Log::ERR, "ensurePersistentWorkspacesPresent: couldn't resolve monitor for {}, skipping", rule.monitor); continue; } @@ -3265,12 +3044,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.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.workspaceString, PMONITOR->m_name); moveWorkspaceToMonitor(PWORKSPACE, PMONITOR); continue; } @@ -3295,6 +3074,27 @@ void CCompositor::ensurePersistentWorkspacesPresent(const std::vectorm_isSpecialWorkspace) + continue; + + const auto RULE = g_pConfigManager->getWorkspaceRuleFor(ws); + if (RULE.monitor.empty()) + continue; + + const auto PMONITOR = getMonitorFromString(RULE.monitor); + if (!PMONITOR) + continue; + + if (ws->m_monitor == PMONITOR) + continue; + + Log::logger->log(Log::DEBUG, "ensureWorkspacesOnAssignedMonitors: moving workspace {} to {}", ws->m_name, PMONITOR->m_name); + moveWorkspaceToMonitor(ws, PMONITOR, true); + } +} + std::optional CCompositor::getVTNr() { if (!m_aqBackend->hasSession()) return std::nullopt; @@ -3315,3 +3115,7 @@ std::optional CCompositor::getVTNr() { return ttynum; } + +bool CCompositor::isVRRActiveOnAnyMonitor() const { + return std::ranges::any_of(m_monitors, [](const PHLMONITOR& m) { return m->m_vrrActive; }); +} diff --git a/src/Compositor.hpp b/src/Compositor.hpp index bf6401e5..6d2044fe 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -4,11 +4,12 @@ #include +#include "helpers/math/Direction.hpp" #include "managers/XWaylandManager.hpp" #include "managers/KeybindManager.hpp" #include "managers/SessionLockManager.hpp" -#include "desktop/Window.hpp" -#include "protocols/types/ColorManagement.hpp" +#include "desktop/view/Window.hpp" +#include "helpers/cm/ColorManagement.hpp" #include #include @@ -40,6 +41,7 @@ class CCompositor { } m_drmRenderNode; bool m_initialized = false; + bool m_safeMode = false; SP m_aqBackend; std::string m_hyprTempDataRoot = ""; @@ -55,6 +57,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,12 +68,7 @@ class CCompositor { void cleanup(); void bumpNofile(); void restoreNofile(); - - WP m_lastFocus; - PHLWINDOWREF m_lastWindow; - PHLMONITORREF m_lastMonitor; - - std::vector m_windowFocusHistory; // first element is the most recently focused + bool setWatchdogFd(int fd); bool m_readyToProcess = false; bool m_sessionActive = true; @@ -99,8 +97,6 @@ class CCompositor { PHLMONITOR getMonitorFromCursor(); PHLMONITOR getMonitorFromVector(const Vector2D&); void removeWindowFromVectorSafe(PHLWINDOW); - void focusWindow(PHLWINDOW, SP pSurface = nullptr, bool preserveFocusHistory = false); - void focusSurface(SP, PHLWINDOW pWindowOwner = nullptr); bool monitorExists(PHLMONITOR); PHLWINDOW vectorToWindowUnified(const Vector2D&, uint8_t properties, PHLWINDOW pIgnoreWindow = nullptr); SP vectorToLayerSurface(const Vector2D&, std::vector*, Vector2D*, PHLLS*, bool aboveLockscreen = false); @@ -118,18 +114,17 @@ 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(); - void updateWindowAnimatedDecorationValues(PHLWINDOW); MONITORID getNextAvailableMonitorID(std::string const& name); void moveWorkspaceToMonitor(PHLWORKSPACE, PHLMONITOR, bool noWarpCursor = false); void swapActiveWorkspaces(PHLMONITOR, PHLMONITOR); @@ -137,7 +132,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); @@ -151,7 +146,6 @@ class CCompositor { Vector2D parseWindowVectorArgsRelative(const std::string&, const Vector2D&); [[nodiscard]] PHLWORKSPACE createNewWorkspace(const WORKSPACEID&, const MONITORID&, const std::string& name = "", bool isEmpty = true); // will be deleted next frame if left empty and unfocused! - void setActiveMonitor(PHLMONITOR); bool isWorkspaceSpecial(const WORKSPACEID&); WORKSPACEID getNewSpecialID(); void performUserChecks(); @@ -167,30 +161,35 @@ class CCompositor { void updateSuspendedStates(); void onNewMonitor(SP output); void ensurePersistentWorkspacesPresent(const std::vector& rules, PHLWORKSPACE pWorkspace = nullptr); + void ensureWorkspacesOnAssignedMonitors(); std::optional getVTNr(); + bool isVRRActiveOnAnyMonitor() const; - NColorManagement::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 639d160a..bb7c601e 100644 --- a/src/SharedDefs.hpp +++ b/src/SharedDefs.hpp @@ -38,10 +38,6 @@ enum eInputType : uint8_t { INPUT_TYPE_MOTION }; -struct SCallbackInfo { - bool cancelled = false; /* on cancellable events, will cancel the event. */ -}; - enum eHyprCtlOutputFormat : uint8_t { FORMAT_NORMAL = 0, FORMAT_JSON @@ -62,5 +58,3 @@ struct SDispatchResult { using WINDOWID = int64_t; using MONITORID = int64_t; using WORKSPACEID = int64_t; - -using HOOK_CALLBACK_FN = std::function; diff --git a/src/config/ConfigDataValues.hpp b/src/config/ConfigDataValues.hpp index 72602df3..8facfd9b 100644 --- a/src/config/ConfigDataValues.hpp +++ b/src/config/ConfigDataValues.hpp @@ -97,27 +97,29 @@ class CCssGapData : public ICustomConfigValueData { int64_t m_bottom; int64_t m_left; - void parseGapData(CVarList varlist) { + 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(std::stoi(varlist[0])); + *this = CCssGapData(toInt(varlist[0])); break; } case 2: { - *this = CCssGapData(std::stoi(varlist[0]), std::stoi(varlist[1])); + *this = CCssGapData(toInt(varlist[0]), toInt(varlist[1])); break; } case 3: { - *this = CCssGapData(std::stoi(varlist[0]), std::stoi(varlist[1]), std::stoi(varlist[2])); + *this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]), toInt(varlist[2])); break; } case 4: { - *this = CCssGapData(std::stoi(varlist[0]), std::stoi(varlist[1]), std::stoi(varlist[2]), std::stoi(varlist[3])); + *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(std::stoi(varlist[0]), std::stoi(varlist[1]), std::stoi(varlist[2]), std::stoi(varlist[3])); + 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; } } diff --git a/src/config/ConfigDescriptions.hpp b/src/config/ConfigDescriptions.hpp index 926d49ac..f6f777d1 100644 --- a/src/config/ConfigDescriptions.hpp +++ b/src/config/ConfigDescriptions.hpp @@ -15,12 +15,6 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_INT, .data = SConfigOptionDescription::SRangeData{1, 0, 20}, }, - SConfigOptionDescription{ - .value = "general:no_border_on_floating", - .description = "disable borders for floating windows", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, - }, SConfigOptionDescription{ .value = "general:gaps_in", .description = "gaps between windows\n\nsupports css style gaps (top, right, bottom, left -> 5 10 15 20)", @@ -144,10 +138,16 @@ inline static const std::vector CONFIG_OPTIONS = { }, SConfigOptionDescription{ .value = "general:modal_parent_blocking", - .description = "If true, parent windows of modals will not be interactive.", + .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: @@ -1115,6 +1115,18 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_BOOL, .data = SConfigOptionDescription::SRangeData{0, -20, 20}, }, + SConfigOptionDescription{ + .value = "group:groupbar:text_padding", + .description = "set horizontal padding for a text", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SRangeData{0, 0, 22}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:blur", + .description = "enable background blur for groupbars", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, /* * misc: @@ -1273,11 +1285,11 @@ inline static const std::vector CONFIG_OPTIONS = { .data = SConfigOptionDescription::SBoolData{true}, }, SConfigOptionDescription{ - .value = "misc:new_window_takes_over_fullscreen", - .description = "if there is a fullscreen or maximized window, decide whether a new tiled window opened should replace it, stay behind or disable the fullscreen/maximized " - "state. 0 - behind, 1 - takes over, 2 - unfullscreen/unmaxize [0/1/2]", + .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{0, 0, 2}, + .data = SConfigOptionDescription::SRangeData{2, 0, 2}, }, SConfigOptionDescription{ .value = "misc:exit_window_retains_fullscreen", @@ -1315,6 +1327,12 @@ inline static const std::vector CONFIG_OPTIONS = { .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.", @@ -1550,10 +1568,28 @@ inline static const std::vector CONFIG_OPTIONS = { }, SConfigOptionDescription{ .value = "render:cm_sdr_eotf", - .description = "Default transfer function for displaying SDR apps. 0 - 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"}, + .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}, }, /* @@ -1646,6 +1682,12 @@ inline static const std::vector CONFIG_OPTIONS = { .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", @@ -1665,10 +1707,16 @@ inline static const std::vector CONFIG_OPTIONS = { .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", + .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{false}, + .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", @@ -1722,6 +1770,12 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_BOOL, .data = SConfigOptionDescription::SBoolData{false}, }, + SConfigOptionDescription{ + .value = "debug:gl_debugging", + .description = "enable OpenGL debugging and error checking, they hurt performance.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, SConfigOptionDescription{ .value = "debug:disable_logs", .description = "disable logging to a file", @@ -1800,6 +1854,46 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_BOOL, .data = SConfigOptionDescription::SBoolData{false}, }, + SConfigOptionDescription{ + .value = "debug:ds_handle_same_buffer", + .description = "Special case for DS with unmodified buffer", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "debug:ds_handle_same_buffer_fifo", + .description = "Special case for DS with unmodified buffer unlocks fifo", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "debug:fifo_pending_workaround", + .description = "Fifo workaround for empty pending list", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "debug:render_solitary_wo_damage", + .description = "Render solitary window with empty damage", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + + /* + * layout: + */ + SConfigOptionDescription{ + .value = "layout:single_window_aspect_ratio", + .description = "If specified, whenever only a single window is open, it will be coerced into the specified aspect ratio. Ignored if the y-value is zero.", + .type = CONFIG_OPTION_VECTOR, + .data = SConfigOptionDescription::SVectorData{{0, 0}, {0, 0}, {1000., 1000.}}, + }, + SConfigOptionDescription{ + .value = "layout:single_window_aspect_ratio_tolerance", + .description = "Minimum distance for single_window_aspect_ratio to take effect, in fractions of the monitor's size.", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.1f, 0.f, 1.f}, + }, /* * dwindle: @@ -1880,18 +1974,6 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_BOOL, .data = SConfigOptionDescription::SBoolData{false}, }, - SConfigOptionDescription{ - .value = "dwindle:single_window_aspect_ratio", - .description = "If specified, whenever only a single window is open, it will be coerced into the specified aspect ratio. Ignored if the y-value is zero.", - .type = CONFIG_OPTION_VECTOR, - .data = SConfigOptionDescription::SVectorData{{0, 0}, {0, 0}, {1000., 1000.}}, - }, - SConfigOptionDescription{ - .value = "dwindle:single_window_aspect_ratio_tolerance", - .description = "Minimum distance for single_window_aspect_ratio to take effect, in fractions of the monitor's size.", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{0.1f, 0.f, 1.f}, - }, /* * master: @@ -1940,12 +2022,6 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_STRING_SHORT, .data = SConfigOptionDescription::SStringData{"left"}, }, - SConfigOptionDescription{ - .value = "master:inherit_fullscreen", - .description = "inherit fullscreen status when cycling/swapping to another window (e.g. monocle layout)", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{true}, - }, SConfigOptionDescription{ .value = "master: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)", @@ -1984,13 +2060,79 @@ inline static const std::vector CONFIG_OPTIONS = { }, /* - * Experimental + * scrolling: + */ + + SConfigOptionDescription{ + .value = "scrolling:fullscreen_on_one_column", + .description = "when enabled, a single column on a workspace will always span the entire screen.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "scrolling:column_width", + .description = "the default width of a column, [0.1 - 1.0].", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{.value = 0.5, .min = 0.1, .max = 1.0}, + }, + SConfigOptionDescription{ + .value = "scrolling:focus_fit_method", + .description = "When a column is focused, what method should be used to bring it into view", + .type = CONFIG_OPTION_CHOICE, + .data = SConfigOptionDescription::SChoiceData{.firstIndex = 0, .choices = "center,fit"}, + }, + SConfigOptionDescription{ + .value = "scrolling:follow_focus", + .description = "when a window is focused, should the layout move to bring it into view automatically", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{.value = true}, + }, + SConfigOptionDescription{ + .value = "scrolling:follow_min_visible", + .description = "when a window is focused, require that at least a given fraction of it is visible for focus to follow", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{.value = 0.4, .min = 0.0, .max = 1.0}, + }, + SConfigOptionDescription{ + .value = "scrolling:explicit_column_widths", + .description = "A comma-separated list of preconfigured widths for colresize +conf/-conf", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"0.333, 0.5, 0.667, 1.0"}, + }, + SConfigOptionDescription{ + .value = "scrolling:direction", + .description = "Direction in which new windows appear and the layout scrolls", + .type = CONFIG_OPTION_CHOICE, + .data = SConfigOptionDescription::SChoiceData{.firstIndex = 0, .choices = "right,left,down,up"}, + }, + SConfigOptionDescription{ + .value = "scrolling:wrap_focus", + .description = "Determines if column focus wraps around when going before the first column or past the last column", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{.value = true}, + }, + SConfigOptionDescription{ + .value = "scrolling:wrap_swapcol", + .description = "Determines if column movement wraps around when moving to before the first column or past the last column", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{.value = true}, + }, + + /* + * Quirks */ SConfigOptionDescription{ - .value = "experimental:xx_color_management_v4", - .description = "enable color management protocol", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, + .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}, + }, + }; diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index d03ad83e..29967e58 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -8,19 +8,24 @@ #include "../render/decorations/CHyprGroupBarDecoration.hpp" #include "config/ConfigDataValues.hpp" #include "config/ConfigValue.hpp" -#include "../desktop/WindowRule.hpp" #include "../protocols/LayerShell.hpp" #include "../xwayland/XWayland.hpp" #include "../protocols/OutputManagement.hpp" #include "../managers/animation/AnimationManager.hpp" -#include "../desktop/LayerSurface.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 "../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" @@ -35,8 +40,10 @@ #include "../managers/input/trackpad/gestures/CloseGesture.hpp" #include "../managers/input/trackpad/gestures/FloatGesture.hpp" #include "../managers/input/trackpad/gestures/FullscreenGesture.hpp" +#include "../managers/input/trackpad/gestures/CursorZoomGesture.hpp" + +#include "../event/EventBus.hpp" -#include "../managers/HookSystemManager.hpp" #include "../protocols/types/ContentType.hpp" #include #include @@ -75,7 +82,7 @@ static Hyprlang::CParseResult configHandleGradientSet(const char* VALUE, void** const auto DATA = sc(*data); - CVarList varlist(V, 0, ' '); + CVarList2 varlist(std::string(V), 0, ' '); DATA->m_colors.clear(); std::string parseError = ""; @@ -84,9 +91,9 @@ static Hyprlang::CParseResult configHandleGradientSet(const char* VALUE, void** if (var.find("deg") != std::string::npos) { // last arg try { - DATA->m_angle = std::stoi(var.substr(0, var.find("deg"))) * (PI / 180.0); // radians + 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); + Log::logger->log(Log::WARN, "Error parsing gradient {}", V); parseError = "Error parsing gradient " + V; } @@ -94,24 +101,24 @@ static Hyprlang::CParseResult configHandleGradientSet(const char* VALUE, void** } if (DATA->m_colors.size() >= 10) { - Debug::log(WARN, "Error parsing gradient {}: max colors is 10.", V); + 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(var); + 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); + Log::logger->log(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); + Log::logger->log(Log::WARN, "Error parsing gradient {}", V); if (parseError.empty()) parseError = "Error parsing gradient " + V + ": No colors?"; @@ -139,7 +146,7 @@ static Hyprlang::CParseResult configHandleGapSet(const char* VALUE, void** data) *data = new CCssGapData(); const auto DATA = sc(*data); - CVarList varlist(V); + CVarList2 varlist((std::string(V))); Hyprlang::CParseResult result; try { @@ -299,54 +306,6 @@ static Hyprlang::CParseResult handleUnbind(const char* c, const char* v) { 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; -} - -static Hyprlang::CParseResult handleWindowRuleV2(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 handleBlurLS(const char* c, const char* v) { - const std::string VALUE = v; - const std::string COMMAND = c; - - const auto RESULT = g_pConfigManager->handleBlurLS(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; @@ -431,6 +390,42 @@ static Hyprlang::CParseResult handleGesture(const char* c, const char* v) { 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 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 = g_pConfigManager->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); @@ -463,7 +458,6 @@ CConfigManager::CConfigManager() { m_config = makeUnique(m_configPaths.begin()->c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = true}); registerConfigVar("general:border_size", Hyprlang::INT{1}); - registerConfigVar("general:no_border_on_floating", Hyprlang::INT{0}); 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"}); @@ -485,6 +479,7 @@ CConfigManager::CConfigManager() { 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}); @@ -511,13 +506,14 @@ CConfigManager::CConfigManager() { 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:new_window_takes_over_fullscreen", Hyprlang::INT{0}); + 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}); @@ -560,11 +556,14 @@ CConfigManager::CConfigManager() { registerConfigVar("group:groupbar:gaps_in", Hyprlang::INT{2}); registerConfigVar("group:groupbar:keep_upper_gap", Hyprlang::INT{1}); registerConfigVar("group:groupbar:text_offset", Hyprlang::INT{0}); + registerConfigVar("group:groupbar:text_padding", Hyprlang::INT{0}); + registerConfigVar("group:groupbar:blur", Hyprlang::INT{0}); registerConfigVar("debug:log_damage", Hyprlang::INT{0}); registerConfigVar("debug:overlay", Hyprlang::INT{0}); registerConfigVar("debug:damage_blink", Hyprlang::INT{0}); registerConfigVar("debug:pass", Hyprlang::INT{0}); + registerConfigVar("debug:gl_debugging", Hyprlang::INT{0}); registerConfigVar("debug:disable_logs", Hyprlang::INT{1}); registerConfigVar("debug:disable_time", Hyprlang::INT{1}); registerConfigVar("debug:enable_stdout_logs", Hyprlang::INT{0}); @@ -576,6 +575,10 @@ CConfigManager::CConfigManager() { registerConfigVar("debug:disable_scale_checks", Hyprlang::INT{0}); registerConfigVar("debug:colored_stdout_logs", Hyprlang::INT{1}); registerConfigVar("debug:full_cm_proto", Hyprlang::INT{0}); + registerConfigVar("debug:ds_handle_same_buffer", Hyprlang::INT{1}); + registerConfigVar("debug:ds_handle_same_buffer_fifo", Hyprlang::INT{1}); + registerConfigVar("debug:fifo_pending_workaround", Hyprlang::INT{0}); + registerConfigVar("debug:render_solitary_wo_damage", Hyprlang::INT{0}); registerConfigVar("decoration:rounding", Hyprlang::INT{0}); registerConfigVar("decoration:rounding_power", {2.F}); @@ -615,6 +618,9 @@ CConfigManager::CConfigManager() { registerConfigVar("decoration:screen_shader", {STRVAL_EMPTY}); registerConfigVar("decoration:border_part_of_window", Hyprlang::INT{1}); + registerConfigVar("layout:single_window_aspect_ratio", Hyprlang::VEC2{0, 0}); + registerConfigVar("layout:single_window_aspect_ratio_tolerance", {0.1f}); + registerConfigVar("dwindle:pseudotile", Hyprlang::INT{0}); registerConfigVar("dwindle:force_split", Hyprlang::INT{0}); registerConfigVar("dwindle:permanent_direction_override", Hyprlang::INT{0}); @@ -627,8 +633,6 @@ CConfigManager::CConfigManager() { registerConfigVar("dwindle:smart_split", Hyprlang::INT{0}); registerConfigVar("dwindle:smart_resizing", Hyprlang::INT{1}); registerConfigVar("dwindle:precise_mouse_move", Hyprlang::INT{0}); - registerConfigVar("dwindle:single_window_aspect_ratio", Hyprlang::VEC2{0, 0}); - registerConfigVar("dwindle:single_window_aspect_ratio_tolerance", {0.1f}); registerConfigVar("master:special_scale_factor", {1.f}); registerConfigVar("master:mfact", {0.55f}); @@ -639,12 +643,21 @@ CConfigManager::CConfigManager() { registerConfigVar("master:new_on_active", {"none"}); registerConfigVar("master:new_on_top", Hyprlang::INT{0}); registerConfigVar("master:orientation", {"left"}); - registerConfigVar("master:inherit_fullscreen", Hyprlang::INT{1}); 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}); @@ -715,9 +728,9 @@ CConfigManager::CConfigManager() { registerConfigVar("binds:movefocus_cycles_fullscreen", Hyprlang::INT{0}); registerConfigVar("binds:movefocus_cycles_groupfirst", Hyprlang::INT{0}); registerConfigVar("binds:disable_keybind_grabbing", Hyprlang::INT{0}); - registerConfigVar("binds:window_direction_monitor_fallback", Hyprlang::INT{1}); registerConfigVar("binds:allow_pin_fullscreen", Hyprlang::INT{0}); registerConfigVar("binds:drag_threshold", Hyprlang::INT{0}); + registerConfigVar("binds:window_direction_monitor_fallback", Hyprlang::INT{1}); registerConfigVar("gestures:workspace_swipe_distance", Hyprlang::INT{300}); registerConfigVar("gestures:workspace_swipe_invert", Hyprlang::INT{1}); @@ -753,10 +766,12 @@ CConfigManager::CConfigManager() { 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}); @@ -782,13 +797,17 @@ CConfigManager::CConfigManager() { registerConfigVar("render:cm_auto_hdr", Hyprlang::INT{1}); registerConfigVar("render:new_render_scheduling", Hyprlang::INT{0}); registerConfigVar("render:non_shader_cm", Hyprlang::INT{3}); - registerConfigVar("render:cm_sdr_eotf", Hyprlang::INT{0}); + registerConfigVar("render:cm_sdr_eotf", {"default"}); + registerConfigVar("render:commit_timing_enabled", Hyprlang::INT{1}); + registerConfigVar("render:icc_vcgt_enabled", Hyprlang::INT{1}); + registerConfigVar("render:use_shader_blur_blend", Hyprlang::INT{0}); registerConfigVar("ecosystem:no_update_news", Hyprlang::INT{0}); registerConfigVar("ecosystem:no_donation_nag", Hyprlang::INT{0}); registerConfigVar("ecosystem:enforce_permissions", Hyprlang::INT{0}); - registerConfigVar("experimental:xx_color_management_v4", 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"}); @@ -844,7 +863,7 @@ CConfigManager::CConfigManager() { m_config->addSpecialConfigValue("monitorv2", "mirror", {STRVAL_EMPTY}); m_config->addSpecialConfigValue("monitorv2", "bitdepth", {STRVAL_EMPTY}); // TODO use correct type m_config->addSpecialConfigValue("monitorv2", "cm", {"auto"}); - m_config->addSpecialConfigValue("monitorv2", "sdr_eotf", Hyprlang::INT{0}); + m_config->addSpecialConfigValue("monitorv2", "sdr_eotf", {"default"}); m_config->addSpecialConfigValue("monitorv2", "sdrbrightness", Hyprlang::FLOAT{1.0}); m_config->addSpecialConfigValue("monitorv2", "sdrsaturation", Hyprlang::FLOAT{1.0}); m_config->addSpecialConfigValue("monitorv2", "vrr", Hyprlang::INT{0}); @@ -856,6 +875,17 @@ CConfigManager::CConfigManager() { m_config->addSpecialConfigValue("monitorv2", "min_luminance", Hyprlang::FLOAT{-1.0}); m_config->addSpecialConfigValue("monitorv2", "max_luminance", Hyprlang::INT{-1}); m_config->addSpecialConfigValue("monitorv2", "max_avg_luminance", Hyprlang::INT{-1}); + m_config->addSpecialConfigValue("monitorv2", "icc", Hyprlang::STRING{""}); + + // windowrule v3 + m_config->addSpecialCategory("windowrule", {.key = "name"}); + 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}); @@ -867,19 +897,21 @@ CConfigManager::CConfigManager() { 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(&::handleWindowRuleV2, "windowrulev2", {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(&::handleBlurLS, "blurls", {false}); m_config->registerHandler(&::handlePlugin, "plugin", {false}); m_config->registerHandler(&::handlePermission, "permission", {false}); - m_config->registerHandler(&::handleGesture, "gesture", {false}); + m_config->registerHandler(&::handleGesture, "gesture", {true}); m_config->registerHandler(&::handleEnv, "env", {true}); + // windowrulev2 and layerrulev2 errors + m_config->registerHandler(&::handleWindowrulev2, "windowrulev2", {false}); + m_config->registerHandler(&::handleLayerrulev2, "layerrulev2", {false}); + // pluginza m_config->addSpecialCategory("plugin", {nullptr, true}); @@ -888,40 +920,65 @@ CConfigManager::CConfigManager() { 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); + Log::logger->log(Log::DEBUG, "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, + 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"); } - 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}); }); } -std::optional CConfigManager::generateConfig(std::string configPath) { +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, bool safeMode) { 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); + Log::logger->log(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"); + Log::logger->log(Log::WARN, "Creating config home directory"); } - Debug::log(WARN, "No config file found; attempting to generate."); + Log::logger->log(Log::WARN, "No config file found; attempting to generate."); std::ofstream ofs; ofs.open(configPath, std::ios::trunc); - ofs << AUTOGENERATED_PREFIX << EXAMPLE_CONFIG; + 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()) @@ -931,7 +988,16 @@ std::optional CConfigManager::generateConfig(std::string configPath } std::string CConfigManager::getMainConfigPath() { - static std::string CONFIG_PATH = [this]() -> std::string { + static bool lastSafeMode = g_pCompositor->m_safeMode; + static auto getCfgPath = [this]() -> std::string { + lastSafeMode = g_pCompositor->m_safeMode; + m_firstExecDispatched = false; + + if (g_pCompositor->m_safeMode) { + const auto CONFIGPATH = g_pCompositor->m_instancePath + "/recoverycfg.conf"; + return generateConfig(CONFIGPATH, false).value(); + } + if (!g_pCompositor->m_explicitConfigPath.empty()) return g_pCompositor->m_explicitConfigPath; @@ -946,7 +1012,13 @@ std::string CConfigManager::getMainConfigPath() { 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."); - }(); + }; + static std::string CONFIG_PATH = getCfgPath(); + + if (lastSafeMode != g_pCompositor->m_safeMode) { + CONFIG_PATH = getCfgPath(); + m_config->changeRootPath(CONFIG_PATH.c_str()); + } return CONFIG_PATH; } @@ -968,7 +1040,7 @@ std::string CConfigManager::getConfigString() { std::ifstream configFile(path); configString += ("\n\nConfig File: " + path + ": "); if (!configFile.is_open()) { - Debug::log(LOG, "Config file not readable/found!"); + Log::logger->log(Log::DEBUG, "Config file not readable/found!"); configString += "Read Failed\n"; continue; } @@ -983,13 +1055,36 @@ 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); + Event::bus()->m_events.config.preReload.emit(); setDefaultAnimationVars(); resetHLConfig(); - m_configCurrentPath = getMainConfigPath(); - const auto ERR = m_config->parse(); + 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); } @@ -1057,29 +1152,28 @@ void CConfigManager::setDefaultAnimationVars() { std::optional CConfigManager::resetHLConfig() { m_monitorRules.clear(); - m_windowRules.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_blurLSNamespaces.clear(); m_workspaceRules.clear(); setDefaultAnimationVars(); // reset anims m_declaredPlugins.clear(); - m_layerRules.clear(); m_failedPluginConfigValues.clear(); m_finalExecRequests.clear(); + m_keywordRules.clear(); // paths m_configPaths.clear(); std::string mainConfigPath = getMainConfigPath(); - Debug::log(LOG, "Using config: {}", mainConfigPath); + Log::logger->log(Log::DEBUG, "Using config: {}", mainConfigPath); m_configPaths.emplace_back(mainConfigPath); const auto RET = verifyConfigExists(); + reloadRuleConfigs(); + return RET; } @@ -1106,7 +1200,8 @@ std::optional CConfigManager::handleMonitorv2(const std::string& ou 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])}); + // 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()); @@ -1119,8 +1214,18 @@ std::optional CConfigManager::handleMonitorv2(const std::string& ou if (VAL && VAL->m_bSetByUser) parser.parseCM(std::any_cast(VAL->getValue())); VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdr_eotf", output.c_str()); - if (VAL && VAL->m_bSetByUser) - parser.rule().sdrEotf = std::any_cast(VAL->getValue()); + if (VAL && VAL->m_bSetByUser) { + const std::string value = std::any_cast(VAL->getValue()); + // remap legacy + if (value == "0") + parser.rule().sdrEotf = NTransferFunction::TF_AUTO; + else if (value == "1") + parser.rule().sdrEotf = NTransferFunction::TF_SRGB; + else if (value == "2") + parser.rule().sdrEotf = NTransferFunction::TF_GAMMA22; + else + parser.rule().sdrEotf = NTransferFunction::fromString(value); + } VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdrbrightness", output.c_str()); if (VAL && VAL->m_bSetByUser) parser.rule().sdrBrightness = std::any_cast(VAL->getValue()); @@ -1157,6 +1262,10 @@ std::optional CConfigManager::handleMonitorv2(const std::string& ou if (VAL && VAL->m_bSetByUser) parser.rule().maxAvgLuminance = std::any_cast(VAL->getValue()); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "icc", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().iccFile = std::any_cast(VAL->getValue()); + auto newrule = parser.rule(); std::erase_if(m_monitorRules, [&](const auto& other) { return other.name == newrule.name; }); @@ -1178,6 +1287,77 @@ Hyprlang::CParseResult CConfigManager::handleMonitorv2() { 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(); @@ -1188,7 +1368,8 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) { static auto PZOOMFACTOR = CConfigValue("cursor:zoom_factor"); for (auto const& m : g_pCompositor->m_monitors) { *(m->m_cursorZoom) = *PZOOMFACTOR; - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id); + if (m->m_activeWorkspace) + m->m_activeWorkspace->m_space->recalculate(); } // Update the keyboard layout to the cfg'd one if this is not the first launch @@ -1256,9 +1437,6 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) { // Update window border colors g_pCompositor->updateAllWindowsAnimatedDecorationValues(); - // update layout - g_pLayoutManager->switchToLayout(std::any_cast(m_config->getConfigValue("general:layout"))); - // manual crash if (std::any_cast(m_config->getConfigValue("debug:manual_crash")) && !m_manualCrashInitiated) { m_manualCrashInitiated = true; @@ -1269,11 +1447,9 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) { 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()); + 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 @@ -1299,7 +1475,10 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) { if (!m_isFirstLaunch) ensurePersistentWorkspacesPresent(); - EMIT_HOOK_EVENT("configReloaded", nullptr); + // update layouts + Layout::Supplementary::algoMatcher()->updateWorkspaceLayouts(); + + Event::bus()->m_events.config.reloaded.emit(); if (g_pEventManager) g_pEventManager->postEvent(SHyprIPCEvent{"configreloaded", ""}); } @@ -1307,11 +1486,10 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) { void CConfigManager::init() { g_pConfigWatcher->setOnChange([this](const CConfigWatcher::SConfigWatchEvent& e) { - Debug::log(LOG, "CConfigManager: file {} modified, reloading", e.file); + Log::logger->log(Log::DEBUG, "CConfigManager: file {} modified, reloading", e.file); reload(); }); - const std::string CONFIGPATH = getMainConfigPath(); reload(); m_isFirstLaunch = false; @@ -1322,8 +1500,9 @@ std::string CConfigManager::parseKeyword(const std::string& COMMAND, const std:: // invalidate layouts if they changed if (COMMAND == "monitor" || COMMAND.contains("gaps_") || COMMAND.starts_with("dwindle:") || COMMAND.starts_with("master:")) { - for (auto const& m : g_pCompositor->m_monitors) - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id); + for (auto const& m : g_pCompositor->m_monitors) { + g_layoutManager->recalculateMonitor(m); + } } // Update window border colors @@ -1389,35 +1568,35 @@ SMonitorRule CConfigManager::getMonitorRuleFor(const PHLMONITOR PMONITOR) { if (!CONFIG) return rule; - Debug::log(LOG, "CConfigManager::getMonitorRuleFor: found a wlr_output_manager override for {}", PMONITOR->m_name); + Log::logger->log(Log::DEBUG, "CConfigManager::getMonitorRuleFor: found a wlr_output_manager override for {}", PMONITOR->m_name); - Debug::log(LOG, " > overriding enabled: {} -> {}", !rule.disabled, !CONFIG->enabled); + Log::logger->log(Log::DEBUG, " > 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); + Log::logger->log(Log::DEBUG, " > 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); + Log::logger->log(Log::DEBUG, " > 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)); + Log::logger->log(Log::DEBUG, " > 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)); + Log::logger->log(Log::DEBUG, " > 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); + Log::logger->log(Log::DEBUG, " > overriding vrr: {} -> {}", rule.vrr.value_or(0), CONFIG->adaptiveSync); rule.vrr = sc(CONFIG->adaptiveSync); } @@ -1430,7 +1609,7 @@ SMonitorRule CConfigManager::getMonitorRuleFor(const PHLMONITOR PMONITOR) { } } - Debug::log(WARN, "No rule found for {}, trying to use the first.", PMONITOR->m_name); + Log::logger->log(Log::WARN, "No rule found for {}, trying to use the first.", PMONITOR->m_name); for (auto const& r : m_monitorRules) { if (r.name.empty()) { @@ -1438,7 +1617,7 @@ SMonitorRule CConfigManager::getMonitorRuleFor(const PHLMONITOR PMONITOR) { } } - Debug::log(WARN, "No rules configured. Using the default hardcoded one."); + Log::logger->log(Log::WARN, "No rules configured. Using the default hardcoded one."); return applyWlrOutputConfig(SMonitorRule{.autoDir = eAutoDirs::DIR_AUTO_RIGHT, .name = "", @@ -1495,6 +1674,8 @@ SWorkspaceRule CConfigManager::mergeWorkspaceRules(const SWorkspaceRule& rule1, mergedRule.onCreatedEmptyRunCmd = rule2.onCreatedEmptyRunCmd; if (rule2.defaultName.has_value()) mergedRule.defaultName = rule2.defaultName; + if (rule2.layout.has_value()) + mergedRule.layout = rule2.layout; if (!rule2.layoutopts.empty()) { for (const auto& layoutopt : rule2.layoutopts) { mergedRule.layoutopts[layoutopt.first] = layoutopt.second; @@ -1503,229 +1684,6 @@ SWorkspaceRule CConfigManager::mergeWorkspaceRules(const SWorkspaceRule& rule1, return mergedRule; } -std::vector> CConfigManager::getMatchingRules(PHLWINDOW pWindow, bool dynamic, bool shadowExec) { - if (!valid(pWindow)) - return std::vector>(); - - // if the window is unmapped, don't process exec rules yet. - shadowExec = shadowExec || !pWindow->m_isMapped; - - std::vector> returns; - - Debug::log(LOG, "Searching for matching rules for {} (title: {})", pWindow->m_class, pWindow->m_title); - - // since some rules will be applied later, we need to store some flags - bool hasFloating = pWindow->m_isFloating; - bool hasFullscreen = pWindow->isFullscreen(); - bool isGrouped = pWindow->m_groupData.pNextWindow; - - // local tags for dynamic tag rule match - auto tags = pWindow->m_tags; - - for (auto const& rule : m_windowRules) { - // check if we have a matching rule - if (!rule->m_v2) { - try { - if (rule->m_value.starts_with("tag:") && !tags.isTagged(rule->m_value.substr(4))) - continue; - - if (rule->m_value.starts_with("title:") && !rule->m_v1Regex.passes(pWindow->m_title)) - continue; - - if (!rule->m_v1Regex.passes(pWindow->m_class)) - continue; - - } catch (...) { - Debug::log(ERR, "Regex error at {}", rule->m_value); - continue; - } - } else { - try { - if (rule->m_X11 != -1) { - if (pWindow->m_isX11 != rule->m_X11) - continue; - } - - if (rule->m_floating != -1) { - if (hasFloating != rule->m_floating) - continue; - } - - if (rule->m_fullscreen != -1) { - if (hasFullscreen != rule->m_fullscreen) - continue; - } - - if (rule->m_pinned != -1) { - if (pWindow->m_pinned != rule->m_pinned) - continue; - } - - if (rule->m_focus != -1) { - if (rule->m_focus != (g_pCompositor->m_lastWindow.lock() == pWindow)) - continue; - } - - if (rule->m_group != -1) { - if (rule->m_group != isGrouped) - continue; - } - - if (rule->m_modal != -1) { - if (rule->m_modal != pWindow->isModal()) - continue; - } - - if (!rule->m_fullscreenState.empty()) { - const auto ARGS = CVarList(rule->m_fullscreenState, 2, ' '); - // - std::optional internalMode, clientMode; - - if (ARGS[0] == "*") - internalMode = std::nullopt; - else if (isNumber(ARGS[0])) - internalMode = sc(std::stoi(ARGS[0])); - else - throw std::runtime_error("szFullscreenState internal mode not valid"); - - if (ARGS[1] == "*") - clientMode = std::nullopt; - else if (isNumber(ARGS[1])) - clientMode = sc(std::stoi(ARGS[1])); - else - throw std::runtime_error("szFullscreenState client mode not valid"); - - if (internalMode.has_value() && pWindow->m_fullscreenState.internal != internalMode) - continue; - - if (clientMode.has_value() && pWindow->m_fullscreenState.client != clientMode) - continue; - } - - if (!rule->m_onWorkspace.empty()) { - const auto PWORKSPACE = pWindow->m_workspace; - if (!PWORKSPACE || !PWORKSPACE->matchesStaticSelector(rule->m_onWorkspace)) - continue; - } - - if (!rule->m_contentType.empty()) { - try { - const auto contentType = NContentType::fromString(rule->m_contentType); - if (pWindow->getContentType() != contentType) - continue; - } catch (std::exception& e) { Debug::log(ERR, "Rule \"content:{}\" failed with: {}", rule->m_contentType, e.what()); } - } - - if (!rule->m_xdgTag.empty()) { - if (pWindow->xdgTag().value_or("") != rule->m_xdgTag) - continue; - } - - if (!rule->m_workspace.empty()) { - const auto PWORKSPACE = pWindow->m_workspace; - - if (!PWORKSPACE) - continue; - - if (rule->m_workspace.starts_with("name:")) { - if (PWORKSPACE->m_name != rule->m_workspace.substr(5)) - continue; - } else { - // number - if (!isNumber(rule->m_workspace)) - throw std::runtime_error("szWorkspace not name: or number"); - - const int64_t ID = std::stoll(rule->m_workspace); - - if (PWORKSPACE->m_id != ID) - continue; - } - } - - if (!rule->m_tag.empty() && !tags.isTagged(rule->m_tag)) - continue; - - if (!rule->m_class.empty() && !rule->m_classRegex.passes(pWindow->m_class)) - continue; - - if (!rule->m_title.empty() && !rule->m_titleRegex.passes(pWindow->m_title)) - continue; - - if (!rule->m_initialTitle.empty() && !rule->m_initialTitleRegex.passes(pWindow->m_initialTitle)) - continue; - - if (!rule->m_initialClass.empty() && !rule->m_initialClassRegex.passes(pWindow->m_initialClass)) - continue; - - } catch (std::exception& e) { - Debug::log(ERR, "Regex error at {} ({})", rule->m_value, e.what()); - continue; - } - } - - // applies. Read the rule and behave accordingly - Debug::log(LOG, "Window rule {} -> {} matched {}", rule->m_rule, rule->m_value, pWindow); - - returns.emplace_back(rule); - - // apply tag with local tags - if (rule->m_ruleType == CWindowRule::RULE_TAG) { - CVarList vars{rule->m_rule, 0, 's', true}; - if (vars.size() == 2 && vars[0] == "tag") - tags.applyTag(vars[1], true); - } - - if (dynamic) - continue; - - if (rule->m_rule == "float") - hasFloating = true; - else if (rule->m_rule == "fullscreen") - hasFullscreen = true; - } - - std::vector PIDs = {sc(pWindow->getPID())}; - while (getPPIDof(PIDs.back()) > 10) - PIDs.push_back(getPPIDof(PIDs.back())); - - bool anyExecFound = false; - - for (auto const& er : m_execRequestedRules) { - if (std::ranges::any_of(PIDs, [&](const auto& pid) { return pid == er.iPid; })) { - returns.emplace_back(makeShared(er.szRule, "", false, true)); - anyExecFound = true; - } - } - - if (anyExecFound && !shadowExec) // remove exec rules to unclog searches in the future, why have the garbage here. - std::erase_if(m_execRequestedRules, [&](const SExecRequestedRule& other) { return std::ranges::any_of(PIDs, [&](const auto& pid) { return pid == other.iPid; }); }); - - return returns; -} - -std::vector> CConfigManager::getMatchingRules(PHLLS pLS) { - std::vector> returns; - - if (!pLS->m_layerSurface || pLS->m_fadingOut) - return returns; - - for (auto const& lr : m_layerRules) { - if (lr->m_targetNamespace.starts_with("address:0x")) { - if (std::format("address:0x{:x}", rc(pLS.get())) != lr->m_targetNamespace) - continue; - } else if (!lr->m_targetNamespaceRegex.passes(pLS->m_layerSurface->m_layerNamespace)) - continue; - - // hit - returns.emplace_back(lr); - } - - if (shouldBlurLS(pLS->m_layerSurface->m_layerNamespace)) - returns.emplace_back(makeShared(pLS->m_layerSurface->m_layerNamespace, "blur")); - - return returns; -} - void CConfigManager::dispatchExecOnce() { if (m_firstExecDispatched || m_isFirstLaunch) return; @@ -1756,7 +1714,12 @@ void CConfigManager::dispatchExecOnce() { g_pInputManager->setTabletConfigs(); // check for user's possible errors with their setup and notify them if needed - g_pCompositor->performUserChecks(); + // this is additionally guarded because exiting safe mode will re-run this. + static bool once = true; + if (once) { + g_pCompositor->performUserChecks(); + once = false; + } } void CConfigManager::dispatchExecShutdown() { @@ -1804,7 +1767,7 @@ void CConfigManager::performMonitorReload() { m_wantsMonitorReload = false; - EMIT_HOOK_EVENT("monitorLayoutChanged", nullptr); + Event::bus()->m_events.monitor.layoutChanged.emit(); } void* const* CConfigManager::getConfigValuePtr(const std::string& val) { @@ -1831,16 +1794,6 @@ bool CConfigManager::deviceConfigExists(const std::string& dev) { return m_config->specialCategoryExistsForKey("device", copy.c_str()); } -bool CConfigManager::shouldBlurLS(const std::string& ns) { - for (auto const& bls : m_blurLSNamespaces) { - if (bls == ns) { - return true; - } - } - - return false; -} - void CConfigManager::ensureMonitorStatus() { for (auto const& rm : g_pCompositor->m_realMonitors) { if (!rm->m_output || rm->m_isUnsafeFallback) @@ -1868,33 +1821,50 @@ void CConfigManager::ensureVRR(PHLMONITOR pMonitor) { 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); + Log::logger->log(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); + 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()) - Debug::log(ERR, "Couldn't commit output {} in ensureVRR -> true", m->m_output->name); + if (!m->m_state.commit()) + Log::logger->log(Log::ERR, "Couldn't commit output {} in ensureVRR -> false", m->m_output->name); + } + m->m_vrrActive = false; } - m->m_vrrActive = true; return; } else if (USEVRR == 2 || USEVRR == 3) { - const auto PWORKSPACE = m->m_activeWorkspace; - if (!PWORKSPACE) return; // ??? bool wantVRR = PWORKSPACE->m_hasFullscreenWindow && (PWORKSPACE->m_fullscreenMode & FSMODE_FULLSCREEN); - if (wantVRR && PWORKSPACE->getFullscreenWindow()->m_windowData.noVRR.valueOrDefault()) + if (wantVRR && PWORKSPACE->getFullscreenWindow()->m_ruleApplicator->noVRR().valueOrDefault()) wantVRR = false; if (wantVRR && USEVRR == 3) { @@ -1910,7 +1880,7 @@ void CConfigManager::ensureVRR(PHLMONITOR pMonitor) { m->m_output->state->setAdaptiveSync(true); if (!m->m_state.test()) { - Debug::log(LOG, "Pending output {} does not accept VRR.", m->m_output->name); + Log::logger->log(Log::DEBUG, "Pending output {} does not accept VRR.", m->m_output->name); m->m_output->state->setAdaptiveSync(false); } } @@ -1963,10 +1933,6 @@ const std::vector& CConfigManager::getAllWorkspaceRules() { return m_workspaceRules; } -void CConfigManager::addExecRule(const SExecRequestedRule& rule) { - m_execRequestedRules.push_back(rule); -} - void CConfigManager::handlePluginLoads() { if (!g_pPluginSystem) return; @@ -2086,7 +2052,7 @@ static bool parseModeLine(const std::string& modeline, drmModeModeInfo& mode) { return false; if (args.size() < 10) { - Debug::log(ERR, "modeline parse error: expected at least 9 arguments, got {}", args.size() - 1); + Log::logger->log(Log::ERR, "modeline parse error: expected at least 9 arguments, got {}", args.size() - 1); return false; } @@ -2105,7 +2071,7 @@ static bool parseModeLine(const std::string& modeline, drmModeModeInfo& mode) { 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()); + Log::logger->log(Log::ERR, "modeline parse error: invalid numeric value: {}", e.what()); return false; } @@ -2128,7 +2094,7 @@ static bool parseModeLine(const std::string& modeline, drmModeModeInfo& mode) { if (it != flagsmap.end()) mode.flags |= it->second; else - Debug::log(ERR, "Invalid flag {} in modeline", key); + 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); @@ -2210,11 +2176,11 @@ bool CMonitorRuleParser::parsePosition(const std::string& value, bool isFirst) { 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'."); + 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; } @@ -2265,7 +2231,7 @@ bool CMonitorRuleParser::parseTransform(const std::string& value) { const auto TSF = std::stoi(value); if (std::clamp(TSF, 0, 7) != TSF) { - Debug::log(ERR, "Invalid transform {} in monitor", TSF); + Log::logger->log(Log::ERR, "Invalid transform {} in monitor", TSF); m_error += "invalid transform "; return false; } @@ -2318,6 +2284,15 @@ bool CMonitorRuleParser::parseVRR(const std::string& value) { return true; } +bool CMonitorRuleParser::parseICC(const std::string& val) { + if (val.empty()) { + m_error += "invalid icc "; + return false; + } + m_rule.iccFile = val; + return true; +} + void CMonitorRuleParser::setDisabled() { m_rule.disabled = true; } @@ -2326,23 +2301,22 @@ void CMonitorRuleParser::setMirror(const std::string& value) { m_rule.mirrorOf = value; } -bool CMonitorRuleParser::setReserved(const SMonitorAdditionalReservedArea& value) { - g_pConfigManager->m_mAdditionalReservedAreas[name()] = value; +bool CMonitorRuleParser::setReserved(const Desktop::CReservedArea& value) { + m_rule.reservedArea = value; return true; } std::optional CConfigManager::handleMonitor(const std::string& command, const std::string& args) { - // get the monitor config - const auto ARGS = CVarList(args); + const auto ARGS = CVarList2(std::string(args)); - auto parser = CMonitorRuleParser(ARGS[0]); + 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(ARGS[2])) + if (!parser.parseTransform(std::string(ARGS[2]))) return parser.getError(); const auto TRANSFORM = parser.rule().transform; @@ -2357,12 +2331,24 @@ std::optional CConfigManager::handleMonitor(const std::string& comm return {}; } else if (ARGS[1] == "addreserved") { + std::optional area; try { - parser.setReserved({.top = std::stoi(ARGS[2]), .bottom = std::stoi(ARGS[3]), .left = std::stoi(ARGS[4]), .right = std::stoi(ARGS[5])}); + // 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"; } - return {}; + + if (!area.has_value()) + return "parse error: bad addreserved"; + + auto rule = std::ranges::find_if(m_monitorRules, [n = ARGS[0]](const auto& other) { return other.name == n; }); + if (rule != m_monitorRules.end()) { + rule->reservedArea = area.value(); + return {}; + } + + // fall } else { - Debug::log(ERR, "ConfigManager parseMonitor, curitem bogus???"); + Log::logger->log(Log::ERR, "ConfigManager parseMonitor, curitem bogus???"); return "parse error: curitem bogus"; } @@ -2373,36 +2359,39 @@ std::optional CConfigManager::handleMonitor(const std::string& comm return {}; } - parser.parseMode(ARGS[1]); - parser.parsePosition(ARGS[2]); - parser.parseScale(ARGS[3]); + 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(ARGS[argno + 1]); + parser.setMirror(std::string(ARGS[argno + 1])); argno++; } else if (ARGS[argno] == "bitdepth") { - parser.parseBitdepth(ARGS[argno + 1]); + parser.parseBitdepth(std::string(ARGS[argno + 1])); argno++; } else if (ARGS[argno] == "cm") { - parser.parseCM(ARGS[argno + 1]); + parser.parseCM(std::string(ARGS[argno + 1])); argno++; } else if (ARGS[argno] == "sdrsaturation") { - parser.parseSDRSaturation(ARGS[argno + 1]); + parser.parseSDRSaturation(std::string(ARGS[argno + 1])); argno++; } else if (ARGS[argno] == "sdrbrightness") { - parser.parseSDRBrightness(ARGS[argno + 1]); + parser.parseSDRBrightness(std::string(ARGS[argno + 1])); argno++; } else if (ARGS[argno] == "transform") { - parser.parseTransform(ARGS[argno + 1]); + parser.parseTransform(std::string(ARGS[argno + 1])); argno++; } else if (ARGS[argno] == "vrr") { - parser.parseVRR(ARGS[argno + 1]); + parser.parseVRR(std::string(ARGS[argno + 1])); + argno++; + } else if (ARGS[argno] == "icc") { + parser.parseICC(std::string(ARGS[argno + 1])); argno++; } else if (ARGS[argno] == "workspace") { - const auto& [id, name, isAutoID] = getWorkspaceIDNameFromString(ARGS[argno + 1]); + const auto& [id, name, isAutoID] = getWorkspaceIDNameFromString(std::string(ARGS[argno + 1])); SWorkspaceRule wsRule; wsRule.monitor = parser.name(); @@ -2413,8 +2402,8 @@ std::optional CConfigManager::handleMonitor(const std::string& comm m_workspaceRules.emplace_back(wsRule); argno++; } else { - Debug::log(ERR, "Config error: invalid monitor syntax at \"{}\"", ARGS[argno]); - return "invalid syntax at \"" + ARGS[argno] + "\""; + Log::logger->log(Log::ERR, "Config error: invalid monitor syntax at \"{}\"", ARGS[argno]); + return "invalid syntax at \"" + std::string(ARGS[argno]) + "\""; } argno++; @@ -2539,52 +2528,45 @@ std::optional CConfigManager::handleBind(const std::string& command // 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; - const auto BINDARGS = command.substr(4); + 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) { - if (arg == 'l') { - locked = true; - } else if (arg == 'r') { - release = true; - } else if (arg == 'e') { - repeat = true; - } else if (arg == 'm') { - mouse = true; - } else if (arg == 'n') { - nonConsuming = true; - } else if (arg == 't') { - transparent = true; - } else if (arg == 'i') { - ignoreMods = true; - } else if (arg == 's') { - multiKey = true; - } else if (arg == 'o') { - longPress = true; - } else if (arg == 'd') { - hasDescription = true; - } else if (arg == 'p') { - dontInhibit = true; - } else if (arg == 'c') { - click = true; - release = true; - } else if (arg == 'g') { - drag = true; - release = true; - } else { - return "bind: invalid flag"; + 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"; } } @@ -2637,12 +2619,12 @@ std::optional CConfigManager::handleBind(const std::string& command const auto DISPATCHER = g_pKeybindManager->m_dispatchers.find(HANDLER); if (DISPATCHER == g_pKeybindManager->m_dispatchers.end()) { - Debug::log(ERR, "Invalid dispatcher: {}", HANDLER); + Log::logger->log(Log::ERR, "Invalid dispatcher: {}", HANDLER); return "Invalid dispatcher, requested \"" + HANDLER + "\" does not exist"; } if (MOD == 0 && !MODSTR.empty()) { - Debug::log(ERR, "Invalid mod: {}", MODSTR); + Log::logger->log(Log::ERR, "Invalid mod: {}", MODSTR); return "Invalid mod, requested mod \"" + MODSTR + "\" is not a valid mod."; } @@ -2650,14 +2632,14 @@ std::optional CConfigManager::handleBind(const std::string& command SParsedKey parsedKey = parseKey(KEY); if (parsedKey.catchAll && m_currentSubmap.name.empty()) { - Debug::log(ERR, "Catchall not allowed outside of submap!"); + 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}); + click, drag, submapUniversal}); } return {}; @@ -2682,239 +2664,6 @@ std::optional CConfigManager::handleUnbind(const std::string& comma return {}; } -std::optional CConfigManager::handleWindowRule(const std::string& command, const std::string& value) { - const auto VARLIST = CVarList(value, 0, ',', true); - - std::vector tokens; - std::unordered_map params; - - bool parsingParams = false; - - for (const auto& varStr : VARLIST) { - std::string_view var = varStr; - auto sep = var.find(':'); - std::string_view key = (sep != std::string_view::npos) ? var.substr(0, sep) : var; - bool isParam = (sep != std::string_view::npos && !(key.starts_with("workspace ") || (key.starts_with("monitor ")) || key.ends_with("plugin"))); - - if (!parsingParams) { - if (!isParam) { - tokens.emplace_back(var); - continue; - } - - parsingParams = true; - } - - if (sep == std::string_view::npos) - return std::format("Invalid rule: {}, Invalid parameter: {}", value, std::string(var)); - - auto pos = var.find_first_not_of(' ', sep + 1); - std::string_view val = (pos != std::string_view::npos) ? var.substr(pos) : std::string_view{}; - params[key] = val; - } - - auto get = [&](std::string_view key) -> std::string_view { - if (auto it = params.find(key); it != params.end()) - return it->second; - return {}; - }; - - auto applyParams = [&](SP rule) -> bool { - bool set = false; - - if (auto v = get("class"); !v.empty()) { - set |= (rule->m_class = v, true); - rule->m_classRegex = {std::string(v)}; - } - if (auto v = get("title"); !v.empty()) { - set |= (rule->m_title = v, true); - rule->m_titleRegex = {std::string(v)}; - } - if (auto v = get("tag"); !v.empty()) - set |= (rule->m_tag = v, true); - if (auto v = get("initialClass"); !v.empty()) { - set |= (rule->m_initialClass = v, true); - rule->m_initialClassRegex = {std::string(v)}; - } - if (auto v = get("initialTitle"); !v.empty()) { - set |= (rule->m_initialTitle = v, true); - rule->m_initialTitleRegex = {std::string(v)}; - } - - if (auto v = get("xwayland"); !v.empty()) - set |= (rule->m_X11 = (v == "1"), true); - if (auto v = get("floating"); !v.empty()) - set |= (rule->m_floating = (v == "1"), true); - if (auto v = get("fullscreen"); !v.empty()) - set |= (rule->m_fullscreen = (v == "1"), true); - if (auto v = get("pinned"); !v.empty()) - set |= (rule->m_pinned = (v == "1"), true); - if (auto v = get("focus"); !v.empty()) - set |= (rule->m_focus = (v == "1"), true); - if (auto v = get("group"); !v.empty()) - set |= (rule->m_group = (v == "1"), true); - if (auto v = get("modal"); !v.empty()) - set |= (rule->m_modal = (v == "1"), true); - - if (auto v = get("fullscreenstate"); !v.empty()) - set |= (rule->m_fullscreenState = v, true); - if (auto v = get("workspace"); !v.empty()) - set |= (rule->m_workspace = v, true); - if (auto v = get("onworkspace"); !v.empty()) - set |= (rule->m_onWorkspace = v, true); - if (auto v = get("content"); !v.empty()) - set |= (rule->m_contentType = v, true); - if (auto v = get("xdgTag"); !v.empty()) - set |= (rule->m_xdgTag = v, true); - - return set; - }; - - std::vector> rules; - - for (auto token : tokens) { - if (token.starts_with("unset")) { - std::string ruleName = ""; - if (token.size() <= 6 || token.contains("all")) - ruleName = "all"; - else - ruleName = std::string(token.substr(6)); - auto rule = makeShared(ruleName, value, true); - applyParams(rule); - std::erase_if(m_windowRules, [&](const auto& other) { - if (!other->m_v2) - return other->m_class == rule->m_class && !rule->m_class.empty(); - - if (rule->m_ruleType != other->m_ruleType && ruleName != "all") - return false; - if (!rule->m_tag.empty() && rule->m_tag != other->m_tag) - return false; - if (!rule->m_class.empty() && rule->m_class != other->m_class) - return false; - if (!rule->m_title.empty() && rule->m_title != other->m_title) - return false; - if (!rule->m_initialClass.empty() && rule->m_initialClass != other->m_initialClass) - return false; - if (!rule->m_initialTitle.empty() && rule->m_initialTitle != other->m_initialTitle) - return false; - if (rule->m_X11 != -1 && rule->m_X11 != other->m_X11) - return false; - if (rule->m_floating != -1 && rule->m_floating != other->m_floating) - return false; - if (rule->m_fullscreen != -1 && rule->m_fullscreen != other->m_fullscreen) - return false; - if (rule->m_pinned != -1 && rule->m_pinned != other->m_pinned) - return false; - if (!rule->m_fullscreenState.empty() && rule->m_fullscreenState != other->m_fullscreenState) - return false; - if (!rule->m_workspace.empty() && rule->m_workspace != other->m_workspace) - return false; - if (rule->m_focus != -1 && rule->m_focus != other->m_focus) - return false; - if (!rule->m_onWorkspace.empty() && rule->m_onWorkspace != other->m_onWorkspace) - return false; - if (!rule->m_contentType.empty() && rule->m_contentType != other->m_contentType) - return false; - if (rule->m_group != -1 && rule->m_group != other->m_group) - return false; - if (rule->m_modal != -1 && rule->m_modal != other->m_modal) - return false; - return true; - }); - } else { - auto rule = makeShared(std::string(token), value, true); - if (rule->m_ruleType == CWindowRule::RULE_INVALID) { - Debug::log(ERR, "Invalid rule found: {}, Invalid value: {}", value, token); - return std::format("Invalid rule found: {}, Invalid value: {}", value, token); - } - if (applyParams(rule)) - rules.emplace_back(rule); - else { - Debug::log(INFO, "===== Skipping rule: {}, Invalid parameters", rule->m_value); - return std::format("Invalid parameters found in: {}", value); - } - } - } - - if (rules.empty() && tokens.empty()) - return "Invalid rule syntax: no rules provided"; - - for (auto& rule : rules) { - if (rule->m_ruleType == CWindowRule::RULE_SIZE || rule->m_ruleType == CWindowRule::RULE_MAXSIZE || rule->m_ruleType == CWindowRule::RULE_MINSIZE) - m_windowRules.insert(m_windowRules.begin(), rule); - else - m_windowRules.emplace_back(rule); - } - - return {}; -} - -std::optional CConfigManager::handleLayerRule(const std::string& command, const std::string& value) { - const auto RULE = trim(value.substr(0, value.find_first_of(','))); - const auto VALUE = trim(value.substr(value.find_first_of(',') + 1)); - - // check rule and value - if (RULE.empty() || VALUE.empty()) - return "empty rule?"; - - if (RULE == "unset") { - std::erase_if(m_layerRules, [&](const auto& other) { return other->m_targetNamespace == VALUE; }); - return {}; - } - - auto rule = makeShared(RULE, VALUE); - - if (rule->m_ruleType == CLayerRule::RULE_INVALID) { - Debug::log(ERR, "Invalid rule found: {}", RULE); - return "Invalid rule found: " + RULE; - } - - rule->m_targetNamespaceRegex = {VALUE}; - - m_layerRules.emplace_back(rule); - - for (auto const& m : g_pCompositor->m_monitors) - for (auto const& lsl : m->m_layerSurfaceLayers) - for (auto const& ls : lsl) - ls->applyRules(); - - return {}; -} - -void CConfigManager::updateBlurredLS(const std::string& name, const bool forceBlur) { - const bool BYADDRESS = name.starts_with("address:"); - std::string matchName = name; - - if (BYADDRESS) - matchName = matchName.substr(8); - - for (auto const& m : g_pCompositor->m_monitors) { - for (auto const& lsl : m->m_layerSurfaceLayers) { - for (auto const& ls : lsl) { - if (BYADDRESS) { - if (std::format("0x{:x}", rc(ls.get())) == matchName) - ls->m_forceBlur = forceBlur; - } else if (ls->m_namespace == matchName) - ls->m_forceBlur = forceBlur; - } - } - } -} - -std::optional CConfigManager::handleBlurLS(const std::string& command, const std::string& value) { - if (value.starts_with("remove,")) { - const auto TOREMOVE = trim(value.substr(7)); - if (std::erase_if(m_blurLSNamespaces, [&](const auto& other) { return other == TOREMOVE; })) - updateBlurredLS(TOREMOVE, false); - return {}; - } - - m_blurLSNamespaces.emplace_back(value); - updateBlurredLS(value, true); - - 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(','); @@ -2933,7 +2682,7 @@ std::optional CConfigManager::handleWorkspaceRules(const std::strin // 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); + // Log::logger->log(Log::ERR, "Invalid workspace identifier found: {}", wsIdent); // return "Invalid workspace identifier found: " + wsIdent; // } // wsRule.monitor = first_ident; @@ -2955,14 +2704,14 @@ std::optional CConfigManager::handleWorkspaceRules(const std::strin auto assignRule = [&](std::string rule) -> std::optional { size_t delim = std::string::npos; if ((delim = rule.find("gapsin:")) != std::string::npos) { - CVarList varlist = CVarList(rule.substr(delim + 7), 0, ' '); - wsRule.gapsIn = CCssGapData(); + 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) { - CVarList varlist = CVarList(rule.substr(delim + 8), 0, ' '); - wsRule.gapsOut = CCssGapData(); + 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); } @@ -2999,7 +2748,7 @@ std::optional CConfigManager::handleWorkspaceRules(const std::strin std::string opt = rule.substr(delim + 10); if (!opt.contains(":")) { // invalid - Debug::log(ERR, "Invalid workspace rule found: {}", rule); + Log::logger->log(Log::ERR, "Invalid workspace rule found: {}", rule); return "Invalid workspace rule found: " + rule; } @@ -3007,6 +2756,9 @@ std::optional CConfigManager::handleWorkspaceRules(const std::strin opt = opt.substr(0, opt.find(':')); wsRule.layoutopts[opt] = val; + } else if ((delim = rule.find("layout:")) != std::string::npos) { + std::string layout = rule.substr(delim + 7); + wsRule.layout = std::move(layout); } return {}; @@ -3014,9 +2766,9 @@ std::optional CConfigManager::handleWorkspaceRules(const std::strin #undef CHECK_OR_THROW - CVarList rulesList{rules, 0, ',', true}; + CVarList2 rulesList(std::string(rules), 0, ',', true); for (auto const& r : rulesList) { - const auto R = assignRule(r); + const auto R = assignRule(std::string(r)); if (R.has_value()) return R; } @@ -3035,15 +2787,15 @@ std::optional CConfigManager::handleWorkspaceRules(const std::strin } std::optional CConfigManager::handleSubmap(const std::string&, const std::string& submap) { - const auto SUBMAP = CConstVarList(submap); - m_currentSubmap.name = (SUBMAP[0] == "reset") ? "" : SUBMAP[0]; - m_currentSubmap.reset = SUBMAP[1]; + 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"); + Log::logger->log(Log::ERR, "source= path garbage"); return "source= path " + rawpath + " bogus!"; } @@ -3057,7 +2809,7 @@ std::optional CConfigManager::handleSource(const std::string& comma 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); + Log::logger->log(Log::ERR, "{}", err); return err; } @@ -3070,7 +2822,7 @@ std::optional CConfigManager::handleSource(const std::string& comma auto file_status = std::filesystem::status(value, ec); if (ec) { - Debug::log(ERR, "source= file from glob result is inaccessible ({}): {}", ec.message(), value); + Log::logger->log(Log::ERR, "source= file from glob result is inaccessible ({}): {}", ec.message(), value); return "source= file " + value + " is inaccessible!"; } @@ -3083,10 +2835,10 @@ std::optional CConfigManager::handleSource(const std::string& comma if (THISRESULT.error && errorsFromParsing.empty()) errorsFromParsing += THISRESULT.getError(); } else if (std::filesystem::is_directory(file_status)) { - Debug::log(WARN, "source= skipping directory {}", value); + Log::logger->log(Log::WARN, "source= skipping directory {}", value); continue; } else { - Debug::log(WARN, "source= skipping non-regular-file {}", value); + Log::logger->log(Log::WARN, "source= skipping non-regular-file {}", value); continue; } } @@ -3136,13 +2888,15 @@ std::optional CConfigManager::handlePlugin(const std::string& comma } std::optional CConfigManager::handlePermission(const std::string& command, const std::string& value) { - CVarList data(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") @@ -3160,14 +2914,14 @@ std::optional CConfigManager::handlePermission(const std::string& c if (mode == PERMISSION_RULE_ALLOW_MODE_UNKNOWN) return "unknown permission allow mode"; - if (m_isFirstLaunch) - g_pDynamicPermissionManager->addConfigPermissionRule(data[0], type, 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) { - CConstVarList data(value); + CVarList2 data((std::string(value))); size_t fingerCount = 0; eTrackpadGestureDirection direction = TRACKPAD_GESTURE_DIR_NONE; @@ -3184,9 +2938,17 @@ std::optional CConfigManager::handleGesture(const std::string& comm if (direction == TRACKPAD_GESTURE_DIR_NONE) return std::format("Invalid direction: {}", data[1]); - int startDataIdx = 2; - uint32_t modMask = 0; - float deltaScale = 1.F; + int startDataIdx = 2; + uint32_t modMask = 0; + float deltaScale = 1.F; + bool disableInhibit = false; + + for (const auto arg : command.substr(7)) { + switch (arg) { + case 'p': disableInhibit = true; break; + default: return "gesture: invalid flag"; + } + } while (true) { @@ -3209,23 +2971,29 @@ std::optional CConfigManager::handleGesture(const std::string& comm if (data[startDataIdx] == "dispatcher") result = g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}, data.join(",", startDataIdx + 2)), fingerCount, - direction, modMask, deltaScale); + direction, modMask, deltaScale, disableInhibit); else if (data[startDataIdx] == "workspace") - result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale); + result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale, disableInhibit); else if (data[startDataIdx] == "resize") - result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale); + result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale, disableInhibit); else if (data[startDataIdx] == "move") - result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale); + result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale, disableInhibit); else if (data[startDataIdx] == "special") - result = g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale); + result = + g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale, disableInhibit); else if (data[startDataIdx] == "close") - result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale); + result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale, disableInhibit); else if (data[startDataIdx] == "float") - result = g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale); + result = + g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale, disableInhibit); else if (data[startDataIdx] == "fullscreen") - result = g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale); - else if (data[startDataIdx] == "unset") - result = g_pTrackpadGestures->removeGesture(fingerCount, direction, modMask, deltaScale); + result = g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale, + disableInhibit); + else if (data[startDataIdx] == "cursorZoom") { + result = g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}, std::string{data[startDataIdx + 2]}), fingerCount, + direction, modMask, deltaScale, disableInhibit); + } else if (data[startDataIdx] == "unset") + result = g_pTrackpadGestures->removeGesture(fingerCount, direction, modMask, deltaScale, disableInhibit); else return std::format("Invalid gesture: {}", data[startDataIdx]); @@ -3235,6 +3003,82 @@ std::optional CConfigManager::handleGesture(const std::string& comm 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; } @@ -3252,7 +3096,7 @@ bool CConfigManager::shouldUseSoftwareCursors(PHLMONITOR pMonitor) { switch (*PNOHW) { case 0: return false; case 1: return true; - case 2: return g_pHyprRenderer->isNvidia() && g_pHyprRenderer->isMgpu(); + case 2: return g_pHyprRenderer->isNvidia() && (g_pHyprRenderer->isMgpu() || g_pCompositor->isVRRActiveOnAnyMonitor()); default: break; } @@ -3265,7 +3109,7 @@ std::string SConfigOptionDescription::jsonify() const { [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); + Log::logger->log(Log::ERR, "invalid SConfigOptionDescription: no config option {} exists", value); return std::string{""}; } const char* const EXPLICIT = PTR->m_bSetByUser ? "true" : "false"; @@ -3279,20 +3123,20 @@ std::string SConfigOptionDescription::jsonify() const { else if (typeid(Hyprlang::FLOAT) == std::type_index(CONFIGVALUE.type())) currentValue = std::format("{:.2f}", std::any_cast(CONFIGVALUE)); else if (typeid(Hyprlang::STRING) == std::type_index(CONFIGVALUE.type())) - currentValue = std::any_cast(CONFIGVALUE); + currentValue = std::format("\"{}\"", std::any_cast(CONFIGVALUE)); else if (typeid(Hyprlang::VEC2) == std::type_index(CONFIGVALUE.type())) { const auto V = std::any_cast(CONFIGVALUE); - currentValue = std::format("{}, {}", V.x, V.y); + currentValue = std::format("\"{}, {}\"", V.x, V.y); } else if (typeid(void*) == std::type_index(CONFIGVALUE.type())) { const auto DATA = sc(std::any_cast(CONFIGVALUE)); - currentValue = DATA->toString(); + currentValue = std::format("\"{}\"", DATA->toString()); } try { using T = std::decay_t; if constexpr (std::is_same_v) { return std::format(R"#( "value": "{}", - "current": "{}", + "current": {}, "explicit": {})#", val.value, currentValue, EXPLICIT); } else if constexpr (std::is_same_v) { @@ -3311,7 +3155,7 @@ std::string SConfigOptionDescription::jsonify() const { val.value, val.min, val.max, currentValue, EXPLICIT); } else if constexpr (std::is_same_v) { return std::format(R"#( "value": "{}", - "current": "{}", + "current": {}, "explicit": {})#", val.color.getAsHex(), currentValue, EXPLICIT); } else if constexpr (std::is_same_v) { @@ -3332,17 +3176,17 @@ std::string SConfigOptionDescription::jsonify() const { "min_y": {}, "max_x": {}, "max_y": {}, - "current": "{}", + "current": {}, "explicit": {})#", val.vec.x, val.vec.y, val.min.x, val.min.y, val.max.x, val.max.y, currentValue, EXPLICIT); } else if constexpr (std::is_same_v) { return std::format(R"#( "value": "{}", - "current": "{}", + "current": {}, "explicit": {})#", val.gradient, currentValue, EXPLICIT); } - } catch (std::bad_any_cast& e) { Debug::log(ERR, "Bad any_cast on value {} in descriptions", value); } + } catch (std::bad_any_cast& e) { Log::logger->log(Log::ERR, "Bad any_cast on value {} in descriptions", value); } return std::string{""}; }, data); @@ -3367,7 +3211,7 @@ void CConfigManager::ensurePersistentWorkspacesPresent() { } 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); + Log::logger->log(Log::DEBUG, "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; @@ -3378,9 +3222,9 @@ std::optional CConfigManager::getStoredFloatingSize(PHLWINDOW window) // 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); + Log::logger->log(Log::DEBUG, "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); + Log::logger->log(Log::DEBUG, "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; diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp index 7f32be41..36e25f6b 100644 --- a/src/config/ConfigManager.hpp +++ b/src/config/ConfigManager.hpp @@ -12,15 +12,14 @@ #include #include #include "../helpers/Monitor.hpp" -#include "../desktop/Window.hpp" -#include "../desktop/LayerRule.hpp" +#include "../desktop/view/Window.hpp" #include "ConfigDataValues.hpp" #include "../SharedDefs.hpp" #include "../helpers/Color.hpp" #include "../desktop/DesktopTypes.hpp" +#include "../desktop/reserved/ReservedArea.hpp" #include "../helpers/memory/Memory.hpp" -#include "../desktop/WindowRule.hpp" #include "../managers/XWaylandManager.hpp" #include "../managers/KeybindManager.hpp" @@ -47,16 +46,10 @@ struct SWorkspaceRule { std::optional noShadow; std::optional onCreatedEmptyRunCmd; std::optional defaultName; + std::optional layout; 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 = ""; @@ -68,11 +61,6 @@ struct SPluginVariable { std::string name = ""; }; -struct SExecRequestedRule { - std::string szRule = ""; - uint64_t iPid = 0; -}; - enum eConfigOptionType : uint8_t { CONFIG_OPTION_BOOL = 0, CONFIG_OPTION_INT = 1, /* e.g. 0/1/2*/ @@ -189,10 +177,11 @@ class CMonitorRuleParser { bool parseSDRBrightness(const std::string& value); bool parseSDRSaturation(const std::string& value); bool parseVRR(const std::string& value); + bool parseICC(const std::string& value); void setDisabled(); void setMirror(const std::string& value); - bool setReserved(const SMonitorAdditionalReservedArea& value); + bool setReserved(const Desktop::CReservedArea& value); private: SMonitorRule m_rule; @@ -203,39 +192,34 @@ class CConfigManager { public: CConfigManager(); - void init(); - void reload(); - std::string verify(); + 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); - bool shouldBlurLS(const std::string&); + 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(); + 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&); + 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(); + PHLMONITOR getBoundMonitorForWS(const std::string&); + std::string getBoundMonitorStringForWS(const std::string&); + const std::vector& getAllWorkspaceRules(); - std::vector> getMatchingRules(PHLWINDOW, bool dynamic = true, bool shadowExec = false); - std::vector> getMatchingRules(PHLLS); - void ensurePersistentWorkspacesPresent(); + void ensurePersistentWorkspacesPresent(); - const std::vector& getAllDescriptions(); - - std::unordered_map m_mAdditionalReservedAreas; + const std::vector& getAllDescriptions(); const std::unordered_map>& getAnimationConfig(); @@ -260,8 +244,6 @@ class CConfigManager { SP getAnimationPropertyConfig(const std::string&); - void addExecRule(const SExecRequestedRule&); - void handlePluginLoads(); std::string getErrors(); @@ -274,22 +256,24 @@ class CConfigManager { 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 handleWindowRule(const std::string&, const std::string&); - std::optional handleLayerRule(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 handleBlurLS(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; @@ -310,19 +294,16 @@ class CConfigManager { SSubmap m_currentSubmap; - std::vector m_execRequestedRules; // rules requested with exec, e.g. [workspace 2] kitty - 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; - std::vector> m_windowRules; - std::vector> m_layerRules; - std::vector m_blurLSNamespaces; bool m_firstExecDispatched = false; bool m_manualCrashInitiated = false; @@ -336,11 +317,11 @@ class CConfigManager { uint32_t m_configValueNumber = 0; // internal methods - void updateBlurredLS(const std::string&, const bool); void setDefaultAnimationVars(); std::optional resetHLConfig(); - std::optional generateConfig(std::string configPath); + std::optional generateConfig(std::string configPath, bool safeMode = false); std::optional verifyConfigExists(); + void reloadRuleConfigs(); void postConfigReload(const Hyprlang::CParseResult& result); SWorkspaceRule mergeWorkspaceRules(const SWorkspaceRule&, const SWorkspaceRule&); diff --git a/src/config/ConfigWatcher.cpp b/src/config/ConfigWatcher.cpp index f3a8a2ca..83f3011c 100644 --- a/src/config/ConfigWatcher.cpp +++ b/src/config/ConfigWatcher.cpp @@ -1,7 +1,9 @@ #include "ConfigWatcher.hpp" +#if defined(__linux__) #include +#endif #include -#include "../debug/Log.hpp" +#include "../debug/log/Logger.hpp" #include #include #include @@ -11,14 +13,14 @@ using namespace Hyprutils::OS; 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; } @@ -76,19 +78,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/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index a0ccddd0..3f51e8df 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -40,21 +40,28 @@ using namespace Hyprutils::OS; #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 "../render/Renderer.hpp" #include "../render/OpenGL.hpp" +#include "../layout/space/Space.hpp" +#include "../layout/algorithm/Algorithm.hpp" +#include "../layout/algorithm/TiledAlgorithm.hpp" +#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp" #if defined(__DragonFly__) || defined(__FreeBSD__) #include @@ -141,13 +148,13 @@ std::string CHyprCtl::getSolitaryBlockedReason(Hyprutils::Memory::CSharedPointer } const std::array DS_REASONS_JSON = { - "\"UNKNOWN\"", "\"USER\"", "\"WINDOWED\"", "\"CONTENT\"", "\"MIRROR\"", "\"RECORD\"", "\"SW\"", - "\"CANDIDATE\"", "\"SURFACE\"", "\"TRANSFORM\"", "\"DMA\"", "\"TEARING\"", "\"FAILED\"", "\"CM\"", + "\"UNKNOWN\"", "\"USER\"", "\"WINDOWED\"", "\"CONTENT\"", "\"MIRROR\"", "\"RECORD\"", "\"SW\"", + "\"CANDIDATE\"", "\"SURFACE\"", "\"TRANSFORM\"", "\"DMA\"", "\"FAILED\"", "\"CM\"", }; const std::array DS_REASONS_TEXT = { - "unknown reason", "user settings", "windowed mode", "content type", "monitor mirrors", "screen record/screenshot", "software renders/cursors", - "missing candidate", "invalid surface", "surface transformations", "invalid buffer", "tearing", "activation failed", "color management", + "unknown reason", "user settings", "windowed mode", "content type", "monitor mirrors", "screen record/screenshot", "software renders/cursors", + "missing candidate", "invalid surface", "surface transformations", "invalid buffer", "activation failed", "color management", }; std::string CHyprCtl::getDSBlockedReason(Hyprutils::Memory::CSharedPointer m, eHyprCtlOutputFormat format) { @@ -170,14 +177,13 @@ std::string CHyprCtl::getDSBlockedReason(Hyprutils::Memory::CSharedPointer TEARING_REASONS_JSON = { - "\"UNKNOWN\"", "\"NOT_TORN\"", "\"USER\"", "\"ZOOM\"", "\"SUPPORT\"", "\"CANDIDATE\"", "\"WINDOW\"", + "\"UNKNOWN\"", "\"NOT_TORN\"", "\"USER\"", "\"ZOOM\"", "\"SUPPORT\"", "\"CANDIDATE\"", "\"WINDOW\"", "\"HW_CURSOR\"", }; -const std::array TEARING_REASONS_TEXT = { - "unknown reason", "next frame is not torn", "user settings", "zoom", "not supported by monitor", "missing candidate", "window settings", -}; +const std::array TEARING_REASONS_TEXT = {"unknown reason", "next frame is not torn", "user settings", "zoom", + "not supported by monitor", "missing candidate", "window settings", "hw cursor"}; -std::string CHyprCtl::getTearingBlockedReason(Hyprutils::Memory::CSharedPointer m, eHyprCtlOutputFormat format) { +std::string CHyprCtl::getTearingBlockedReason(Hyprutils::Memory::CSharedPointer m, eHyprCtlOutputFormat format) { const auto reasons = m->isTearingBlocked(true); if (!reasons || (reasons == CMonitor::TC_NOT_TORN && m->m_tearingState.activelyTearing)) return "null"; @@ -253,13 +259,13 @@ std::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer 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), (m == g_pCompositor->m_lastMonitor ? "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)); + 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)); } else { result += std::format( @@ -272,8 +278,8 @@ std::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer 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_transform), (m == g_pCompositor->m_lastMonitor ? "yes" : "no"), sc(m->m_dpmsStatus), m->m_output->state->state().adaptiveSync, + 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), @@ -317,7 +323,7 @@ static std::string monitorsRequest(eHyprCtlOutputFormat format, std::string requ } static std::string getTagsData(PHLWINDOW w, eHyprCtlOutputFormat format) { - const auto tags = w->m_tags.getTags(); + const auto tags = w->m_ruleApplicator->m_tagKeeper.getTags(); if (format == eHyprCtlOutputFormat::FORMAT_JSON) return std::ranges::fold_left(tags, std::string(), @@ -328,23 +334,19 @@ static std::string getTagsData(PHLWINDOW w, eHyprCtlOutputFormat format) { static std::string getGroupedData(PHLWINDOW w, eHyprCtlOutputFormat format) { const bool isJson = format == eHyprCtlOutputFormat::FORMAT_JSON; - if (w->m_groupData.pNextWindow.expired()) + if (!w->m_group) return isJson ? "" : "0"; std::ostringstream result; - PHLWINDOW head = w->getGroupHead(); - PHLWINDOW curr = head; - while (true) { + for (const auto& curr : w->m_group->windows()) { if (isJson) result << std::format("\"0x{:x}\"", rc(curr.get())); else result << std::format("{:x}", rc(curr.get())); - curr = curr->m_groupData.pNextWindow.lock(); - // We've wrapped around to the start, break out without trailing comma - if (curr == head) - break; - result << (isJson ? ", " : ","); + + if (curr != w->m_group->windows().back()) + result << (isJson ? ", " : ","); } return result.str(); @@ -352,9 +354,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 < g_pCompositor->m_windowFocusHistory.size(); ++i) { - if (g_pCompositor->m_windowFocusHistory[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; }; @@ -372,7 +375,6 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) { "name": "{}" }}, "floating": {}, - "pseudo": {}, "monitor": {}, "class": "{}", "title": "{}", @@ -383,34 +385,39 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) { "pinned": {}, "fullscreen": {}, "fullscreenClient": {}, + "overFullscreen": {}, "grouped": [{}], "tags": [{}], "swallowing": "0x{:x}", "focusHistoryID": {}, "inhibitingIdle": {}, "xdgTag": "{}", - "xdgDescription": "{}" + "xdgDescription": "{}", + "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(""))); + (g_pInputManager->isWindowInhibiting(w, false) ? "true" : "false"), escapeJSONStrings(w->xdgTag().value_or("")), escapeJSONStrings(w->xdgDescription().value_or("")), + escapeJSONStrings(NContentType::toString(w->getContentType())), w->m_stableID); } 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\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("")); + w->xdgTag().value_or(""), w->xdgDescription().value_or(""), NContentType::toString(w->getContentType()), w->m_stableID); } } @@ -444,8 +451,15 @@ static std::string clientsRequest(eHyprCtlOutputFormat format, std::string reque } std::string CHyprCtl::getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat format) { - const auto PLASTW = w->getLastFocusedWindow(); - const auto PMONITOR = w->m_monitor.lock(); + const auto PLASTW = w->getLastFocusedWindow(); + const auto PMONITOR = w->m_monitor.lock(); + + std::string layoutName = "unknown"; + if (w->m_space && w->m_space->algorithm() && w->m_space->algorithm()->tiledAlgo()) { + const auto& TILED_ALGO = w->m_space->algorithm()->tiledAlgo(); + layoutName = Layout::Supplementary::algoMatcher()->getNameForTiledAlgo(&typeid(*TILED_ALGO.get())); + } + if (format == eHyprCtlOutputFormat::FORMAT_JSON) { return std::format(R"#({{ "id": {}, @@ -456,16 +470,17 @@ std::string CHyprCtl::getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat form "hasfullscreen": {}, "lastwindow": "0x{:x}", "lastwindowtitle": "{}", - "ispersistent": {} + "ispersistent": {}, + "tiledLayout": "{}" }})#", w->m_id, escapeJSONStrings(w->m_name), escapeJSONStrings(PMONITOR ? PMONITOR->m_name : "?"), escapeJSONStrings(PMONITOR ? std::to_string(PMONITOR->m_id) : "null"), w->getWindows(), w->m_hasFullscreenWindow ? "true" : "false", - rc(PLASTW.get()), PLASTW ? escapeJSONStrings(PLASTW->m_title) : "", w->isPersistent() ? "true" : "false"); + rc(PLASTW.get()), PLASTW ? escapeJSONStrings(PLASTW->m_title) : "", w->isPersistent() ? "true" : "false", escapeJSONStrings(layoutName)); } else { - return std::format( - "workspace ID {} ({}) on monitor {}:\n\tmonitorID: {}\n\twindows: {}\n\thasfullscreen: {}\n\tlastwindow: 0x{:x}\n\tlastwindowtitle: {}\n\tispersistent: {}\n\n", - w->m_id, w->m_name, PMONITOR ? PMONITOR->m_name : "?", PMONITOR ? std::to_string(PMONITOR->m_id) : "null", w->getWindows(), sc(w->m_hasFullscreenWindow), - rc(PLASTW.get()), PLASTW ? PLASTW->m_title : "", sc(w->isPersistent())); + return std::format("workspace ID {} ({}) on monitor {}:\n\tmonitorID: {}\n\twindows: {}\n\thasfullscreen: {}\n\tlastwindow: 0x{:x}\n\tlastwindowtitle: {}\n\tispersistent: " + "{}\n\ttiledLayout: {}\n\n", + w->m_id, w->m_name, PMONITOR ? PMONITOR->m_name : "?", PMONITOR ? std::to_string(PMONITOR->m_id) : "null", w->getWindows(), + sc(w->m_hasFullscreenWindow), rc(PLASTW.get()), PLASTW ? PLASTW->m_title : "", sc(w->isPersistent()), layoutName); } } @@ -521,11 +536,11 @@ static std::string getWorkspaceRuleData(const SWorkspaceRule& r, eHyprCtlOutputF } static std::string activeWorkspaceRequest(eHyprCtlOutputFormat format, std::string request) { - if (!g_pCompositor->m_lastMonitor) + if (!Desktop::focusState()->monitor()) return "unsafe state"; std::string result = ""; - auto w = g_pCompositor->m_lastMonitor->m_activeWorkspace; + auto w = Desktop::focusState()->monitor()->m_activeWorkspace; if (!valid(w)) return "internal error"; @@ -575,7 +590,7 @@ static std::string workspaceRulesRequest(eHyprCtlOutputFormat format, std::strin } static std::string activeWindowRequest(eHyprCtlOutputFormat format, std::string request) { - const auto PWINDOW = g_pCompositor->m_lastWindow.lock(); + const auto PWINDOW = Desktop::focusState()->window(); if (!validMapped(PWINDOW)) return format == eHyprCtlOutputFormat::FORMAT_JSON ? "{}" : "Invalid"; @@ -664,28 +679,6 @@ static std::string layersRequest(eHyprCtlOutputFormat format, std::string reques return result; } -static std::string layoutsRequest(eHyprCtlOutputFormat format, std::string request) { - std::string result = ""; - if (format == eHyprCtlOutputFormat::FORMAT_JSON) { - result += "["; - - for (auto const& m : g_pLayoutManager->getAllLayoutNames()) { - result += std::format( - R"#( - "{}",)#", - m); - } - trimTrailingComma(result); - - result += "\n]\n"; - } else { - for (auto const& m : g_pLayoutManager->getAllLayoutNames()) { - result += std::format("{}\n", m); - } - } - return result; -} - static std::string configErrorsRequest(eHyprCtlOutputFormat format, std::string request) { std::string result = ""; std::string currErrors = g_pConfigManager->getErrors(); @@ -796,7 +789,7 @@ static std::string devicesRequest(eHyprCtlOutputFormat format, std::string reque result += std::format( R"#( {{ "address": "0x{:x}", - "type": "tabletTool", + "type": "tabletTool" }},)#", rc(d.get())); } @@ -953,11 +946,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; } @@ -1025,6 +1017,7 @@ static std::string bindsRequest(eHyprCtlOutputFormat format, std::string request "has_description": {}, "modmask": {}, "submap": "{}", + "submap_universal": "{}", "key": "{}", "keycode": {}, "catch_all": {}, @@ -1033,8 +1026,9 @@ static std::string bindsRequest(eHyprCtlOutputFormat format, std::string request "arg": "{}" }},)#", kb->locked ? "true" : "false", kb->mouse ? "true" : "false", kb->release ? "true" : "false", kb->repeat ? "true" : "false", kb->longPress ? "true" : "false", - kb->nonConsuming ? "true" : "false", kb->hasDescription ? "true" : "false", kb->modmask, escapeJSONStrings(kb->submap.name), escapeJSONStrings(kb->key), - kb->keycode, kb->catchAll ? "true" : "false", escapeJSONStrings(kb->description), escapeJSONStrings(kb->handler), escapeJSONStrings(kb->arg)); + kb->nonConsuming ? "true" : "false", kb->hasDescription ? "true" : "false", kb->modmask, escapeJSONStrings(kb->submap.name), kb->submapUniversal, + escapeJSONStrings(kb->key), kb->keycode, kb->catchAll ? "true" : "false", escapeJSONStrings(kb->description), escapeJSONStrings(kb->handler), + escapeJSONStrings(kb->arg)); } trimTrailingComma(ret); ret += "]"; @@ -1057,8 +1051,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"; @@ -1068,6 +1065,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 { @@ -1091,10 +1091,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\","; @@ -1102,6 +1104,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); @@ -1243,7 +1248,7 @@ 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; } @@ -1270,10 +1275,19 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in) if (COMMAND.empty()) return "Invalid input: command is empty"; + g_pHyprCtl->m_currentRequestParams.isDynamicKeyword = true; + std::string retval = g_pConfigManager->parseKeyword(COMMAND, VALUE); + g_pHyprCtl->m_currentRequestParams.isDynamicKeyword = false; + + if (COMMAND == "source") { + g_pConfigManager->m_wantsMonitorReload = true; + g_pEventLoopManager->doLater([] { g_pConfigManager->reloadRules(); }); + } + // if we are executing a dynamic source we have to reload everything, so every if will have a check for source. - if (COMMAND == "monitor" || COMMAND == "source") + if (COMMAND == "monitor") g_pConfigManager->m_wantsMonitorReload = true; // for monitor keywords if (COMMAND.contains("monitorv2")) @@ -1286,10 +1300,8 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in) g_pInputManager->setTabletConfigs(); // update tablets } - static auto PLAYOUT = CConfigValue("general:layout"); - - if (COMMAND.contains("general:layout")) - g_pLayoutManager->switchToLayout(*PLAYOUT); // update layout + if (COMMAND.contains("general:layout") || (COMMAND.contains("workspace") && VALUE.contains("layout:"))) + Layout::Supplementary::algoMatcher()->updateWorkspaceLayouts(); if (COMMAND.contains("decoration:screen_shader") || COMMAND == "source") g_pHyprOpenGL->m_reloadScreenShader = true; @@ -1304,20 +1316,31 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in) g_pConfigManager->updateWatcher(); // decorations will probably need a repaint - if (COMMAND.contains("decoration:") || COMMAND.contains("border") || COMMAND == "workspace" || COMMAND.contains("zoom_factor") || COMMAND == "source" || - COMMAND.starts_with("windowrule")) { + if (COMMAND.contains("decoration:") || COMMAND.contains("border") || COMMAND == "workspace" || COMMAND.contains("zoom_factor") || COMMAND == "source") { static auto PZOOMFACTOR = CConfigValue("cursor:zoom_factor"); for (auto const& m : g_pCompositor->m_monitors) { *(m->m_cursorZoom) = *PZOOMFACTOR; g_pHyprRenderer->damageMonitor(m); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id); + if (m->m_activeWorkspace) + m->m_activeWorkspace->m_space->recalculate(); + } + } + + if (COMMAND.contains("windowrule ") || COMMAND.contains("windowrule[")) + g_pConfigManager->reloadRules(); + + if (COMMAND.contains("layerrule") || COMMAND.contains("layerrule[")) { + g_pConfigManager->reloadRules(); + // Damage all monitors to redraw static layers. + for (auto const& m : g_pCompositor->m_monitors) { + g_pHyprRenderer->damageMonitor(m); } } if (COMMAND.contains("workspace")) g_pConfigManager->ensurePersistentWorkspacesPresent(); - Debug::log(LOG, "Hyprctl: keyword {} : {}", COMMAND, VALUE); + Log::logger->log(Log::DEBUG, "Hyprctl: keyword {} : {}", COMMAND, VALUE); if (retval.empty()) return "ok"; @@ -1519,11 +1542,6 @@ static std::string dispatchSeterror(eHyprCtlOutputFormat format, std::string req return "ok"; } -static std::string dispatchSetProp(eHyprCtlOutputFormat format, std::string request) { - auto result = g_pKeybindManager->m_dispatchers["setprop"](request.substr(request.find_first_of(' ') + 1)); - return "DEPRECATED: use hyprctl dispatch setprop instead" + (result.success ? "" : "\n" + result.error); -} - static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string request) { CVarList vars(request, 0, ' '); @@ -1541,9 +1559,9 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ const bool FORMNORM = format == FORMAT_NORMAL; auto sizeToString = [&](bool max) -> std::string { - auto sizeValue = PWINDOW->m_windowData.minSize.valueOr(Vector2D(MIN_WINDOW_SIZE, MIN_WINDOW_SIZE)); + auto sizeValue = PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D(MIN_WINDOW_SIZE, MIN_WINDOW_SIZE)); if (max) - sizeValue = PWINDOW->m_windowData.maxSize.valueOr(Vector2D(INFINITY, INFINITY)); + sizeValue = PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D(INFINITY, INFINITY)); if (FORMNORM) return std::format("{} {}", sizeValue.x, sizeValue.y); @@ -1554,7 +1572,7 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ } }; - auto alphaToString = [&](CWindowOverridableVar& alpha, bool getAlpha) -> std::string { + auto alphaToString = [&](Desktop::Types::COverridableVar& alpha, bool getAlpha) -> std::string { if (FORMNORM) { if (getAlpha) return std::format("{}", alpha.valueOrDefault().alpha); @@ -1578,7 +1596,7 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ static auto PGROUPACTIVELOCKEDCOL = CConfigValue("group:col.border_locked_active"); static auto PGROUPINACTIVELOCKEDCOL = CConfigValue("group:col.border_locked_inactive"); - const bool GROUPLOCKED = PWINDOW->m_groupData.pNextWindow.lock() ? PWINDOW->getGroupHead()->m_groupData.locked : false; + const bool GROUPLOCKED = PWINDOW->m_group ? PWINDOW->m_group->locked() : false; if (active) { auto* const ACTIVECOL = (CGradientValueData*)(PACTIVECOL.ptr())->getData(); @@ -1586,9 +1604,9 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ auto* const GROUPACTIVECOL = (CGradientValueData*)(PGROUPACTIVECOL.ptr())->getData(); auto* const GROUPACTIVELOCKEDCOL = (CGradientValueData*)(PGROUPACTIVELOCKEDCOL.ptr())->getData(); const auto* const ACTIVECOLOR = - !PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); + !PWINDOW->m_group ? (!(PWINDOW->m_groupRules & Desktop::View::GROUP_DENY) ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); - std::string borderColorString = PWINDOW->m_windowData.activeBorderColor.valueOr(*ACTIVECOLOR).toString(); + std::string borderColorString = PWINDOW->m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR).toString(); if (FORMNORM) return borderColorString; else @@ -1598,10 +1616,10 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ auto* const NOGROUPINACTIVECOL = (CGradientValueData*)(PNOGROUPINACTIVECOL.ptr())->getData(); auto* const GROUPINACTIVECOL = (CGradientValueData*)(PGROUPINACTIVECOL.ptr())->getData(); auto* const GROUPINACTIVELOCKEDCOL = (CGradientValueData*)(PGROUPINACTIVELOCKEDCOL.ptr())->getData(); - const auto* const INACTIVECOLOR = !PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) : - (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); + const auto* const INACTIVECOLOR = !PWINDOW->m_group ? (!(PWINDOW->m_groupRules & Desktop::View::GROUP_DENY) ? INACTIVECOL : NOGROUPINACTIVECOL) : + (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); - std::string borderColorString = PWINDOW->m_windowData.inactiveBorderColor.valueOr(*INACTIVECOLOR).toString(); + std::string borderColorString = PWINDOW->m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR).toString(); if (FORMNORM) return borderColorString; else @@ -1616,38 +1634,92 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ return std::format(R"({{"{}": {}}})", PROP, prop.valueOrDefault()); }; - if (PROP == "animationstyle") { - auto& animationStyle = PWINDOW->m_windowData.animationStyle; + if (PROP == "animation") { + auto& animationStyle = PWINDOW->m_ruleApplicator->animationStyle(); if (FORMNORM) return animationStyle.valueOr("(unset)"); else return std::format(R"({{"{}": "{}"}})", PROP, animationStyle.valueOr("")); - } else if (PROP == "maxsize") + } else if (PROP == "max_size") return sizeToString(true); - else if (PROP == "minsize") + else if (PROP == "min_size") return sizeToString(false); - else if (PROP == "alpha") - return alphaToString(PWINDOW->m_windowData.alpha, true); - else if (PROP == "alphainactive") - return alphaToString(PWINDOW->m_windowData.alphaInactive, true); - else if (PROP == "alphafullscreen") - return alphaToString(PWINDOW->m_windowData.alphaFullscreen, true); - else if (PROP == "alphaoverride") - return alphaToString(PWINDOW->m_windowData.alpha, false); - else if (PROP == "alphainactiveoverride") - return alphaToString(PWINDOW->m_windowData.alphaInactive, false); - else if (PROP == "alphafullscreenoverride") - return alphaToString(PWINDOW->m_windowData.alphaFullscreen, false); - else if (PROP == "activebordercolor") + else if (PROP == "opacity") + return alphaToString(PWINDOW->m_ruleApplicator->alpha(), true); + else if (PROP == "opacity_inactive") + return alphaToString(PWINDOW->m_ruleApplicator->alphaInactive(), true); + else if (PROP == "opacity_fullscreen") + return alphaToString(PWINDOW->m_ruleApplicator->alphaFullscreen(), true); + else if (PROP == "opacity_override") + return alphaToString(PWINDOW->m_ruleApplicator->alpha(), false); + else if (PROP == "opacity_inactive_override") + return alphaToString(PWINDOW->m_ruleApplicator->alphaInactive(), false); + else if (PROP == "opacity_fullscreen_override") + return alphaToString(PWINDOW->m_ruleApplicator->alphaFullscreen(), false); + else if (PROP == "active_border_color") return borderColorToString(true); - else if (PROP == "inactivebordercolor") + else if (PROP == "inactive_border_color") return borderColorToString(false); - else if (auto search = NWindowProperties::boolWindowProperties.find(PROP); search != NWindowProperties::boolWindowProperties.end()) - return windowPropToString(*search->second(PWINDOW)); - else if (auto search = NWindowProperties::intWindowProperties.find(PROP); search != NWindowProperties::intWindowProperties.end()) - return windowPropToString(*search->second(PWINDOW)); - else if (auto search = NWindowProperties::floatWindowProperties.find(PROP); search != NWindowProperties::floatWindowProperties.end()) - return windowPropToString(*search->second(PWINDOW)); + else if (PROP == "allows_input") + return windowPropToString(PWINDOW->m_ruleApplicator->allowsInput()); + else if (PROP == "decorate") + return windowPropToString(PWINDOW->m_ruleApplicator->decorate()); + else if (PROP == "focus_on_activate") + return windowPropToString(PWINDOW->m_ruleApplicator->focusOnActivate()); + else if (PROP == "keep_aspect_ratio") + return windowPropToString(PWINDOW->m_ruleApplicator->keepAspectRatio()); + else if (PROP == "nearest_neighbor") + return windowPropToString(PWINDOW->m_ruleApplicator->nearestNeighbor()); + else if (PROP == "no_anim") + return windowPropToString(PWINDOW->m_ruleApplicator->noAnim()); + else if (PROP == "no_blur") + return windowPropToString(PWINDOW->m_ruleApplicator->noBlur()); + else if (PROP == "no_dim") + return windowPropToString(PWINDOW->m_ruleApplicator->noDim()); + else if (PROP == "no_focus") + return windowPropToString(PWINDOW->m_ruleApplicator->noFocus()); + else if (PROP == "no_max_size") + return windowPropToString(PWINDOW->m_ruleApplicator->noMaxSize()); + else if (PROP == "no_shadow") + return windowPropToString(PWINDOW->m_ruleApplicator->noShadow()); + else if (PROP == "no_shortcuts_inhibit") + return windowPropToString(PWINDOW->m_ruleApplicator->noShortcutsInhibit()); + else if (PROP == "opaque") + return windowPropToString(PWINDOW->m_ruleApplicator->opaque()); + else if (PROP == "dim_around") + return windowPropToString(PWINDOW->m_ruleApplicator->dimAround()); + else if (PROP == "force_rgbx") + return windowPropToString(PWINDOW->m_ruleApplicator->RGBX()); + else if (PROP == "sync_fullscreen") + return windowPropToString(PWINDOW->m_ruleApplicator->syncFullscreen()); + else if (PROP == "immediate") + return windowPropToString(PWINDOW->m_ruleApplicator->tearing()); + else if (PROP == "xray") + return windowPropToString(PWINDOW->m_ruleApplicator->xray()); + else if (PROP == "render_unfocused") + return windowPropToString(PWINDOW->m_ruleApplicator->renderUnfocused()); + else if (PROP == "no_follow_mouse") + return windowPropToString(PWINDOW->m_ruleApplicator->noFollowMouse()); + else if (PROP == "no_screen_share") + return windowPropToString(PWINDOW->m_ruleApplicator->noScreenShare()); + else if (PROP == "no_vrr") + return windowPropToString(PWINDOW->m_ruleApplicator->noVRR()); + else if (PROP == "persistent_size") + return windowPropToString(PWINDOW->m_ruleApplicator->persistentSize()); + else if (PROP == "stay_focused") + return windowPropToString(PWINDOW->m_ruleApplicator->stayFocused()); + else if (PROP == "idle_inhibit") + return windowPropToString(PWINDOW->m_ruleApplicator->idleInhibitMode()); + else if (PROP == "border_size") + return windowPropToString(PWINDOW->m_ruleApplicator->borderSize()); + else if (PROP == "rounding") + return windowPropToString(PWINDOW->m_ruleApplicator->rounding()); + else if (PROP == "rounding_power") + return windowPropToString(PWINDOW->m_ruleApplicator->roundingPower()); + else if (PROP == "scroll_mouse") + return windowPropToString(PWINDOW->m_ruleApplicator->scrollMouse()); + else if (PROP == "scroll_touchpad") + return windowPropToString(PWINDOW->m_ruleApplicator->scrollTouchpad()); return "prop not found"; } @@ -1977,7 +2049,12 @@ static std::string submapRequest(eHyprCtlOutputFormat format, std::string reques } static std::string reloadShaders(eHyprCtlOutputFormat format, std::string request) { - if (g_pHyprOpenGL->initShaders()) + CVarList vars(request, 0, ' '); + + if (vars.size() > 2) + return "too many args"; + + if (g_pHyprOpenGL && g_pHyprRenderer->reloadShaders(vars.size() == 2 ? vars[1] : "")) return format == FORMAT_JSON ? "{\"ok\": true}" : "ok"; else return format == FORMAT_JSON ? "{\"ok\": false}" : "error"; @@ -2000,19 +2077,17 @@ 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}); registerCommand(SHyprCtlCommand{"notify", false, dispatchNotify}); registerCommand(SHyprCtlCommand{"dismissnotify", false, dispatchDismissNotify}); - registerCommand(SHyprCtlCommand{"setprop", false, dispatchSetProp}); registerCommand(SHyprCtlCommand{"getprop", false, dispatchGetProp}); registerCommand(SHyprCtlCommand{"seterror", false, dispatchSeterror}); registerCommand(SHyprCtlCommand{"switchxkblayout", false, switchXKBLayoutRequest}); @@ -2114,10 +2189,6 @@ std::string CHyprCtl::getReply(std::string request) { g_pInputManager->setTouchDeviceConfigs(); // update touch device cfgs g_pInputManager->setTabletConfigs(); // update tablets - static auto PLAYOUT = CConfigValue("general:layout"); - - g_pLayoutManager->switchToLayout(*PLAYOUT); // update layout - g_pHyprOpenGL->m_reloadScreenShader = true; for (auto& [m, rd] : g_pHyprOpenGL->m_monitorRenderResources) { @@ -2128,13 +2199,20 @@ std::string CHyprCtl::getReply(std::string request) { if (!w->m_isMapped || !w->m_workspace || !w->m_workspace->isVisible()) continue; - w->updateDynamicRules(); - g_pCompositor->updateWindowAnimatedDecorationValues(w); + 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); } } @@ -2146,30 +2224,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; @@ -2177,7 +2277,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(); } @@ -2203,10 +2303,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); } // @@ -2241,7 +2341,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()); } @@ -2261,10 +2361,10 @@ 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); @@ -2281,7 +2381,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; } @@ -2292,14 +2392,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 95bb65b8..a6fa3721 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 @@ -25,9 +25,10 @@ class CHyprCtl { Hyprutils::OS::CFileDescriptor m_socketFD; struct { - bool all = false; - bool sysInfoConfig = false; - pid_t pid = 0; + bool all = false; + bool sysInfoConfig = false; + bool isDynamicKeyword = false; + pid_t pid = 0; SP> pendingPromise; } m_currentRequestParams; diff --git a/src/debug/HyprDebugOverlay.cpp b/src/debug/HyprDebugOverlay.cpp index ce967446..17ce12fa 100644 --- a/src/debug/HyprDebugOverlay.cpp +++ b/src/debug/HyprDebugOverlay.cpp @@ -5,9 +5,10 @@ #include "../render/pass/TexPassElement.hpp" #include "../render/Renderer.hpp" #include "../managers/animation/AnimationManager.hpp" +#include "../desktop/state/FocusState.hpp" CHyprDebugOverlay::CHyprDebugOverlay() { - m_texture = makeShared(); + m_texture = g_pHyprRenderer->createTexture(); } void CHyprMonitorDebugOverlay::renderData(PHLMONITOR pMonitor, float durationUs) { @@ -57,7 +58,7 @@ void CHyprMonitorDebugOverlay::frameData(PHLMONITOR pMonitor) { m_monitor = pMonitor; // anim data too - const auto PMONITORFORTICKS = g_pHyprRenderer->m_mostHzMonitor ? g_pHyprRenderer->m_mostHzMonitor.lock() : g_pCompositor->m_lastMonitor.lock(); + 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(); @@ -258,15 +259,7 @@ void CHyprDebugOverlay::draw() { cairo_surface_flush(m_cairoSurface); // copy the data to an OpenGL texture we have - const auto DATA = cairo_image_surface_get_data(m_cairoSurface); - m_texture->allocate(); - m_texture->bind(); - m_texture->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); - m_texture->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); - m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); - m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA); + m_texture = g_pHyprRenderer->createTexture(m_cairoSurface); CTexPassElement::SRenderData data; data.tex = m_texture; diff --git a/src/debug/HyprDebugOverlay.hpp b/src/debug/HyprDebugOverlay.hpp index 72987d94..bf188359 100644 --- a/src/debug/HyprDebugOverlay.hpp +++ b/src/debug/HyprDebugOverlay.hpp @@ -42,7 +42,7 @@ class CHyprDebugOverlay { cairo_surface_t* m_cairoSurface = nullptr; cairo_t* m_cairo = nullptr; - SP m_texture; + SP m_texture; friend class CHyprMonitorDebugOverlay; friend class CHyprRenderer; diff --git a/src/debug/HyprNotificationOverlay.cpp b/src/debug/HyprNotificationOverlay.cpp index 1c66a53b..e67b0434 100644 --- a/src/debug/HyprNotificationOverlay.cpp +++ b/src/debug/HyprNotificationOverlay.cpp @@ -4,9 +4,9 @@ #include "../Compositor.hpp" #include "../config/ConfigValue.hpp" #include "../render/pass/TexPassElement.hpp" +#include "../event/EventBus.hpp" #include "../managers/animation/AnimationManager.hpp" -#include "../managers/HookSystemManager.hpp" #include "../render/Renderer.hpp" static inline auto iconBackendFromLayout(PangoLayout* layout) { @@ -22,14 +22,12 @@ static inline auto iconBackendFromLayout(PangoLayout* layout) { } CHyprNotificationOverlay::CHyprNotificationOverlay() { - static auto P = g_pHookSystem->hookDynamic("focusedMon", [&](void* self, SCallbackInfo& info, std::any param) { + static auto P = Event::bus()->m_events.monitor.focused.listen([&](PHLMONITOR mon) { if (m_notifications.empty()) return; g_pHyprRenderer->damageBox(m_lastDamage); }); - - m_texture = makeShared(); } CHyprNotificationOverlay::~CHyprNotificationOverlay() { @@ -232,16 +230,7 @@ void CHyprNotificationOverlay::draw(PHLMONITOR pMonitor) { m_lastDamage = damage; - // copy the data to an OpenGL texture we have - const auto DATA = cairo_image_surface_get_data(m_cairoSurface); - m_texture->allocate(); - m_texture->bind(); - m_texture->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); - m_texture->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); - m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); - m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, MONSIZE.x, MONSIZE.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA); + m_texture = g_pHyprRenderer->createTexture(m_cairoSurface); CTexPassElement::SRenderData data; data.tex = m_texture; diff --git a/src/debug/HyprNotificationOverlay.hpp b/src/debug/HyprNotificationOverlay.hpp index 868eb05b..ec7aed72 100644 --- a/src/debug/HyprNotificationOverlay.hpp +++ b/src/debug/HyprNotificationOverlay.hpp @@ -18,7 +18,7 @@ enum eIconBackend : uint8_t { static const std::array, 3 /* backends */> ICONS_ARRAY = { std::array{"[!]", "[i]", "[Hint]", "[Err]", "[?]", "[ok]", ""}, std::array{"", "", "", "", "", "󰸞", ""}, std::array{"", "", "", "", "", ""}}; -static const std::array ICONS_COLORS = {CHyprColor{255.0 / 255.0, 204 / 255.0, 102 / 255.0, 1.0}, +static const std::array ICONS_COLORS = {CHyprColor{1.0, 204 / 255.0, 102 / 255.0, 1.0}, CHyprColor{128 / 255.0, 255 / 255.0, 255 / 255.0, 1.0}, CHyprColor{179 / 255.0, 255 / 255.0, 204 / 255.0, 1.0}, CHyprColor{255 / 255.0, 77 / 255.0, 77 / 255.0, 1.0}, @@ -57,7 +57,7 @@ class CHyprNotificationOverlay { PHLMONITORREF m_lastMonitor; Vector2D m_lastSize = Vector2D(-1, -1); - SP m_texture; + SP m_texture; }; inline UP g_pHyprNotificationOverlay; diff --git a/src/debug/Log.cpp b/src/debug/Log.cpp deleted file mode 100644 index e70617d3..00000000 --- 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 c3146805..00000000 --- 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/TracyDefines.hpp b/src/debug/TracyDefines.hpp index 49d296f6..d06332f3 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 75% rename from src/debug/CrashReporter.cpp rename to src/debug/crash/CrashReporter.cpp index 17c4ebf7..fad6ad21 100644 --- a/src/debug/CrashReporter.cpp +++ b/src/debug/crash/CrashReporter.cpp @@ -6,36 +6,43 @@ #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) { +[[noreturn]] static inline void exitWithError(char const* err) { 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. @@ -43,17 +50,17 @@ char const* getRandomMessage() { 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(2); - stderr += "Hyprland has crashed :( Consult the crash report at "; - if (!reportPath.boundsExceeded()) { - stderr += reportPath.getStr(); - } else { - stderr += "[ERROR: Crash report path does not fit into memory! Check if your $CACHE_HOME/$HOME is too deeply nested. Max 255 characters.]"; - } - stderr += " for more information.\n"; - stderr.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 0ba48e7c..661f702f 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 baee7b44..22717f1b 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 00000000..8ec967fe --- /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 00000000..d4e868de --- /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 07b4387d..c1cce9eb 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 5c70f21a..571679dc 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 f724c7b9..b52f17cd 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/LayerRule.cpp b/src/desktop/LayerRule.cpp deleted file mode 100644 index d6bfcadf..00000000 --- a/src/desktop/LayerRule.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include -#include "LayerRule.hpp" -#include -#include -#include "../debug/Log.hpp" - -static const auto RULES = std::unordered_set{"noanim", "blur", "blurpopups", "dimaround", "noscreenshare"}; -static const auto RULES_PREFIX = std::unordered_set{"ignorealpha", "ignorezero", "xray", "animation", "order", "abovelock"}; - -CLayerRule::CLayerRule(const std::string& rule_, const std::string& ns_) : m_targetNamespace(ns_), m_rule(rule_) { - const bool VALID = RULES.contains(m_rule) || std::ranges::any_of(RULES_PREFIX, [&rule_](const auto& prefix) { return rule_.starts_with(prefix); }); - - if (!VALID) - return; - - if (m_rule == "noanim") - m_ruleType = RULE_NOANIM; - else if (m_rule == "blur") - m_ruleType = RULE_BLUR; - else if (m_rule == "blurpopups") - m_ruleType = RULE_BLURPOPUPS; - else if (m_rule == "dimaround") - m_ruleType = RULE_DIMAROUND; - else if (m_rule == "noscreenshare") - m_ruleType = RULE_NOSCREENSHARE; - else if (m_rule.starts_with("ignorealpha")) - m_ruleType = RULE_IGNOREALPHA; - else if (m_rule.starts_with("ignorezero")) - m_ruleType = RULE_IGNOREZERO; - else if (m_rule.starts_with("xray")) - m_ruleType = RULE_XRAY; - else if (m_rule.starts_with("animation")) - m_ruleType = RULE_ANIMATION; - else if (m_rule.starts_with("order")) - m_ruleType = RULE_ORDER; - else if (m_rule.starts_with("abovelock")) - m_ruleType = RULE_ABOVELOCK; - else { - Debug::log(ERR, "CLayerRule: didn't match a rule that was found valid?!"); - m_ruleType = RULE_INVALID; - } -} diff --git a/src/desktop/LayerRule.hpp b/src/desktop/LayerRule.hpp deleted file mode 100644 index 7b6c8a6d..00000000 --- a/src/desktop/LayerRule.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include -#include -#include "Rule.hpp" - -class CLayerRule { - public: - CLayerRule(const std::string& rule, const std::string& targetNS); - - enum eRuleType : uint8_t { - RULE_INVALID = 0, - RULE_NOANIM, - RULE_BLUR, - RULE_BLURPOPUPS, - RULE_DIMAROUND, - RULE_ABOVELOCK, - RULE_IGNOREALPHA, - RULE_IGNOREZERO, - RULE_XRAY, - RULE_ANIMATION, - RULE_ORDER, - RULE_ZUMBA, - RULE_NOSCREENSHARE - }; - - eRuleType m_ruleType = RULE_INVALID; - - const std::string m_targetNamespace; - const std::string m_rule; - - CRuleRegexContainer m_targetNamespaceRegex; -}; diff --git a/src/desktop/LayerSurface.hpp b/src/desktop/LayerSurface.hpp deleted file mode 100644 index b70739cd..00000000 --- a/src/desktop/LayerSurface.hpp +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -#include -#include "../defines.hpp" -#include "WLSurface.hpp" -#include "../helpers/AnimatedVariable.hpp" - -class CLayerShellResource; - -class CLayerSurface { - public: - static PHLLS create(SP); - - private: - CLayerSurface(SP); - - public: - ~CLayerSurface(); - - void applyRules(); - 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; - bool m_noAnimations = false; - - bool m_forceBlur = false; - bool m_forceBlurPopups = false; - int64_t m_xray = -1; - bool m_ignoreAlpha = false; - float m_ignoreAlphaValue = 0.f; - bool m_dimAround = false; - bool m_noScreenShare = false; - int64_t m_order = 0; - bool m_aboveLockscreen = false; - bool m_aboveLockscreenInteractable = false; - - std::optional m_animationStyle; - - 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 964b36b6..00000000 --- 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/Rule.cpp b/src/desktop/Rule.cpp deleted file mode 100644 index 93f38de0..00000000 --- a/src/desktop/Rule.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include -#include "../helpers/memory/Memory.hpp" -#include "Rule.hpp" -#include "../debug/Log.hpp" - -CRuleRegexContainer::CRuleRegexContainer(const std::string& regex_) { - const bool NEGATIVE = regex_.starts_with("negative:"); - - m_negative = NEGATIVE; - m_regex = makeUnique(NEGATIVE ? regex_.substr(9) : regex_); - - // TODO: maybe pop an error? - if (!m_regex->ok()) - Debug::log(ERR, "RuleRegexContainer: regex {} failed to parse!", regex_); -} - -bool CRuleRegexContainer::passes(const std::string& str) const { - if (!m_regex) - return false; - - return RE2::FullMatch(str, *m_regex) != m_negative; -} \ No newline at end of file diff --git a/src/desktop/Rule.hpp b/src/desktop/Rule.hpp deleted file mode 100644 index 9d3de70e..00000000 --- a/src/desktop/Rule.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include - -//NOLINTNEXTLINE -namespace re2 { - class RE2; -}; - -class CRuleRegexContainer { - public: - CRuleRegexContainer() = default; - - CRuleRegexContainer(const std::string& regex); - - bool passes(const std::string& str) const; - - private: - Hyprutils::Memory::CUniquePointer m_regex; - bool m_negative = false; -}; \ No newline at end of file diff --git a/src/desktop/Subsurface.hpp b/src/desktop/Subsurface.hpp deleted file mode 100644 index 7c42dad9..00000000 --- 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 4d26d509..00000000 --- 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 fbf63243..00000000 --- a/src/desktop/Window.cpp +++ /dev/null @@ -1,1948 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include -#include "Window.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 "../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; - - 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; - - 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 (g_pCompositor->m_lastWindow == m_self) { - g_pCompositor->m_lastFocus.reset(); - g_pCompositor->m_lastWindow.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_windowData.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_windowData.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_windowData.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(); - - std::erase_if(g_pCompositor->m_windowFocusHistory, [this](const auto& other) { return other.expired() || other == m_self; }); - - 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); - - g_pCompositor->m_windowFocusHistory.push_back(m_self); - - 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 && g_pCompositor->m_lastWindow == m_self) - g_pCompositor->m_lastWindow.reset(); - - setSuspended(hidden); -} - -bool CWindow::isHidden() { - return m_hidden; -} - -void CWindow::applyDynamicRule(const SP& r) { - const eOverridePriority priority = r->m_execRule ? PRIORITY_SET_PROP : PRIORITY_WINDOW_RULE; - static auto PCLAMP_TILED = CConfigValue("misc:size_limits_tiled"); - - switch (r->m_ruleType) { - case CWindowRule::RULE_TAG: { - CVarList vars{r->m_rule, 0, 's', true}; - - if (vars.size() == 2 && vars[0] == "tag") - m_tags.applyTag(vars[1], true); - else - Debug::log(ERR, "Tag rule invalid: {}", r->m_rule); - break; - } - case CWindowRule::RULE_OPACITY: { - try { - CVarList vars(r->m_rule, 0, ' '); - - int opacityIDX = 0; - - for (auto const& r : vars) { - if (r == "opacity") - continue; - - if (r == "override") { - if (opacityIDX == 1) - m_windowData.alpha = CWindowOverridableVar(SAlphaValue{.alpha = m_windowData.alpha.value().alpha, .overridden = true}, priority); - else if (opacityIDX == 2) - m_windowData.alphaInactive = CWindowOverridableVar(SAlphaValue{.alpha = m_windowData.alphaInactive.value().alpha, .overridden = true}, priority); - else if (opacityIDX == 3) - m_windowData.alphaFullscreen = CWindowOverridableVar(SAlphaValue{.alpha = m_windowData.alphaFullscreen.value().alpha, .overridden = true}, priority); - } else { - if (opacityIDX == 0) { - m_windowData.alpha = CWindowOverridableVar(SAlphaValue{.alpha = std::stof(r), .overridden = false}, priority); - } else if (opacityIDX == 1) { - m_windowData.alphaInactive = CWindowOverridableVar(SAlphaValue{.alpha = std::stof(r), .overridden = false}, priority); - } else if (opacityIDX == 2) { - m_windowData.alphaFullscreen = CWindowOverridableVar(SAlphaValue{.alpha = std::stof(r), .overridden = false}, priority); - } else { - throw std::runtime_error("more than 3 alpha values"); - } - - opacityIDX++; - } - } - - if (opacityIDX == 1) { - m_windowData.alphaInactive = m_windowData.alpha; - m_windowData.alphaFullscreen = m_windowData.alpha; - } - } catch (std::exception& e) { Debug::log(ERR, "Opacity rule \"{}\" failed with: {}", r->m_rule, e.what()); } - break; - } - case CWindowRule::RULE_ANIMATION: { - auto STYLE = r->m_rule.substr(r->m_rule.find_first_of(' ') + 1); - m_windowData.animationStyle = CWindowOverridableVar(STYLE, priority); - break; - } - case CWindowRule::RULE_BORDERCOLOR: { - 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(r->m_rule.substr(r->m_rule.find_first_of(' ') + 1)), 0, 's', true); - - // Basic form has only two colors, everything else can be parsed as a gradient - if (colorsAndAngles.size() == 2 && !colorsAndAngles[1].contains("deg")) { - m_windowData.activeBorderColor = CWindowOverridableVar(CGradientValueData(CHyprColor(configStringToInt(colorsAndAngles[0]).value_or(0))), priority); - m_windowData.inactiveBorderColor = CWindowOverridableVar(CGradientValueData(CHyprColor(configStringToInt(colorsAndAngles[1]).value_or(0))), priority); - return; - } - - for (auto const& token : colorsAndAngles) { - // The first angle, or an explicit "0deg", splits the two gradients - if (active && token.contains("deg")) { - activeBorderGradient.m_angle = std::stoi(token.substr(0, token.size() - 3)) * (PI / 180.0); - active = false; - } else if (token.contains("deg")) - inactiveBorderGradient.m_angle = std::stoi(token.substr(0, token.size() - 3)) * (PI / 180.0); - else if (active) - activeBorderGradient.m_colors.emplace_back(configStringToInt(token).value_or(0)); - else - inactiveBorderGradient.m_colors.emplace_back(configStringToInt(token).value_or(0)); - } - - activeBorderGradient.updateColorsOk(); - - // 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", r->m_rule); - else if (activeBorderGradient.m_colors.empty()) - Debug::log(WARN, "Bordercolor rule \"{}\" has no colors, ignoring", r->m_rule); - else if (inactiveBorderGradient.m_colors.empty()) - m_windowData.activeBorderColor = CWindowOverridableVar(activeBorderGradient, priority); - else { - m_windowData.activeBorderColor = CWindowOverridableVar(activeBorderGradient, priority); - m_windowData.inactiveBorderColor = CWindowOverridableVar(inactiveBorderGradient, priority); - } - } catch (std::exception& e) { Debug::log(ERR, "BorderColor rule \"{}\" failed with: {}", r->m_rule, e.what()); } - break; - } - case CWindowRule::RULE_IDLEINHIBIT: { - auto IDLERULE = r->m_rule.substr(r->m_rule.find_first_of(' ') + 1); - - if (IDLERULE == "none") - m_idleInhibitMode = IDLEINHIBIT_NONE; - else if (IDLERULE == "always") - m_idleInhibitMode = IDLEINHIBIT_ALWAYS; - else if (IDLERULE == "focus") - m_idleInhibitMode = IDLEINHIBIT_FOCUS; - else if (IDLERULE == "fullscreen") - m_idleInhibitMode = IDLEINHIBIT_FULLSCREEN; - else - Debug::log(ERR, "Rule idleinhibit: unknown mode {}", IDLERULE); - break; - } - case CWindowRule::RULE_MAXSIZE: { - try { - if (!m_isFloating && !sc(*PCLAMP_TILED)) - return; - const auto VEC = configStringToVector2D(r->m_rule.substr(8)); - if (VEC.x < 1 || VEC.y < 1) { - Debug::log(ERR, "Invalid size for maxsize"); - return; - } - - m_windowData.maxSize = CWindowOverridableVar(VEC, priority); - clampWindowSize(std::nullopt, m_windowData.maxSize.value()); - - } catch (std::exception& e) { Debug::log(ERR, "maxsize rule \"{}\" failed with: {}", r->m_rule, e.what()); } - break; - } - case CWindowRule::RULE_MINSIZE: { - try { - if (!m_isFloating && !sc(*PCLAMP_TILED)) - return; - const auto VEC = configStringToVector2D(r->m_rule.substr(8)); - if (VEC.x < 1 || VEC.y < 1) { - Debug::log(ERR, "Invalid size for minsize"); - return; - } - - m_windowData.minSize = CWindowOverridableVar(VEC, priority); - clampWindowSize(m_windowData.minSize.value(), std::nullopt); - - if (m_groupData.pNextWindow.expired()) - setHidden(false); - } catch (std::exception& e) { Debug::log(ERR, "minsize rule \"{}\" failed with: {}", r->m_rule, e.what()); } - break; - } - case CWindowRule::RULE_RENDERUNFOCUSED: { - m_windowData.renderUnfocused = CWindowOverridableVar(true, priority); - g_pHyprRenderer->addWindowToRenderUnfocused(m_self.lock()); - break; - } - case CWindowRule::RULE_PROP: { - const CVarList VARS(r->m_rule, 0, ' '); - if (auto search = NWindowProperties::intWindowProperties.find(VARS[1]); search != NWindowProperties::intWindowProperties.end()) { - try { - *(search->second(m_self.lock())) = CWindowOverridableVar(sc(std::stoi(VARS[2])), priority); - } catch (std::exception& e) { Debug::log(ERR, "Rule \"{}\" failed with: {}", r->m_rule, e.what()); } - } else if (auto search = NWindowProperties::floatWindowProperties.find(VARS[1]); search != NWindowProperties::floatWindowProperties.end()) { - try { - *(search->second(m_self.lock())) = CWindowOverridableVar(std::stof(VARS[2]), priority); - } catch (std::exception& e) { Debug::log(ERR, "Rule \"{}\" failed with: {}", r->m_rule, e.what()); } - } else if (auto search = NWindowProperties::boolWindowProperties.find(VARS[1]); search != NWindowProperties::boolWindowProperties.end()) { - try { - *(search->second(m_self.lock())) = CWindowOverridableVar(VARS[2].empty() ? true : sc(std::stoi(VARS[2])), priority); - } catch (std::exception& e) { Debug::log(ERR, "Rule \"{}\" failed with: {}", r->m_rule, e.what()); } - } - break; - } - case CWindowRule::RULE_PERSISTENTSIZE: { - m_windowData.persistentSize = CWindowOverridableVar(true, PRIORITY_WINDOW_RULE); - break; - } - case CWindowRule::RULE_NOVRR: { - m_windowData.noVRR = CWindowOverridableVar(true, priority); - break; - } - default: break; - } -} - -void CWindow::updateDynamicRules() { - m_windowData.alpha.unset(PRIORITY_WINDOW_RULE); - m_windowData.alphaInactive.unset(PRIORITY_WINDOW_RULE); - m_windowData.alphaFullscreen.unset(PRIORITY_WINDOW_RULE); - - unsetWindowData(PRIORITY_WINDOW_RULE); - - m_windowData.animationStyle.unset(PRIORITY_WINDOW_RULE); - m_windowData.maxSize.unset(PRIORITY_WINDOW_RULE); - m_windowData.minSize.unset(PRIORITY_WINDOW_RULE); - - m_windowData.activeBorderColor.unset(PRIORITY_WINDOW_RULE); - m_windowData.inactiveBorderColor.unset(PRIORITY_WINDOW_RULE); - - m_windowData.renderUnfocused.unset(PRIORITY_WINDOW_RULE); - m_windowData.noVRR.unset(PRIORITY_WINDOW_RULE); - - m_idleInhibitMode = IDLEINHIBIT_NONE; - - m_tags.removeDynamicTags(); - - m_matchedRules = g_pConfigManager->getMatchingRules(m_self.lock()); - for (const auto& r : m_matchedRules) { - applyDynamicRule(r); - } - - EMIT_HOOK_EVENT("windowUpdateRules", m_self.lock()); - - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID()); -} - -// 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))}); - } -} - -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))}); - 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->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(); - 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 == g_pCompositor->m_lastWindow.lock(); - - 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) - g_pCompositor->focusWindow(pWindow); - - g_pHyprRenderer->damageWindow(pWindow); - - 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; -} - -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); -} - -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_windowData.roundingPower.valueOr(*PROUNDINGPOWER); - float rounding = m_windowData.rounding.valueOr(*PROUNDING) * (roundingPower / 2.0); /* Make perceived roundness consistent. */ - - return m_windowData.noRounding.valueOrDefault() ? 0 : rounding; -} - -float CWindow::roundingPower() { - static auto PROUNDINGPOWER = CConfigValue("decoration:rounding_power"); - - return m_windowData.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) { - static auto PNOBORDERONFLOATING = CConfigValue("general:no_border_on_floating"); - - if (*PNOBORDERONFLOATING) - m_windowData.noBorder = CWindowOverridableVar(m_isFloating, PRIORITY_LAYOUT); - else - m_windowData.noBorder.unset(PRIORITY_LAYOUT); - - m_windowData.borderSize.matchOptional(workspaceRule.borderSize, PRIORITY_WORKSPACE_RULE); - m_windowData.decorate.matchOptional(workspaceRule.decorate, PRIORITY_WORKSPACE_RULE); - m_windowData.noBorder.matchOptional(workspaceRule.noBorder, PRIORITY_WORKSPACE_RULE); - m_windowData.noRounding.matchOptional(workspaceRule.noRounding, PRIORITY_WORKSPACE_RULE); - m_windowData.noShadow.matchOptional(workspaceRule.noShadow, PRIORITY_WORKSPACE_RULE); -} - -int CWindow::getRealBorderSize() { - if (m_windowData.noBorder.valueOrDefault() || (m_workspace && isEffectiveInternalFSMode(FSMODE_FULLSCREEN)) || !m_windowData.decorate.valueOrDefault()) - return 0; - - static auto PBORDERSIZE = CConfigValue("general:border_size"); - - return m_windowData.borderSize.valueOr(*PBORDERSIZE); -} - -float CWindow::getScrollMouse() { - static auto PINPUTSCROLLFACTOR = CConfigValue("input:scroll_factor"); - return m_windowData.scrollMouse.valueOr(*PINPUTSCROLLFACTOR); -} - -float CWindow::getScrollTouchpad() { - static auto PTOUCHPADSCROLLFACTOR = CConfigValue("input:touchpad:scroll_factor"); - return m_windowData.scrollTouchpad.valueOr(*PTOUCHPADSCROLLFACTOR); -} - -bool CWindow::isScrollMouseOverridden() { - return m_windowData.scrollMouse.hasValue(); -} - -bool CWindow::isScrollTouchpadOverridden() { - return m_windowData.scrollTouchpad.hasValue(); -} - -bool CWindow::canBeTorn() { - static auto PTEARING = CConfigValue("general:allow_tearing"); - return m_windowData.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::string environFile = "/proc/" + std::to_string(PID) + "/environ"; - std::ifstream ifs(environFile, std::ios::binary); - - if (!ifs.good()) - return {}; - - std::vector buffer; - size_t needle = 0; - buffer.resize(512, '\0'); - while (ifs.read(buffer.data() + needle, 512)) { - buffer.resize(buffer.size() + 512, '\0'); - needle += 512; - } - - 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 (g_pCompositor->m_lastWindow == 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_windowData.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); - - g_pCompositor->focusWindow(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); - g_pCompositor->setActiveMonitor(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 == g_pCompositor->m_lastWindow) { // 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 == g_pCompositor->m_lastWindow) { // 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) { - updateDynamicRules(); - g_pCompositor->updateWindowAnimatedDecorationValues(m_self.lock()); - 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 : g_pCompositor->m_windowFocusHistory) { - 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]; -} - -void CWindow::unsetWindowData(eOverridePriority priority) { - for (auto const& element : NWindowProperties::boolWindowProperties) { - element.second(m_self.lock())->unset(priority); - } - for (auto const& element : NWindowProperties::intWindowProperties) { - element.second(m_self.lock())->unset(priority); - } - for (auto const& element : NWindowProperties::floatWindowProperties) { - element.second(m_self.lock())->unset(priority); - } -} - -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_windowData.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->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; -} diff --git a/src/desktop/Window.hpp b/src/desktop/Window.hpp deleted file mode 100644 index 0a7e207c..00000000 --- a/src/desktop/Window.hpp +++ /dev/null @@ -1,555 +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 "WindowRule.hpp" -#include "WindowOverridableVar.hpp" -#include "../protocols/types/ContentType.hpp" - -class CXDGSurfaceResource; -class CXWaylandSurface; - -enum eIdleInhibitMode : uint8_t { - IDLEINHIBIT_NONE = 0, - IDLEINHIBIT_ALWAYS, - IDLEINHIBIT_FULLSCREEN, - IDLEINHIBIT_FOCUS -}; - -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 SAlphaValue { - float alpha; - bool overridden; - - float applyAlpha(float a) const { - if (overridden) - return alpha; - else - return alpha * a; - }; -}; - -struct SWindowData { - CWindowOverridableVar alpha = SAlphaValue{.alpha = 1.f, .overridden = false}; - CWindowOverridableVar alphaInactive = SAlphaValue{.alpha = 1.f, .overridden = false}; - CWindowOverridableVar alphaFullscreen = SAlphaValue{.alpha = 1.f, .overridden = false}; - - CWindowOverridableVar allowsInput = false; - CWindowOverridableVar dimAround = false; - CWindowOverridableVar decorate = true; - CWindowOverridableVar focusOnActivate = false; - CWindowOverridableVar keepAspectRatio = false; - CWindowOverridableVar nearestNeighbor = false; - CWindowOverridableVar noAnim = false; - CWindowOverridableVar noBorder = false; - CWindowOverridableVar noBlur = false; - CWindowOverridableVar noDim = false; - CWindowOverridableVar noFocus = false; - CWindowOverridableVar noMaxSize = false; - CWindowOverridableVar noRounding = false; - CWindowOverridableVar noShadow = false; - CWindowOverridableVar noShortcutsInhibit = false; - CWindowOverridableVar opaque = false; - CWindowOverridableVar RGBX = false; - CWindowOverridableVar syncFullscreen = true; - CWindowOverridableVar tearing = false; - CWindowOverridableVar xray = false; - CWindowOverridableVar renderUnfocused = false; - CWindowOverridableVar noFollowMouse = false; - CWindowOverridableVar noScreenShare = false; - CWindowOverridableVar noVRR = false; - - CWindowOverridableVar borderSize = {std::string("general:border_size"), sc(0), std::nullopt}; - CWindowOverridableVar rounding = {std::string("decoration:rounding"), sc(0), std::nullopt}; - - CWindowOverridableVar roundingPower = {std::string("decoration:rounding_power")}; - CWindowOverridableVar scrollMouse = {std::string("input:scroll_factor")}; - CWindowOverridableVar scrollTouchpad = {std::string("input:touchpad:scroll_factor")}; - - CWindowOverridableVar animationStyle; - CWindowOverridableVar maxSize; - CWindowOverridableVar minSize; - - CWindowOverridableVar activeBorderColor; - CWindowOverridableVar inactiveBorderColor; - - CWindowOverridableVar persistentSize; -}; - -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 - SWindowData m_windowData; - - // 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; - - // focus stuff - bool m_stayFocused = false; - - // for toplevel monitor events - MONITORID m_lastSurfaceMonitorID = -1; - - // for idle inhibiting windows - eIdleInhibitMode m_idleInhibitMode = IDLEINHIBIT_NONE; - - // 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; - - // stores the currently matched window rules - std::vector> m_matchedRules; - - // window tags - CTagKeeper m_tags; - - // 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 applyDynamicRule(const SP& r); - void updateDynamicRules(); - 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(); - void unsetWindowData(eOverridePriority priority); - 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(); - - 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: - // 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; -} - -namespace NWindowProperties { - static const std::unordered_map*(const PHLWINDOW&)>> boolWindowProperties = { - {"allowsinput", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.allowsInput; }}, - {"dimaround", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.dimAround; }}, - {"decorate", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.decorate; }}, - {"focusonactivate", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.focusOnActivate; }}, - {"keepaspectratio", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.keepAspectRatio; }}, - {"nearestneighbor", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.nearestNeighbor; }}, - {"noanim", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noAnim; }}, - {"noblur", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noBlur; }}, - {"noborder", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noBorder; }}, - {"nodim", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noDim; }}, - {"nofocus", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noFocus; }}, - {"nomaxsize", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noMaxSize; }}, - {"norounding", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noRounding; }}, - {"noshadow", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noShadow; }}, - {"noshortcutsinhibit", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noShortcutsInhibit; }}, - {"opaque", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.opaque; }}, - {"forcergbx", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.RGBX; }}, - {"syncfullscreen", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.syncFullscreen; }}, - {"novrr", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noVRR; }}, - {"immediate", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.tearing; }}, - {"xray", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.xray; }}, - {"nofollowmouse", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noFollowMouse; }}, - {"noscreenshare", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.noScreenShare; }}, - }; - - const std::unordered_map*(const PHLWINDOW&)>> intWindowProperties = { - {"rounding", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.rounding; }}, - {"bordersize", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.borderSize; }}, - }; - - const std::unordered_map*(PHLWINDOW)>> floatWindowProperties = { - {"roundingpower", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.roundingPower; }}, - {"scrollmouse", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.scrollMouse; }}, - {"scrolltouchpad", [](const PHLWINDOW& pWindow) { return &pWindow->m_windowData.scrollTouchpad; }}, - }; -}; - -/** - 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/WindowOverridableVar.hpp b/src/desktop/WindowOverridableVar.hpp deleted file mode 100644 index ea113d3e..00000000 --- a/src/desktop/WindowOverridableVar.hpp +++ /dev/null @@ -1,132 +0,0 @@ -#pragma once - -#include -#include -#include -#include "../config/ConfigValue.hpp" - -enum eOverridePriority : uint8_t { - PRIORITY_LAYOUT = 0, - PRIORITY_WORKSPACE_RULE, - PRIORITY_WINDOW_RULE, - PRIORITY_SET_PROP, -}; - -template -T clampOptional(T const& value, std::optional const& min, std::optional const& max) { - return std::clamp(value, min.value_or(std::numeric_limits::min()), max.value_or(std::numeric_limits::max())); -} - -template || std::is_same_v || std::is_same_v> -class CWindowOverridableVar { - public: - CWindowOverridableVar(T const& value, eOverridePriority priority) { - m_values[priority] = value; - } - - CWindowOverridableVar(T const& value) : m_defaultValue{value} {} - CWindowOverridableVar(T const& value, std::optional const& min, std::optional const& max = std::nullopt) : m_defaultValue{value}, m_minValue{min}, m_maxValue{max} {} - CWindowOverridableVar(std::string const& value) - requires(Extended && !std::is_same_v) - : m_configValue(SP>(new CConfigValue(value))) {} - CWindowOverridableVar(std::string const& value, std::optional const& min, std::optional const& max = std::nullopt) - requires(Extended && !std::is_same_v) - : m_minValue(min), m_maxValue(max), m_configValue(SP>(new CConfigValue(value))) {} - - CWindowOverridableVar() = default; - ~CWindowOverridableVar() = default; - - CWindowOverridableVar& operator=(CWindowOverridableVar const& other) { - // Self-assignment check - if (this == &other) - return *this; - - for (auto const& value : other.m_values) { - if constexpr (Extended && !std::is_same_v) - m_values[value.first] = clampOptional(value.second, m_minValue, m_maxValue); - else - m_values[value.first] = value.second; - } - - return *this; - } - - void unset(eOverridePriority priority) { - m_values.erase(priority); - } - - bool hasValue() { - return !m_values.empty(); - } - - T value() { - if (!m_values.empty()) - return std::prev(m_values.end())->second; - else - throw std::bad_optional_access(); - } - - T valueOr(T const& other) { - if (hasValue()) - return value(); - else - return other; - } - - T valueOrDefault() - requires(Extended && !std::is_same_v) - { - if (hasValue()) - return value(); - else if (m_defaultValue.has_value()) - return m_defaultValue.value(); - else - return **std::any_cast>>(m_configValue); - } - - T valueOrDefault() - requires(!Extended || std::is_same_v) - { - if (hasValue()) - return value(); - else if (!m_defaultValue.has_value()) - throw std::bad_optional_access(); - else - return m_defaultValue.value(); - } - - eOverridePriority getPriority() { - if (!m_values.empty()) - return std::prev(m_values.end())->first; - else - throw std::bad_optional_access(); - } - - void increment(T const& other, eOverridePriority priority) { - if constexpr (std::is_same_v) - m_values[priority] = valueOr(false) ^ other; - else - m_values[priority] = clampOptional(valueOrDefault() + other, m_minValue, m_maxValue); - } - - void matchOptional(std::optional const& optValue, eOverridePriority priority) { - if (optValue.has_value()) - m_values[priority] = optValue.value(); - else - unset(priority); - } - - operator std::optional() { - if (hasValue()) - return value(); - else - return std::nullopt; - } - - private: - std::map m_values; - std::optional m_defaultValue; // used for toggling, so required for bool - std::optional m_minValue; - std::optional m_maxValue; - std::any m_configValue; // only there for select variables -}; diff --git a/src/desktop/WindowRule.cpp b/src/desktop/WindowRule.cpp deleted file mode 100644 index dc6564ca..00000000 --- a/src/desktop/WindowRule.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "WindowRule.hpp" -#include -#include -#include -#include "../config/ConfigManager.hpp" - -static const auto RULES = std::unordered_set{ - "float", "fullscreen", "maximize", "noinitialfocus", "pin", "stayfocused", "tile", "renderunfocused", "persistentsize", -}; -static const auto RULES_PREFIX = std::unordered_set{ - "animation", "bordercolor", "bordersize", "center", "content", "fullscreenstate", "group", "idleinhibit", "maxsize", "minsize", "monitor", - "move", "noclosefor", "opacity", "plugin:", "prop", "pseudo", "rounding", "roundingpower", "scrollmouse", "scrolltouchpad", "size", - "suppressevent", "tag", "workspace", "xray", "novrr", -}; - -CWindowRule::CWindowRule(const std::string& rule, const std::string& value, bool isV2, bool isExecRule) : m_value(value), m_rule(rule), m_v2(isV2), m_execRule(isExecRule) { - const auto VALS = CVarList(rule, 2, ' '); - const bool VALID = RULES.contains(rule) || std::ranges::any_of(RULES_PREFIX, [&rule](auto prefix) { return rule.starts_with(prefix); }) || - (NWindowProperties::boolWindowProperties.contains(VALS[0])) || (NWindowProperties::intWindowProperties.contains(VALS[0])) || - (NWindowProperties::floatWindowProperties.contains(VALS[0])); - - if (!VALID) - return; - - if (rule == "float") - m_ruleType = RULE_FLOAT; - else if (rule == "fullscreen") - m_ruleType = RULE_FULLSCREEN; - else if (rule == "maximize") - m_ruleType = RULE_MAXIMIZE; - else if (rule == "noinitialfocus") - m_ruleType = RULE_NOINITIALFOCUS; - else if (rule == "pin") - m_ruleType = RULE_PIN; - else if (rule == "stayfocused") - m_ruleType = RULE_STAYFOCUSED; - else if (rule == "tile") - m_ruleType = RULE_TILE; - else if (rule == "renderunfocused") - m_ruleType = RULE_RENDERUNFOCUSED; - else if (rule == "persistentsize") - m_ruleType = RULE_PERSISTENTSIZE; - else if (rule.starts_with("animation")) - m_ruleType = RULE_ANIMATION; - else if (rule.starts_with("bordercolor")) - m_ruleType = RULE_BORDERCOLOR; - else if (rule.starts_with("center")) - m_ruleType = RULE_CENTER; - else if (rule.starts_with("fullscreenstate")) - m_ruleType = RULE_FULLSCREENSTATE; - else if (rule.starts_with("group")) - m_ruleType = RULE_GROUP; - else if (rule.starts_with("idleinhibit")) - m_ruleType = RULE_IDLEINHIBIT; - else if (rule.starts_with("maxsize")) - m_ruleType = RULE_MAXSIZE; - else if (rule.starts_with("minsize")) - m_ruleType = RULE_MINSIZE; - else if (rule.starts_with("monitor")) - m_ruleType = RULE_MONITOR; - else if (rule.starts_with("move")) - m_ruleType = RULE_MOVE; - else if (rule.starts_with("opacity")) - m_ruleType = RULE_OPACITY; - else if (rule.starts_with("plugin:")) - m_ruleType = RULE_PLUGIN; - else if (rule.starts_with("pseudo")) - m_ruleType = RULE_PSEUDO; - else if (rule.starts_with("size")) - m_ruleType = RULE_SIZE; - else if (rule.starts_with("suppressevent")) - m_ruleType = RULE_SUPPRESSEVENT; - else if (rule.starts_with("novrr")) - m_ruleType = RULE_NOVRR; - else if (rule.starts_with("tag")) - m_ruleType = RULE_TAG; - else if (rule.starts_with("workspace")) - m_ruleType = RULE_WORKSPACE; - else if (rule.starts_with("prop")) - m_ruleType = RULE_PROP; - else if (rule.starts_with("content")) - m_ruleType = RULE_CONTENT; - else if (rule.starts_with("noclosefor")) - m_ruleType = RULE_NOCLOSEFOR; - else { - // check if this is a prop. - const CVarList VARS(rule, 0, 's', true); - const bool ISPROP = NWindowProperties::intWindowProperties.contains(VARS[0]) || NWindowProperties::boolWindowProperties.contains(VARS[0]) || - NWindowProperties::floatWindowProperties.contains(VARS[0]); - if (ISPROP) { - *const_cast(&m_rule) = "prop " + rule; - m_ruleType = RULE_PROP; - Debug::log(LOG, "CWindowRule: direct prop rule found, rewritten {} -> {}", rule, m_rule); - } else { - Debug::log(ERR, "CWindowRule: didn't match a rule that was found valid?!"); - m_ruleType = RULE_INVALID; - } - } -} diff --git a/src/desktop/WindowRule.hpp b/src/desktop/WindowRule.hpp deleted file mode 100644 index 5bf462e9..00000000 --- a/src/desktop/WindowRule.hpp +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once - -#include -#include -#include "Rule.hpp" - -class CWindowRule { - public: - CWindowRule(const std::string& rule, const std::string& value, bool isV2 = false, bool isExecRule = false); - - enum eRuleType : uint8_t { - RULE_INVALID = 0, - RULE_FLOAT, - RULE_FULLSCREEN, - RULE_MAXIMIZE, - RULE_NOINITIALFOCUS, - RULE_PIN, - RULE_STAYFOCUSED, - RULE_TILE, - RULE_RENDERUNFOCUSED, - RULE_ANIMATION, - RULE_BORDERCOLOR, - RULE_CENTER, - RULE_FULLSCREENSTATE, - RULE_GROUP, - RULE_IDLEINHIBIT, - RULE_MAXSIZE, - RULE_MINSIZE, - RULE_MONITOR, - RULE_MOVE, - RULE_OPACITY, - RULE_PLUGIN, - RULE_PSEUDO, - RULE_SIZE, - RULE_SUPPRESSEVENT, - RULE_TAG, - RULE_WORKSPACE, - RULE_PROP, - RULE_CONTENT, - RULE_PERSISTENTSIZE, - RULE_NOCLOSEFOR, - RULE_NOVRR, - }; - - eRuleType m_ruleType = RULE_INVALID; - - const std::string m_value; - const std::string m_rule; - const bool m_v2 = false; - const bool m_execRule = false; - - std::string m_title; - std::string m_class; - std::string m_initialTitle; - std::string m_initialClass; - std::string m_tag; - int m_X11 = -1; // -1 means "ANY" - int m_floating = -1; - int m_fullscreen = -1; - int m_pinned = -1; - int m_focus = -1; - int m_group = -1; - int m_modal = -1; - std::string m_fullscreenState = ""; // empty means any - std::string m_onWorkspace = ""; // empty means any - std::string m_workspace = ""; // empty means any - std::string m_contentType = ""; // empty means any - std::string m_xdgTag = ""; // empty means any - - // precompiled regexes - CRuleRegexContainer m_titleRegex; - CRuleRegexContainer m_classRegex; - CRuleRegexContainer m_initialTitleRegex; - CRuleRegexContainer m_initialClassRegex; - CRuleRegexContainer m_v1Regex; -}; diff --git a/src/desktop/Workspace.cpp b/src/desktop/Workspace.cpp index ee35313a..5df3f087 100644 --- a/src/desktop/Workspace.cpp +++ b/src/desktop/Workspace.cpp @@ -1,10 +1,13 @@ #include "Workspace.hpp" +#include "view/Group.hpp" #include "../Compositor.hpp" #include "../config/ConfigValue.hpp" #include "config/ConfigManager.hpp" #include "managers/animation/AnimationManager.hpp" #include "../managers/EventManager.hpp" -#include "../managers/HookSystemManager.hpp" +#include "../layout/space/Space.hpp" +#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp" +#include "../event/EventBus.hpp" #include #include @@ -34,13 +37,14 @@ void CWorkspace::init(PHLWORKSPACE self) { if (RULEFORTHIS.defaultName.has_value()) m_name = RULEFORTHIS.defaultName.value(); - m_focusedWindowHook = g_pHookSystem->hookDynamic("closeWindow", [this](void* self, SCallbackInfo& info, std::any param) { - const auto PWINDOW = std::any_cast(param); - - if (PWINDOW == m_lastFocusedWindow.lock()) + m_focusedWindowHook = Event::bus()->m_events.window.close.listen([this](PHLWINDOW pWindow) { + if (pWindow == m_lastFocusedWindow.lock()) m_lastFocusedWindow.reset(); }); + m_space = Layout::CSpace::create(m_self.lock()); + m_space->setAlgorithmProvider(Layout::Supplementary::algoMatcher()->createAlgorithmForWorkspace(m_self.lock())); + m_inert = false; const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(self); @@ -52,26 +56,19 @@ void CWorkspace::init(PHLWORKSPACE self) { g_pEventManager->postEvent({.event = "createworkspace", .data = m_name}); g_pEventManager->postEvent({.event = "createworkspacev2", .data = std::format("{},{}", m_id, m_name)}); - EMIT_HOOK_EVENT("createWorkspace", this); -} - -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 +79,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 +135,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 +150,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 +158,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 +174,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 +189,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 +204,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 +225,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 +263,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 +293,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 +301,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 +327,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 +336,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 +358,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; } @@ -428,14 +407,19 @@ bool CWorkspace::isVisibleNotCovered() { int CWorkspace::getWindows(std::optional onlyTiled, std::optional onlyPinned, std::optional onlyVisible) { int no = 0; - for (auto const& w : g_pCompositor->m_windows) { - if (w->workspaceID() != m_id || !w->m_isMapped) + + if (!m_space) + return 0; + + for (auto const& t : m_space->targets()) { + if (!t) continue; - if (onlyTiled.has_value() && w->m_isFloating == onlyTiled.value()) + + if (onlyTiled.has_value() && t->floating() == onlyTiled.value()) continue; - if (onlyPinned.has_value() && w->m_pinned != onlyPinned.value()) + if (onlyPinned.has_value() && (!t->window() || t->window()->m_pinned != onlyPinned.value())) continue; - if (onlyVisible.has_value() && w->isHidden() == onlyVisible.value()) + if (onlyVisible.has_value() && (!t->window() || t->window()->isHidden() == onlyVisible.value())) continue; no++; } @@ -445,16 +429,16 @@ int CWorkspace::getWindows(std::optional onlyTiled, std::optional on int CWorkspace::getGroups(std::optional onlyTiled, std::optional onlyPinned, std::optional onlyVisible) { int no = 0; - for (auto const& w : g_pCompositor->m_windows) { - if (w->workspaceID() != m_id || !w->m_isMapped) + for (auto const& g : Desktop::View::groups()) { + const auto HEAD = g->head(); + + if (HEAD->workspaceID() != m_id || !HEAD->m_isMapped) continue; - if (!w->m_groupData.head) + if (onlyTiled.has_value() && HEAD->m_isFloating == onlyTiled.value()) continue; - if (onlyTiled.has_value() && w->m_isFloating == onlyTiled.value()) + if (onlyPinned.has_value() && HEAD->m_pinned != onlyPinned.value()) continue; - if (onlyPinned.has_value() && w->m_pinned != onlyPinned.value()) - continue; - if (onlyVisible.has_value() && w->isHidden() == onlyVisible.value()) + if (onlyVisible.has_value() && g->current()->isHidden() == onlyVisible.value()) continue; no++; } @@ -522,7 +506,7 @@ 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()); @@ -536,13 +520,11 @@ void CWorkspace::rename(const std::string& name) { } void CWorkspace::updateWindows() { - m_hasFullscreenWindow = std::ranges::any_of(g_pCompositor->m_windows, [this](const auto& w) { return w->m_isMapped && w->m_workspace == m_self && w->isFullscreen(); }); + m_hasFullscreenWindow = std::ranges::any_of(m_space->targets(), [](const auto& t) { return t->fullscreenMode() != FSMODE_NONE; }); - for (auto const& w : g_pCompositor->m_windows) { - if (!w->m_isMapped || w->m_workspace != m_self) - continue; - - w->updateDynamicRules(); + 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 72bc3a67..87d1c2d8 100644 --- a/src/desktop/Workspace.hpp +++ b/src/desktop/Workspace.hpp @@ -6,6 +6,10 @@ #include "../helpers/MiscFunctions.hpp" #include "../helpers/signal/Signal.hpp" +namespace Layout { + class CSpace; +}; + enum eFullscreenMode : int8_t { FSMODE_NONE = 0, FSMODE_MAXIMIZED = 1 << 0, @@ -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 @@ -59,29 +63,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 +93,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 00000000..1dd32164 --- /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 00000000..92645683 --- /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 00000000..daa115f8 --- /dev/null +++ b/src/desktop/history/WorkspaceHistoryTracker.cpp @@ -0,0 +1,151 @@ +#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 + +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); + }); + }); +} + +CWorkspaceHistoryTracker::SWorkspacePreviousData& CWorkspaceHistoryTracker::dataFor(PHLWORKSPACE ws) { + for (auto& ref : m_datas) { + if (ref.workspace != ws) + continue; + + return ref; + } + + return m_datas.emplace_back(SWorkspacePreviousData{ + .workspace = ws, + }); +} + +void CWorkspaceHistoryTracker::track(PHLWORKSPACE w) { + if (!w || !w->m_monitor || w == m_lastWorkspaceData.workspace) + return; + + static auto PALLOWWORKSPACECYCLES = CConfigValue("binds:allow_workspace_cycles"); + + auto& data = dataFor(w); + + Hyprutils::Utils::CScopeGuard x([&] { setLastWorkspaceData(w); }); + + if (m_lastWorkspaceData.workspace == w && !*PALLOWWORKSPACECYCLES) + return; + + data.previous = m_lastWorkspaceData.workspace; + if (m_lastWorkspaceData.workspace) { + data.previousName = m_lastWorkspaceData.workspace->m_name; + data.previousID = m_lastWorkspaceData.workspace->m_id; + data.previousMon = m_lastWorkspaceData.workspace->m_monitor; + } else { + data.previousName = m_lastWorkspaceData.workspaceName; + data.previousID = m_lastWorkspaceData.workspaceID; + data.previousMon = m_lastWorkspaceData.monitor; + } +} + +void CWorkspaceHistoryTracker::gc() { + std::erase_if(m_datas, [](const auto& e) { return !e.workspace; }); +} + +const CWorkspaceHistoryTracker::SWorkspacePreviousData* CWorkspaceHistoryTracker::previousWorkspace(PHLWORKSPACE ws) { + gc(); + + for (const auto& d : m_datas) { + if (d.workspace != ws) + continue; + return &d; + } + + return &dataFor(ws); +} + +SWorkspaceIDName CWorkspaceHistoryTracker::previousWorkspaceIDName(PHLWORKSPACE ws) { + gc(); + + for (const auto& d : m_datas) { + if (d.workspace != ws) + continue; + return SWorkspaceIDName{.id = d.previousID, .name = d.previousName, .isAutoIDd = d.previousID <= 0}; + } + + auto& d = dataFor(ws); + return SWorkspaceIDName{.id = d.previousID, .name = d.previousName, .isAutoIDd = d.previousID <= 0}; +} + +const CWorkspaceHistoryTracker::SWorkspacePreviousData* CWorkspaceHistoryTracker::previousWorkspace(PHLWORKSPACE ws, PHLMONITOR restrict) { + if (!restrict) + return previousWorkspace(ws); + + auto& data = dataFor(ws); + while (true) { + + // case 1: previous exists + if (data.previous) { + if (data.previous->m_monitor != restrict) { + data = dataFor(data.previous.lock()); + continue; + } + + break; + } + + // case 2: previous doesnt exist, but we have mon + if (data.previousMon) { + if (data.previousMon != restrict) + return nullptr; + + break; + } + + // case 3: no mon and no previous + return nullptr; + } + + return &data; +} + +SWorkspaceIDName CWorkspaceHistoryTracker::previousWorkspaceIDName(PHLWORKSPACE ws, PHLMONITOR restrict) { + const auto DATA = previousWorkspace(ws, restrict); + if (!DATA) + return SWorkspaceIDName{.id = WORKSPACE_INVALID}; + + return SWorkspaceIDName{.id = DATA->previousID, .name = DATA->previousName, .isAutoIDd = DATA->previousID <= 0}; +} + +void CWorkspaceHistoryTracker::setLastWorkspaceData(PHLWORKSPACE w) { + if (!w) { + m_lastWorkspaceData = {}; + return; + } + + m_lastWorkspaceData.workspace = w; + m_lastWorkspaceData.workspaceID = w->m_id; + m_lastWorkspaceData.workspaceName = w->m_name; + m_lastWorkspaceData.monitor = w->m_monitor; +} diff --git a/src/desktop/history/WorkspaceHistoryTracker.hpp b/src/desktop/history/WorkspaceHistoryTracker.hpp new file mode 100644 index 00000000..baecb363 --- /dev/null +++ b/src/desktop/history/WorkspaceHistoryTracker.hpp @@ -0,0 +1,52 @@ +#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 SWorkspacePreviousData { + PHLWORKSPACEREF workspace; + PHLWORKSPACEREF previous; + PHLMONITORREF previousMon; + std::string previousName = ""; + WORKSPACEID previousID = WORKSPACE_INVALID; + }; + + const SWorkspacePreviousData* previousWorkspace(PHLWORKSPACE ws); + SWorkspaceIDName previousWorkspaceIDName(PHLWORKSPACE ws); + + const SWorkspacePreviousData* 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::vector m_datas; + + void track(PHLWORKSPACE w); + void gc(); + void setLastWorkspaceData(PHLWORKSPACE w); + + SWorkspacePreviousData& dataFor(PHLWORKSPACE ws); + }; + + SP workspaceTracker(); +}; \ No newline at end of file diff --git a/src/desktop/reserved/ReservedArea.cpp b/src/desktop/reserved/ReservedArea.cpp new file mode 100644 index 00000000..8b4956dd --- /dev/null +++ b/src/desktop/reserved/ReservedArea.cpp @@ -0,0 +1,93 @@ +#include "ReservedArea.hpp" +#include "../../macros.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 + + ASSERT(parent.containsPoint(child.pos() + Vector2D{0.0001, 0.0001})); + ASSERT(parent.containsPoint(child.pos() + child.size() - Vector2D{0.0001, 0.0001})); + + m_initialTopLeft = child.pos() - parent.pos(); + m_initialBottomRight = (parent.pos() + parent.size()) - (child.pos() + child.size()); + + calculate(); +} + +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 00000000..2aca595d --- /dev/null +++ b/src/desktop/reserved/ReservedArea.hpp @@ -0,0 +1,48 @@ +#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 operator==(const CReservedArea& other) const; + + private: + void calculate(); + + Vector2D m_topLeft, m_bottomRight; + Vector2D m_initialTopLeft, m_initialBottomRight; + + 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 new file mode 100644 index 00000000..fa0c2e27 --- /dev/null +++ b/src/desktop/rule/Engine.cpp @@ -0,0 +1,56 @@ +#include "Engine.hpp" +#include "Rule.hpp" +#include "../view/LayerSurface.hpp" +#include "../../Compositor.hpp" + +using namespace Desktop; +using namespace Desktop::Rule; + +SP Rule::ruleEngine() { + static SP engine = makeShared(); + return engine; +} + +void CRuleEngine::registerRule(SP&& rule) { + m_rules.emplace_back(std::move(rule)); +} + +void CRuleEngine::unregisterRule(const std::string& name) { + if (name.empty()) + return; + + std::erase_if(m_rules, [&name](const auto& el) { return el->name() == name; }); +} + +void CRuleEngine::unregisterRule(const SP& rule) { + std::erase(m_rules, rule); + cleanExecRules(); +} + +void CRuleEngine::cleanExecRules() { + std::erase_if(m_rules, [](const auto& e) { return e->isExecRule() && e->execExpired(); }); +} + +void CRuleEngine::updateAllRules() { + cleanExecRules(); + for (const auto& w : g_pCompositor->m_windows) { + if (!validMapped(w) || w->isHidden()) + continue; + + w->m_ruleApplicator->propertiesChanged(RULE_PROP_ALL); + } + for (const auto& ls : g_pCompositor->m_layers) { + if (!validMapped(ls)) + continue; + + ls->m_ruleApplicator->propertiesChanged(RULE_PROP_ALL); + } +} + +void CRuleEngine::clearAllRules() { + std::erase_if(m_rules, [](const auto& e) { return !e->isExecRule() || e->execExpired(); }); +} + +const std::vector>& CRuleEngine::rules() { + return m_rules; +} diff --git a/src/desktop/rule/Engine.hpp b/src/desktop/rule/Engine.hpp new file mode 100644 index 00000000..b0ea118e --- /dev/null +++ b/src/desktop/rule/Engine.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "Rule.hpp" + +namespace Desktop::Rule { + class CRuleEngine { + public: + CRuleEngine() = default; + ~CRuleEngine() = default; + + void registerRule(SP&& rule); + void unregisterRule(const std::string& name); + void unregisterRule(const SP& rule); + void updateAllRules(); + void cleanExecRules(); + void clearAllRules(); + const std::vector>& rules(); + + private: + std::vector> m_rules; + }; + + SP ruleEngine(); +} \ No newline at end of file diff --git a/src/desktop/rule/Rule.cpp b/src/desktop/rule/Rule.cpp new file mode 100644 index 00000000..ab5525e8 --- /dev/null +++ b/src/desktop/rule/Rule.cpp @@ -0,0 +1,151 @@ +#include "Rule.hpp" +#include "../../debug/log/Logger.hpp" +#include + +#include "matchEngine/RegexMatchEngine.hpp" +#include "matchEngine/BoolMatchEngine.hpp" +#include "matchEngine/IntMatchEngine.hpp" +#include "matchEngine/WorkspaceMatchEngine.hpp" +#include "matchEngine/TagMatchEngine.hpp" + +using namespace Desktop; +using namespace Desktop::Rule; + +static const std::unordered_map MATCH_PROP_STRINGS = { + {RULE_PROP_CLASS, "class"}, // + {RULE_PROP_TITLE, "title"}, // + {RULE_PROP_INITIAL_CLASS, "initial_class"}, // + {RULE_PROP_INITIAL_TITLE, "initial_title"}, // + {RULE_PROP_FLOATING, "float"}, // + {RULE_PROP_TAG, "tag"}, // + {RULE_PROP_XWAYLAND, "xwayland"}, // + {RULE_PROP_FULLSCREEN, "fullscreen"}, // + {RULE_PROP_PINNED, "pin"}, // + {RULE_PROP_FOCUS, "focus"}, // + {RULE_PROP_GROUP, "group"}, // + {RULE_PROP_MODAL, "modal"}, // + {RULE_PROP_FULLSCREENSTATE_INTERNAL, "fullscreen_state_internal"}, // + {RULE_PROP_FULLSCREENSTATE_CLIENT, "fullscreen_state_client"}, // + {RULE_PROP_ON_WORKSPACE, "workspace"}, // + {RULE_PROP_CONTENT, "content"}, // + {RULE_PROP_XDG_TAG, "xdg_tag"}, // + {RULE_PROP_NAMESPACE, "namespace"}, // +}; + +static const std::unordered_map RULE_ENGINES = { + {RULE_PROP_CLASS, RULE_MATCH_ENGINE_REGEX}, // + {RULE_PROP_TITLE, RULE_MATCH_ENGINE_REGEX}, // + {RULE_PROP_INITIAL_CLASS, RULE_MATCH_ENGINE_REGEX}, // + {RULE_PROP_INITIAL_TITLE, RULE_MATCH_ENGINE_REGEX}, // + {RULE_PROP_FLOATING, RULE_MATCH_ENGINE_BOOL}, // + {RULE_PROP_TAG, RULE_MATCH_ENGINE_TAG}, // + {RULE_PROP_XWAYLAND, RULE_MATCH_ENGINE_BOOL}, // + {RULE_PROP_FULLSCREEN, RULE_MATCH_ENGINE_BOOL}, // + {RULE_PROP_PINNED, RULE_MATCH_ENGINE_BOOL}, // + {RULE_PROP_FOCUS, RULE_MATCH_ENGINE_BOOL}, // + {RULE_PROP_GROUP, RULE_MATCH_ENGINE_BOOL}, // + {RULE_PROP_MODAL, RULE_MATCH_ENGINE_BOOL}, // + {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_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() { + static std::vector strings; + static bool once = true; + if (once) { + for (const auto& [k, v] : MATCH_PROP_STRINGS) { + strings.emplace_back(v); + } + once = false; + } + return strings; +} + +std::optional Rule::matchPropFromString(const std::string_view& s) { + const auto IT = std::ranges::find_if(MATCH_PROP_STRINGS, [&s](const auto& el) { return el.second == s; }); + if (IT == MATCH_PROP_STRINGS.end()) + return std::nullopt; + + return IT->first; +} + +std::optional Rule::matchPropFromString(const std::string& s) { + return matchPropFromString(std::string_view{s}); +} + +IRule::IRule(const std::string& name) : m_name(name) { + ; +} + +void IRule::registerMatch(eRuleProperty p, const std::string& s) { + if (!RULE_ENGINES.contains(p)) { + Log::logger->log(Log::ERR, "BUG THIS: IRule: RULE_ENGINES does not contain rule idx {}", sc>(p)); + return; + } + + switch (RULE_ENGINES.at(p)) { + case RULE_MATCH_ENGINE_REGEX: m_matchEngines[p] = makeUnique(s); break; + case RULE_MATCH_ENGINE_BOOL: m_matchEngines[p] = makeUnique(s); break; + case RULE_MATCH_ENGINE_INT: m_matchEngines[p] = makeUnique(s); break; + case RULE_MATCH_ENGINE_WORKSPACE: m_matchEngines[p] = makeUnique(s); break; + case RULE_MATCH_ENGINE_TAG: m_matchEngines[p] = makeUnique(s); break; + } + + m_mask |= p; +} + +std::underlying_type_t IRule::getPropertiesMask() { + return m_mask; +} + +bool IRule::has(eRuleProperty p) { + return m_matchEngines.contains(p); +} + +bool IRule::matches(eRuleProperty p, const std::string& s) { + if (!has(p)) + return false; + + return m_matchEngines[p]->match(s); +} + +bool IRule::matches(eRuleProperty p, bool b) { + if (!has(p)) + return false; + + return m_matchEngines[p]->match(b); +} + +const std::string& IRule::name() { + return m_name; +} + +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); +} + +bool IRule::isExecRule() { + return m_execData.isExecRule; +} + +bool IRule::isExecPersistent() { + return m_execData.isExecPersistent; +} + +bool IRule::execExpired() { + return Time::steadyNow() > m_execData.expiresAt; +} + +const std::string& IRule::execToken() { + return m_execData.token; +} diff --git a/src/desktop/rule/Rule.hpp b/src/desktop/rule/Rule.hpp new file mode 100644 index 00000000..efd3cb39 --- /dev/null +++ b/src/desktop/rule/Rule.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include "matchEngine/MatchEngine.hpp" + +#include "../../helpers/memory/Memory.hpp" +#include "../../helpers/time/Time.hpp" +#include +#include +#include + +namespace Desktop::Rule { + enum eRuleProperty : uint32_t { + RULE_PROP_NONE = 0, + RULE_PROP_CLASS = (1 << 0), + RULE_PROP_TITLE = (1 << 1), + RULE_PROP_INITIAL_CLASS = (1 << 2), + RULE_PROP_INITIAL_TITLE = (1 << 3), + RULE_PROP_FLOATING = (1 << 4), + RULE_PROP_TAG = (1 << 5), + RULE_PROP_XWAYLAND = (1 << 6), + RULE_PROP_FULLSCREEN = (1 << 7), + RULE_PROP_PINNED = (1 << 8), + RULE_PROP_FOCUS = (1 << 9), + RULE_PROP_GROUP = (1 << 10), + RULE_PROP_MODAL = (1 << 11), + RULE_PROP_FULLSCREENSTATE_INTERNAL = (1 << 12), + RULE_PROP_FULLSCREENSTATE_CLIENT = (1 << 13), + RULE_PROP_ON_WORKSPACE = (1 << 14), + RULE_PROP_CONTENT = (1 << 15), + 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(), + }; + + enum eRuleType : uint8_t { + RULE_TYPE_WINDOW = 0, + RULE_TYPE_LAYER, + }; + + std::optional matchPropFromString(const std::string& s); + std::optional matchPropFromString(const std::string_view& s); + const std::vector& allMatchPropStrings(); + + class IRule { + public: + virtual ~IRule() = default; + + virtual eRuleType type() = 0; + virtual std::underlying_type_t getPropertiesMask(); + + void registerMatch(eRuleProperty, const std::string&); + void markAsExecRule(const std::string& token, uint64_t pid, bool persistent = false); + bool isExecRule(); + bool isExecPersistent(); + bool execExpired(); + const std::string& execToken(); + + const std::string& name(); + + protected: + IRule(const std::string& name = ""); + + bool matches(eRuleProperty, const std::string& s); + bool matches(eRuleProperty, bool b); + bool has(eRuleProperty); + + // + std::unordered_map> m_matchEngines; + + private: + std::underlying_type_t m_mask = 0; + std::string m_name = ""; + + struct { + bool isExecRule = false; + bool isExecPersistent = false; + std::string token; + uint64_t pid = 0; + Time::steady_tp expiresAt; + } m_execData; + }; +} diff --git a/src/desktop/rule/effect/EffectContainer.hpp b/src/desktop/rule/effect/EffectContainer.hpp new file mode 100644 index 00000000..51cae07e --- /dev/null +++ b/src/desktop/rule/effect/EffectContainer.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Desktop::Rule { + template + class IEffectContainer { + static_assert(std::is_enum_v); + + protected: + const std::string DEFAULT_MISSING_KEY = ""; + + public: + // Make sure we're using at least a uint16_t for dynamic registrations to not overflow. + // 32k should be enough + using storageType = std::conditional_t<(sizeof(std::underlying_type_t) >= 2), std::underlying_type_t, uint16_t>; + + IEffectContainer(std::vector&& defaultKeys) : m_keys(std::move(defaultKeys)), m_originalSize(m_keys.size()) { + ; + } + virtual ~IEffectContainer() = default; + + virtual storageType registerEffect(std::string&& name) { + if (m_keys.size() >= std::numeric_limits::max()) + return 0; + if (auto it = std::ranges::find(m_keys, name); it != m_keys.end()) + return it - m_keys.begin(); + m_keys.emplace_back(std::move(name)); + return m_keys.size() - 1; + } + + virtual void unregisterEffect(storageType id) { + if (id >= m_keys.size()) + return; + + m_keys[id] = DEFAULT_MISSING_KEY; + } + + virtual void unregisterEffect(const std::string& name) { + for (auto& key : m_keys) { + if (key == name) { + key = DEFAULT_MISSING_KEY; + break; + } + } + } + + virtual const std::string& get(storageType idx) { + if (idx >= m_keys.size()) + return DEFAULT_MISSING_KEY; + + return m_keys[idx]; + } + + virtual std::optional get(const std::string_view& s) { + for (storageType i = 0; i < m_keys.size(); ++i) { + if (m_keys[i] == s) + return i; + } + + return std::nullopt; + } + + virtual const std::vector& allEffectStrings() { + return m_keys; + } + + // whether the effect has been added dynamically as opposed to in the ctor. + virtual bool isEffectDynamic(storageType i) { + return i >= m_originalSize; + } + + protected: + std::vector m_keys; + size_t m_originalSize = 0; + }; +}; diff --git a/src/desktop/rule/layerRule/LayerRule.cpp b/src/desktop/rule/layerRule/LayerRule.cpp new file mode 100644 index 00000000..eccd9914 --- /dev/null +++ b/src/desktop/rule/layerRule/LayerRule.cpp @@ -0,0 +1,43 @@ +#include "LayerRule.hpp" +#include "../../../debug/log/Logger.hpp" +#include "../../view/LayerSurface.hpp" + +using namespace Desktop; +using namespace Desktop::Rule; + +CLayerRule::CLayerRule(const std::string& name) : IRule(name) { + ; +} + +eRuleType CLayerRule::type() { + return RULE_TYPE_LAYER; +} + +void CLayerRule::addEffect(CLayerRule::storageType e, const std::string& result) { + m_effects.emplace_back(std::make_pair<>(e, result)); +} + +const std::vector>& CLayerRule::effects() { + return m_effects; +} + +bool CLayerRule::matches(PHLLS ls) { + if (m_matchEngines.empty()) + return false; + + for (const auto& [prop, engine] : m_matchEngines) { + switch (prop) { + default: { + Log::logger->log(Log::TRACE, "CLayerRule::matches: skipping prop entry {}", sc>(prop)); + break; + } + + case RULE_PROP_NAMESPACE: + if (!engine->match(ls->m_namespace)) + return false; + break; + } + } + + return true; +} diff --git a/src/desktop/rule/layerRule/LayerRule.hpp b/src/desktop/rule/layerRule/LayerRule.hpp new file mode 100644 index 00000000..990796c1 --- /dev/null +++ b/src/desktop/rule/layerRule/LayerRule.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "../Rule.hpp" +#include "../../DesktopTypes.hpp" +#include "LayerRuleEffectContainer.hpp" + +namespace Desktop::Rule { + class CLayerRule : public IRule { + public: + using storageType = CLayerRuleEffectContainer::storageType; + + CLayerRule(const std::string& name = ""); + virtual ~CLayerRule() = default; + + virtual eRuleType type(); + + void addEffect(storageType e, const std::string& result); + const std::vector>& effects(); + + bool matches(PHLLS w); + + private: + std::vector> m_effects; + }; +}; diff --git a/src/desktop/rule/layerRule/LayerRuleApplicator.cpp b/src/desktop/rule/layerRule/LayerRuleApplicator.cpp new file mode 100644 index 00000000..fec3a5b2 --- /dev/null +++ b/src/desktop/rule/layerRule/LayerRuleApplicator.cpp @@ -0,0 +1,159 @@ +#include "LayerRuleApplicator.hpp" +#include "LayerRule.hpp" +#include "../Engine.hpp" +#include "../../view/LayerSurface.hpp" +#include "../../types/OverridableVar.hpp" +#include "../../../helpers/MiscFunctions.hpp" +#include "../../../event/EventBus.hpp" + +using namespace Desktop; +using namespace Desktop::Rule; + +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? + +#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) + +#undef UNSET + + if (prio == Types::PRIORITY_WINDOW_RULE) + std::erase_if(m_otherProps.props, [props](const auto& el) { return !el.second || el.second->propMask & props; }); +} + +void CLayerRuleApplicator::applyDynamicRule(const SP& rule) { + for (const auto& [key, effect] : rule->effects()) { + switch (key) { + default: { + if (key <= LAYER_RULE_EFFECT_LAST_STATIC) { + Log::logger->log(Log::TRACE, "CLayerRuleApplicator::applyDynamicRule: Skipping effect {}, not dynamic", sc>(key)); + break; + } + + // custom type, add to our vec + if (!m_otherProps.props.contains(key)) { + m_otherProps.props.emplace(key, + makeUnique(SCustomPropContainer{ + .idx = key, + .propMask = rule->getPropertiesMask(), + .effect = effect, + })); + } else { + auto& e = m_otherProps.props[key]; + e->propMask |= rule->getPropertiesMask(); + e->effect = effect; + } + + break; + } + case LAYER_RULE_EFFECT_NONE: { + Log::logger->log(Log::ERR, "CLayerRuleApplicator::applyDynamicRule: BUG THIS: LAYER_RULE_EFFECT_NONE??"); + break; + } + case LAYER_RULE_EFFECT_NO_ANIM: { + m_noanim.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_noanim.second |= rule->getPropertiesMask(); + break; + } + case LAYER_RULE_EFFECT_BLUR: { + m_blur.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_blur.second |= rule->getPropertiesMask(); + break; + } + case LAYER_RULE_EFFECT_BLUR_POPUPS: { + m_blurPopups.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_blurPopups.second |= rule->getPropertiesMask(); + break; + } + case LAYER_RULE_EFFECT_DIM_AROUND: { + m_dimAround.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_dimAround.second |= rule->getPropertiesMask(); + break; + } + case LAYER_RULE_EFFECT_XRAY: { + m_xray.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_xray.second |= rule->getPropertiesMask(); + break; + } + case LAYER_RULE_EFFECT_NO_SCREEN_SHARE: { + m_noScreenShare.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_noScreenShare.second |= rule->getPropertiesMask(); + break; + } + case LAYER_RULE_EFFECT_ORDER: { + try { + 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 (...) { 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 (...) { Log::logger->log(Log::ERR, "CLayerRuleApplicator::applyDynamicRule: invalid order {}", effect); } + break; + } + case LAYER_RULE_EFFECT_ANIMATION: { + m_animationStyle.first.set(effect, Types::PRIORITY_WINDOW_RULE); + m_animationStyle.second |= rule->getPropertiesMask(); + break; + } + } + } +} + +void CLayerRuleApplicator::propertiesChanged(std::underlying_type_t props) { + if (!m_ls) + return; + + resetProps(props); + + // FIXME: this will not update properties correctly if we implement dynamic rules for + // layers, due to effects overlapping on 0 prop intersection. + // See WindowRule.cpp, and ::propertiesChanged there. + + for (const auto& r : ruleEngine()->rules()) { + if (r->type() != RULE_TYPE_LAYER) + continue; + + if (!(r->getPropertiesMask() & props)) + continue; + + auto wr = reinterpretPointerCast(r); + + if (!wr->matches(m_ls.lock())) + continue; + + applyDynamicRule(wr); + } + + // for plugins + Event::bus()->m_events.layer.updateRules.emit(m_ls.lock()); +} diff --git a/src/desktop/rule/layerRule/LayerRuleApplicator.hpp b/src/desktop/rule/layerRule/LayerRuleApplicator.hpp new file mode 100644 index 00000000..35aa18c5 --- /dev/null +++ b/src/desktop/rule/layerRule/LayerRuleApplicator.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include "LayerRuleEffectContainer.hpp" +#include "../../DesktopTypes.hpp" +#include "../Rule.hpp" +#include "../../types/OverridableVar.hpp" +#include "../../../helpers/math/Math.hpp" +#include "../../../config/ConfigDataValues.hpp" + +namespace Desktop::Rule { + class CLayerRule; + + class CLayerRuleApplicator { + public: + CLayerRuleApplicator(PHLLS ls); + ~CLayerRuleApplicator() = default; + + CLayerRuleApplicator(const CLayerRuleApplicator&) = delete; + CLayerRuleApplicator(CLayerRuleApplicator&) = delete; + CLayerRuleApplicator(CLayerRuleApplicator&&) = delete; + + 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: \ + std::pair, std::underlying_type_t> m_##name = {def, RULE_PROP_NONE}; \ + \ + public: \ + Types::COverridableVar& name() { \ + return m_##name.first; \ + } \ + void name##Override(const Types::COverridableVar& other) { \ + m_##name.first = other; \ + } + + // dynamic props + DEFINE_PROP(bool, noanim, false) + DEFINE_PROP(bool, blur, false) + DEFINE_PROP(bool, blurPopups, false) + DEFINE_PROP(bool, dimAround, false) + DEFINE_PROP(bool, xray, false) + DEFINE_PROP(bool, noScreenShare, false) + + DEFINE_PROP(Hyprlang::INT, order, 0) + DEFINE_PROP(Hyprlang::INT, aboveLock, 0) + + DEFINE_PROP(Hyprlang::FLOAT, ignoreAlpha, 0.F) + + DEFINE_PROP(std::string, animationStyle, std::string("")) + +#undef COMMA +#undef DEFINE_PROP + + private: + PHLLSREF m_ls; + + void applyDynamicRule(const SP& rule); + }; +}; diff --git a/src/desktop/rule/layerRule/LayerRuleEffectContainer.cpp b/src/desktop/rule/layerRule/LayerRuleEffectContainer.cpp new file mode 100644 index 00000000..17394239 --- /dev/null +++ b/src/desktop/rule/layerRule/LayerRuleEffectContainer.cpp @@ -0,0 +1,33 @@ +#include "LayerRuleEffectContainer.hpp" + +using namespace Desktop; +using namespace Desktop::Rule; + +// +SP Rule::layerEffects() { + static SP container = makeShared(); + return container; +} + +static const std::vector EFFECT_STRINGS = { + "__internal_none", // + "no_anim", // + "blur", // + "blur_popups", // + "ignore_alpha", // + "dim_around", // + "xray", // + "animation", // + "order", // + "above_lock", // + "no_screen_share", // + "__internal_last_static", // +}; + +// This is here so that if we change the rules, we get reminded to update +// the strings. +static_assert(LAYER_RULE_EFFECT_LAST_STATIC == 11); + +CLayerRuleEffectContainer::CLayerRuleEffectContainer() : IEffectContainer(std::vector{EFFECT_STRINGS}) { + ; +} diff --git a/src/desktop/rule/layerRule/LayerRuleEffectContainer.hpp b/src/desktop/rule/layerRule/LayerRuleEffectContainer.hpp new file mode 100644 index 00000000..e3b3d26c --- /dev/null +++ b/src/desktop/rule/layerRule/LayerRuleEffectContainer.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "../effect/EffectContainer.hpp" +#include "../../../helpers/memory/Memory.hpp" + +#pragma once + +namespace Desktop::Rule { + enum eLayerRuleEffect : uint8_t { + LAYER_RULE_EFFECT_NONE = 0, + + LAYER_RULE_EFFECT_NO_ANIM, + LAYER_RULE_EFFECT_BLUR, + LAYER_RULE_EFFECT_BLUR_POPUPS, + LAYER_RULE_EFFECT_IGNORE_ALPHA, + LAYER_RULE_EFFECT_DIM_AROUND, + LAYER_RULE_EFFECT_XRAY, + LAYER_RULE_EFFECT_ANIMATION, + LAYER_RULE_EFFECT_ORDER, + LAYER_RULE_EFFECT_ABOVE_LOCK, + LAYER_RULE_EFFECT_NO_SCREEN_SHARE, + + LAYER_RULE_EFFECT_LAST_STATIC, + }; + + class CLayerRuleEffectContainer : public IEffectContainer { + public: + CLayerRuleEffectContainer(); + virtual ~CLayerRuleEffectContainer() = default; + }; + + SP layerEffects(); +}; \ No newline at end of file diff --git a/src/desktop/rule/matchEngine/BoolMatchEngine.cpp b/src/desktop/rule/matchEngine/BoolMatchEngine.cpp new file mode 100644 index 00000000..f5c47227 --- /dev/null +++ b/src/desktop/rule/matchEngine/BoolMatchEngine.cpp @@ -0,0 +1,12 @@ +#include "BoolMatchEngine.hpp" +#include "../../../helpers/MiscFunctions.hpp" + +using namespace Desktop::Rule; + +CBoolMatchEngine::CBoolMatchEngine(const std::string& s) : m_value(truthy(s)) { + ; +} + +bool CBoolMatchEngine::match(bool other) { + return other == m_value; +} diff --git a/src/desktop/rule/matchEngine/BoolMatchEngine.hpp b/src/desktop/rule/matchEngine/BoolMatchEngine.hpp new file mode 100644 index 00000000..bd162cda --- /dev/null +++ b/src/desktop/rule/matchEngine/BoolMatchEngine.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "MatchEngine.hpp" + +namespace Desktop::Rule { + class CBoolMatchEngine : public IMatchEngine { + public: + CBoolMatchEngine(const std::string&); + virtual ~CBoolMatchEngine() = default; + + virtual bool match(bool other); + + private: + bool m_value = false; + }; +} \ No newline at end of file diff --git a/src/desktop/rule/matchEngine/IntMatchEngine.cpp b/src/desktop/rule/matchEngine/IntMatchEngine.cpp new file mode 100644 index 00000000..c8f3c09e --- /dev/null +++ b/src/desktop/rule/matchEngine/IntMatchEngine.cpp @@ -0,0 +1,14 @@ +#include "IntMatchEngine.hpp" +#include "../../../debug/log/Logger.hpp" + +using namespace Desktop::Rule; + +CIntMatchEngine::CIntMatchEngine(const std::string& s) { + try { + m_value = std::stoi(s); + } catch (...) { Log::logger->log(Log::ERR, "CIntMatchEngine: invalid input {}", s); } +} + +bool CIntMatchEngine::match(int other) { + return m_value == other; +} diff --git a/src/desktop/rule/matchEngine/IntMatchEngine.hpp b/src/desktop/rule/matchEngine/IntMatchEngine.hpp new file mode 100644 index 00000000..2eda492c --- /dev/null +++ b/src/desktop/rule/matchEngine/IntMatchEngine.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "MatchEngine.hpp" + +namespace Desktop::Rule { + class CIntMatchEngine : public IMatchEngine { + public: + CIntMatchEngine(const std::string&); + virtual ~CIntMatchEngine() = default; + + virtual bool match(int other); + + private: + int m_value = 0; + }; +} \ No newline at end of file diff --git a/src/desktop/rule/matchEngine/MatchEngine.cpp b/src/desktop/rule/matchEngine/MatchEngine.cpp new file mode 100644 index 00000000..0bc89d7f --- /dev/null +++ b/src/desktop/rule/matchEngine/MatchEngine.cpp @@ -0,0 +1,23 @@ +#include "MatchEngine.hpp" + +using namespace Desktop::Rule; + +bool IMatchEngine::match(const std::string&) { + return false; +} + +bool IMatchEngine::match(bool) { + return false; +} + +bool IMatchEngine::match(int) { + return false; +} + +bool IMatchEngine::match(PHLWORKSPACE) { + return false; +} + +bool IMatchEngine::match(const CTagKeeper& keeper) { + return false; +} diff --git a/src/desktop/rule/matchEngine/MatchEngine.hpp b/src/desktop/rule/matchEngine/MatchEngine.hpp new file mode 100644 index 00000000..9588ac05 --- /dev/null +++ b/src/desktop/rule/matchEngine/MatchEngine.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "../../DesktopTypes.hpp" + +class CTagKeeper; + +namespace Desktop::Rule { + enum eRuleMatchEngine : uint8_t { + RULE_MATCH_ENGINE_REGEX = 0, + RULE_MATCH_ENGINE_BOOL, + RULE_MATCH_ENGINE_INT, + RULE_MATCH_ENGINE_WORKSPACE, + RULE_MATCH_ENGINE_TAG, + }; + + class IMatchEngine { + public: + virtual ~IMatchEngine() = default; + virtual bool match(const std::string&); + virtual bool match(bool); + virtual bool match(int); + virtual bool match(PHLWORKSPACE); + virtual bool match(const CTagKeeper& keeper); + + protected: + IMatchEngine() = default; + }; +}; \ No newline at end of file diff --git a/src/desktop/rule/matchEngine/RegexMatchEngine.cpp b/src/desktop/rule/matchEngine/RegexMatchEngine.cpp new file mode 100644 index 00000000..14e30af1 --- /dev/null +++ b/src/desktop/rule/matchEngine/RegexMatchEngine.cpp @@ -0,0 +1,17 @@ +#include "RegexMatchEngine.hpp" +#include + +using namespace Desktop::Rule; + +CRegexMatchEngine::CRegexMatchEngine(const std::string& regex) { + if (regex.starts_with("negative:")) { + m_negative = true; + m_regex = makeUnique(regex.substr(9)); + return; + } + m_regex = makeUnique(regex); +} + +bool CRegexMatchEngine::match(const std::string& other) { + return re2::RE2::FullMatch(other, *m_regex) != m_negative; +} \ No newline at end of file diff --git a/src/desktop/rule/matchEngine/RegexMatchEngine.hpp b/src/desktop/rule/matchEngine/RegexMatchEngine.hpp new file mode 100644 index 00000000..e980ce70 --- /dev/null +++ b/src/desktop/rule/matchEngine/RegexMatchEngine.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "MatchEngine.hpp" +#include "../../../helpers/memory/Memory.hpp" + +//NOLINTNEXTLINE +namespace re2 { + class RE2; +}; + +namespace Desktop::Rule { + class CRegexMatchEngine : public IMatchEngine { + public: + CRegexMatchEngine(const std::string& regex); + virtual ~CRegexMatchEngine() = default; + + virtual bool match(const std::string& other); + + private: + UP m_regex; + bool m_negative = false; + }; +} \ No newline at end of file diff --git a/src/desktop/rule/matchEngine/TagMatchEngine.cpp b/src/desktop/rule/matchEngine/TagMatchEngine.cpp new file mode 100644 index 00000000..6b38c980 --- /dev/null +++ b/src/desktop/rule/matchEngine/TagMatchEngine.cpp @@ -0,0 +1,13 @@ +#include "TagMatchEngine.hpp" +#include "../../../helpers/TagKeeper.hpp" +#include + +using namespace Desktop::Rule; + +CTagMatchEngine::CTagMatchEngine(const std::string& tag) : m_tag(tag) { + ; +} + +bool CTagMatchEngine::match(const CTagKeeper& keeper) { + return keeper.isTagged(m_tag); +} diff --git a/src/desktop/rule/matchEngine/TagMatchEngine.hpp b/src/desktop/rule/matchEngine/TagMatchEngine.hpp new file mode 100644 index 00000000..e5e65c9b --- /dev/null +++ b/src/desktop/rule/matchEngine/TagMatchEngine.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "MatchEngine.hpp" +#include + +namespace Desktop::Rule { + class CTagMatchEngine : public IMatchEngine { + public: + CTagMatchEngine(const std::string& tag); + virtual ~CTagMatchEngine() = default; + + virtual bool match(const CTagKeeper& keeper); + + private: + std::string m_tag; + }; +} diff --git a/src/desktop/rule/matchEngine/WorkspaceMatchEngine.cpp b/src/desktop/rule/matchEngine/WorkspaceMatchEngine.cpp new file mode 100644 index 00000000..fea5c384 --- /dev/null +++ b/src/desktop/rule/matchEngine/WorkspaceMatchEngine.cpp @@ -0,0 +1,12 @@ +#include "WorkspaceMatchEngine.hpp" +#include "../../Workspace.hpp" + +using namespace Desktop::Rule; + +CWorkspaceMatchEngine::CWorkspaceMatchEngine(const std::string& s) : m_value(s) { + ; +} + +bool CWorkspaceMatchEngine::match(PHLWORKSPACE ws) { + return ws && ws->matchesStaticSelector(m_value); +} diff --git a/src/desktop/rule/matchEngine/WorkspaceMatchEngine.hpp b/src/desktop/rule/matchEngine/WorkspaceMatchEngine.hpp new file mode 100644 index 00000000..dcdf4136 --- /dev/null +++ b/src/desktop/rule/matchEngine/WorkspaceMatchEngine.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "MatchEngine.hpp" +#include + +namespace Desktop::Rule { + class CWorkspaceMatchEngine : public IMatchEngine { + public: + CWorkspaceMatchEngine(const std::string&); + virtual ~CWorkspaceMatchEngine() = default; + + virtual bool match(PHLWORKSPACE ws); + + private: + std::string m_value = ""; + }; +} diff --git a/src/desktop/rule/utils/SetUtils.hpp b/src/desktop/rule/utils/SetUtils.hpp new file mode 100644 index 00000000..75fd4739 --- /dev/null +++ b/src/desktop/rule/utils/SetUtils.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace Desktop::Rule { + template + bool setsIntersect(const std::unordered_set& A, const std::unordered_set& B) { + if (A.size() > B.size()) + return setsIntersect(B, A); + + for (const auto& e : A) { + if (B.contains(e)) + return true; + } + return false; + } +}; \ No newline at end of file diff --git a/src/desktop/rule/windowRule/WindowRule.cpp b/src/desktop/rule/windowRule/WindowRule.cpp new file mode 100644 index 00000000..8028e482 --- /dev/null +++ b/src/desktop/rule/windowRule/WindowRule.cpp @@ -0,0 +1,165 @@ +#include "WindowRule.hpp" +#include "../../view/Window.hpp" +#include "../../../helpers/Monitor.hpp" +#include "../../../Compositor.hpp" +#include "../../../managers/TokenManager.hpp" +#include "../../../desktop/state/FocusState.hpp" + +using namespace Desktop; +using namespace Desktop::Rule; + +CWindowRule::CWindowRule(const std::string& name) : IRule(name) { + ; +} + +eRuleType CWindowRule::type() { + return RULE_TYPE_WINDOW; +} + +void CWindowRule::addEffect(CWindowRule::storageType e, const std::string& result) { + m_effects.emplace_back(std::make_pair<>(e, result)); + m_effectSet.emplace(e); +} + +const std::vector>& CWindowRule::effects() { + return m_effects; +} + +bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) { + if (m_matchEngines.empty()) + return false; + + for (const auto& [prop, engine] : m_matchEngines) { + switch (prop) { + default: { + Log::logger->log(Log::TRACE, "CWindowRule::matches: skipping prop entry {}", sc>(prop)); + break; + } + + case RULE_PROP_TITLE: + if (!engine->match(w->m_title)) + return false; + break; + case RULE_PROP_INITIAL_TITLE: + if (!engine->match(w->m_initialTitle)) + return false; + break; + case RULE_PROP_CLASS: + if (!engine->match(w->m_class)) + return false; + break; + case RULE_PROP_INITIAL_CLASS: + if (!engine->match(w->m_initialClass)) + return false; + break; + case RULE_PROP_FLOATING: + if (!engine->match(w->m_isFloating)) + return false; + break; + case RULE_PROP_TAG: + if (!engine->match(w->m_ruleApplicator->m_tagKeeper)) + return false; + break; + case RULE_PROP_XWAYLAND: + if (!engine->match(w->m_isX11)) + return false; + break; + case RULE_PROP_FULLSCREEN: + if (!engine->match(w->m_fullscreenState.internal != 0)) + return false; + break; + case RULE_PROP_PINNED: + if (!engine->match(w->m_pinned)) + return false; + break; + case RULE_PROP_FOCUS: + if (!engine->match(Desktop::focusState()->window() == w)) + return false; + break; + case RULE_PROP_GROUP: + if (!engine->match(!!w->m_group)) + return false; + break; + case RULE_PROP_MODAL: + if (!engine->match(w->isModal())) + return false; + break; + case RULE_PROP_FULLSCREENSTATE_INTERNAL: + if (!engine->match(w->m_fullscreenState.internal)) + return false; + break; + case RULE_PROP_FULLSCREENSTATE_CLIENT: + if (!engine->match(w->m_fullscreenState.client)) + return false; + break; + case RULE_PROP_ON_WORKSPACE: + if (!engine->match(w->m_workspace)) + return false; + break; + case RULE_PROP_CONTENT: + if (!engine->match(w->getContentType()) && !engine->match(NContentType::toString(w->getContentType()))) + return false; + break; + case RULE_PROP_XDG_TAG: + if (!w->xdgTag().has_value() || !engine->match(*w->xdgTag())) + return false; + break; + + case RULE_PROP_EXEC_TOKEN: + if (!allowEnvLookup) + break; + + const auto ENV = w->getEnv(); + bool match = 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; + } + } + + return true; +} + +SP CWindowRule::buildFromExecString(std::string&& s) { + CVarList2 varlist(std::move(s), 0, ';'); + SP wr = makeShared("__exec_rule"); + + for (const auto& el : varlist) { + // split element by space, can't do better + size_t spacePos = el.find(' '); + if (spacePos != std::string::npos) { + // great, split and try to parse + auto LHS = el.substr(0, spacePos); + const auto EFFECT = windowEffects()->get(LHS); + + if (!EFFECT.has_value() || *EFFECT == WINDOW_RULE_EFFECT_NONE) + continue; // invalid... + + wr->addEffect(*EFFECT, std::string{el.substr(spacePos + 1)}); + continue; + } + + // assume 1 maybe... + + const auto EFFECT = windowEffects()->get(el); + + if (!EFFECT.has_value() || *EFFECT == WINDOW_RULE_EFFECT_NONE) + continue; // invalid... + + wr->addEffect(*EFFECT, std::string{"1"}); + } + + return wr; +} + +const std::unordered_set& CWindowRule::effectsSet() { + return m_effectSet; +} diff --git a/src/desktop/rule/windowRule/WindowRule.hpp b/src/desktop/rule/windowRule/WindowRule.hpp new file mode 100644 index 00000000..f7621828 --- /dev/null +++ b/src/desktop/rule/windowRule/WindowRule.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "../Rule.hpp" +#include "../../DesktopTypes.hpp" +#include "WindowRuleEffectContainer.hpp" +#include "../../../helpers/math/Math.hpp" + +#include + +namespace Desktop::Rule { + constexpr const char* EXEC_RULE_ENV_NAME = "HL_EXEC_RULE_TOKEN"; + + class CWindowRule : public IRule { + private: + using storageType = CWindowRuleEffectContainer::storageType; + + public: + CWindowRule(const std::string& name = ""); + virtual ~CWindowRule() = default; + + static SP buildFromExecString(std::string&&); + + virtual eRuleType type(); + + void addEffect(storageType e, const std::string& result); + const std::vector>& effects(); + const std::unordered_set& effectsSet(); + + bool matches(PHLWINDOW w, bool allowEnvLookup = false); + + private: + std::vector> m_effects; + std::unordered_set m_effectSet; + }; +}; diff --git a/src/desktop/rule/windowRule/WindowRuleApplicator.cpp b/src/desktop/rule/windowRule/WindowRuleApplicator.cpp new file mode 100644 index 00000000..07cb5f64 --- /dev/null +++ b/src/desktop/rule/windowRule/WindowRuleApplicator.cpp @@ -0,0 +1,642 @@ +#include "WindowRuleApplicator.hpp" +#include "WindowRule.hpp" +#include "../Engine.hpp" +#include "../utils/SetUtils.hpp" +#include "../../view/Window.hpp" +#include "../../types/OverridableVar.hpp" +#include "../../../event/EventBus.hpp" + +#include + +using namespace Hyprutils::String; + +using namespace Desktop; +using namespace Desktop::Rule; + +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 + + if (prio == Types::PRIORITY_WINDOW_RULE) { + std::erase_if(m_dynamicTags, [props, this](const auto& el) { + const bool REMOVE = el.second & props; + + if (REMOVE) + m_tagKeeper.removeDynamicTag(el.first); + + return REMOVE; + }); + + std::erase_if(m_otherProps.props, [props](const auto& el) { return !el.second || el.second->propMask & props; }); + } + + return effectsNuked; +} + +CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const SP& rule) { + SRuleResult result; + + for (const auto& [key, effect] : rule->effects()) { + switch (key) { + default: { + if (key <= WINDOW_RULE_EFFECT_LAST_STATIC) { + Log::logger->log(Log::TRACE, "CWindowRuleApplicator::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 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 (...) { 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 (...) { Log::logger->log(Log::ERR, "CWindowRuleApplicator::applyDynamicRule: invalid rounding_power {}", effect); } + break; + } + case WINDOW_RULE_EFFECT_PERSISTENT_SIZE: { + m_persistentSize.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_persistentSize.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_ANIMATION: { + m_animationStyle.first.set(effect, Types::PRIORITY_WINDOW_RULE); + m_animationStyle.second |= rule->getPropertiesMask(); + break; + } + 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), 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); + m_inactiveBorderColor.first = + Types::COverridableVar(CGradientValueData(CHyprColor(configStringToInt(colorsAndAngles[1]).value_or(0))), Types::PRIORITY_WINDOW_RULE); + m_activeBorderColor.second |= rule->getPropertiesMask(); + m_inactiveBorderColor.second |= rule->getPropertiesMask(); + break; + } + + for (auto const& token : colorsAndAngles) { + // The first angle, or an explicit "0deg", splits the two gradients + if (active && token.contains("deg")) { + activeBorderGradient.m_angle = std::stoi(token.substr(0, token.size() - 3)) * (PI / 180.0); + active = false; + } else if (token.contains("deg")) + inactiveBorderGradient.m_angle = std::stoi(token.substr(0, token.size() - 3)) * (PI / 180.0); + else if (active) + activeBorderGradient.m_colors.emplace_back(configStringToInt(token).value_or(0)); + else + inactiveBorderGradient.m_colors.emplace_back(configStringToInt(token).value_or(0)); + } + + activeBorderGradient.updateColorsOk(); + + // Includes sanity checks for the number of colors in each gradient + if (activeBorderGradient.m_colors.size() > 10 || inactiveBorderGradient.m_colors.size() > 10) + Log::logger->log(Log::WARN, "Bordercolor rule \"{}\" has more than 10 colors in one gradient, ignoring", effect); + else if (activeBorderGradient.m_colors.empty()) + 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) { Log::logger->log(Log::ERR, "BorderColor rule \"{}\" failed with: {}", effect, e.what()); } + m_activeBorderColor.second = rule->getPropertiesMask(); + m_inactiveBorderColor.second = rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_IDLE_INHIBIT: { + if (effect == "none") + m_idleInhibitMode.first.set(IDLEINHIBIT_NONE, Types::PRIORITY_WINDOW_RULE); + else if (effect == "always") + m_idleInhibitMode.first.set(IDLEINHIBIT_ALWAYS, Types::PRIORITY_WINDOW_RULE); + else if (effect == "focus") + m_idleInhibitMode.first.set(IDLEINHIBIT_FOCUS, Types::PRIORITY_WINDOW_RULE); + else if (effect == "fullscreen") + m_idleInhibitMode.first.set(IDLEINHIBIT_FULLSCREEN, Types::PRIORITY_WINDOW_RULE); + else + Log::logger->log(Log::ERR, "Rule idleinhibit: unknown mode {}", effect); + m_idleInhibitMode.second = rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_OPACITY: { + try { + CVarList2 vars(std::string{effect}, 0, ' '); + + int opacityIDX = 0; + + for (const auto& r : vars) { + if (r == "opacity") + continue; + + if (r == "override") { + if (opacityIDX == 1) + m_alpha.first = Types::COverridableVar(Types::SAlphaValue{.alpha = m_alpha.first.value().alpha, .overridden = true}, Types::PRIORITY_WINDOW_RULE); + else if (opacityIDX == 2) + m_alphaInactive.first = + Types::COverridableVar(Types::SAlphaValue{.alpha = m_alphaInactive.first.value().alpha, .overridden = true}, Types::PRIORITY_WINDOW_RULE); + else if (opacityIDX == 3) + m_alphaFullscreen.first = + Types::COverridableVar(Types::SAlphaValue{.alpha = m_alphaFullscreen.first.value().alpha, .overridden = true}, Types::PRIORITY_WINDOW_RULE); + } else { + if (opacityIDX == 0) + m_alpha.first = Types::COverridableVar(Types::SAlphaValue{.alpha = std::stof(std::string{r}), .overridden = false}, Types::PRIORITY_WINDOW_RULE); + else if (opacityIDX == 1) + m_alphaInactive.first = + Types::COverridableVar(Types::SAlphaValue{.alpha = std::stof(std::string{r}), .overridden = false}, Types::PRIORITY_WINDOW_RULE); + else if (opacityIDX == 2) + m_alphaFullscreen.first = + Types::COverridableVar(Types::SAlphaValue{.alpha = std::stof(std::string{r}), .overridden = false}, Types::PRIORITY_WINDOW_RULE); + else + throw std::runtime_error("more than 3 alpha values"); + + opacityIDX++; + } + } + + if (opacityIDX == 1) { + m_alphaInactive.first = m_alpha.first; + m_alphaFullscreen.first = m_alpha.first; + } + } 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(); + break; + } + case WINDOW_RULE_EFFECT_TAG: { + m_dynamicTags.emplace_back(std::make_pair<>(effect, rule->getPropertiesMask())); + m_tagKeeper.applyTag(effect, true); + result.tagsChanged = true; + break; + } + case WINDOW_RULE_EFFECT_MAX_SIZE: { + try { + static auto PCLAMP_TILED = CConfigValue("misc:size_limits_tiled"); + + if (!m_window) + break; + + const auto VEC = m_window->calculateExpression(effect); + if (!VEC) { + Log::logger->log(Log::ERR, "failed to parse {} as an expression", effect); + break; + } + if (VEC->x < 1 || VEC->y < 1) { + Log::logger->log(Log::ERR, "Invalid size for maxsize"); + break; + } + + m_maxSize.first = Types::COverridableVar(*VEC, Types::PRIORITY_WINDOW_RULE); + + if (*PCLAMP_TILED || m_window->m_isFloating) + m_window->clampWindowSize(std::nullopt, m_maxSize.first.value()); + } catch (std::exception& e) { Log::logger->log(Log::ERR, "maxsize rule \"{}\" failed with: {}", effect, e.what()); } + m_maxSize.second = rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_MIN_SIZE: { + try { + static auto PCLAMP_TILED = CConfigValue("misc:size_limits_tiled"); + + if (!m_window) + break; + + const auto VEC = m_window->calculateExpression(effect); + if (!VEC) { + Log::logger->log(Log::ERR, "failed to parse {} as an expression", effect); + break; + } + + if (VEC->x < 1 || VEC->y < 1) { + 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; + } + case WINDOW_RULE_EFFECT_BORDER_SIZE: { + try { + auto oldBorderSize = m_borderSize.first.valueOrDefault(); + m_borderSize.first.set(std::stoi(effect), Types::PRIORITY_WINDOW_RULE); + m_borderSize.second |= rule->getPropertiesMask(); + if (oldBorderSize != m_borderSize.first.valueOrDefault()) + result.needsRelayout = true; + } catch (...) { Log::logger->log(Log::ERR, "CWindowRuleApplicator::applyDynamicRule: invalid border_size {}", effect); } + break; + } + case WINDOW_RULE_EFFECT_ALLOWS_INPUT: { + m_allowsInput.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_allowsInput.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_DIM_AROUND: { + m_dimAround.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_dimAround.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_DECORATE: { + m_decorate.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_decorate.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_FOCUS_ON_ACTIVATE: { + m_focusOnActivate.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_focusOnActivate.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_KEEP_ASPECT_RATIO: { + m_keepAspectRatio.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_keepAspectRatio.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_NEAREST_NEIGHBOR: { + m_nearestNeighbor.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_nearestNeighbor.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_NO_ANIM: { + m_noAnim.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_noAnim.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_NO_BLUR: { + m_noBlur.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_noBlur.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_NO_DIM: { + m_noDim.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_noDim.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_NO_FOCUS: { + m_noFocus.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_noFocus.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_NO_FOLLOW_MOUSE: { + m_noFollowMouse.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_noFollowMouse.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_NO_MAX_SIZE: { + m_noMaxSize.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_noMaxSize.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_NO_SHADOW: { + m_noShadow.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_noShadow.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_NO_SHORTCUTS_INHIBIT: { + m_noShortcutsInhibit.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_noShortcutsInhibit.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_OPAQUE: { + m_opaque.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_opaque.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_FORCE_RGBX: { + m_RGBX.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_RGBX.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_SYNC_FULLSCREEN: { + m_syncFullscreen.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_syncFullscreen.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_IMMEDIATE: { + m_tearing.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_tearing.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_XRAY: { + m_xray.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_xray.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_RENDER_UNFOCUSED: { + m_renderUnfocused.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_renderUnfocused.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_NO_SCREEN_SHARE: { + m_noScreenShare.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_noScreenShare.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_NO_VRR: { + m_noVRR.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_noVRR.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_STAY_FOCUSED: { + m_stayFocused.first.set(truthy(effect), Types::PRIORITY_WINDOW_RULE); + m_stayFocused.second |= rule->getPropertiesMask(); + break; + } + case WINDOW_RULE_EFFECT_SCROLL_MOUSE: { + try { + m_scrollMouse.first.set(std::clamp(std::stof(effect), 0.01F, 10.F), Types::PRIORITY_WINDOW_RULE); + m_scrollMouse.second |= rule->getPropertiesMask(); + } 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 (...) { Log::logger->log(Log::ERR, "CWindowRuleApplicator::applyDynamicRule: invalid scroll_touchpad {}", effect); } + break; + } + } + } + return result; +} + +CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyStaticRule(const SP& rule) { + for (const auto& [key, effect] : rule->effects()) { + switch (key) { + default: { + Log::logger->log(Log::TRACE, "CWindowRuleApplicator::applyStaticRule: Skipping effect {}, not static", sc>(key)); + break; + } + + case WINDOW_RULE_EFFECT_FLOAT: { + static_.floating = truthy(effect); + break; + } + case WINDOW_RULE_EFFECT_TILE: { + static_.floating = !truthy(effect); + break; + } + case WINDOW_RULE_EFFECT_FULLSCREEN: { + static_.fullscreen = truthy(effect); + break; + } + case WINDOW_RULE_EFFECT_MAXIMIZE: { + static_.maximize = truthy(effect); + break; + } + case WINDOW_RULE_EFFECT_FULLSCREENSTATE: { + CVarList2 vars(std::string{effect}, 0, 's'); + try { + static_.fullscreenStateInternal = std::stoi(std::string{vars[0]}); + if (!vars[1].empty()) + static_.fullscreenStateClient = std::stoi(std::string{vars[1]}); + } 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; + } + case WINDOW_RULE_EFFECT_SIZE: { + static_.size = effect; + break; + } + case WINDOW_RULE_EFFECT_CENTER: { + static_.position.clear(); + static_.center = truthy(effect); + break; + } + case WINDOW_RULE_EFFECT_PSEUDO: { + static_.pseudo = truthy(effect); + break; + } + case WINDOW_RULE_EFFECT_MONITOR: { + static_.monitor = effect; + break; + } + case WINDOW_RULE_EFFECT_WORKSPACE: { + static_.workspace = effect; + break; + } + case WINDOW_RULE_EFFECT_NOINITIALFOCUS: { + static_.noInitialFocus = truthy(effect); + break; + } + case WINDOW_RULE_EFFECT_PIN: { + static_.pin = truthy(effect); + break; + } + case WINDOW_RULE_EFFECT_GROUP: { + static_.group = effect; + break; + } + case WINDOW_RULE_EFFECT_SUPPRESSEVENT: { + CVarList2 varlist(std::string{effect}, 0, 's'); + for (const auto& e : varlist) { + static_.suppressEvent.emplace_back(e); + } + break; + } + case WINDOW_RULE_EFFECT_CONTENT: { + static_.content = NContentType::fromString(effect); + break; + } + case WINDOW_RULE_EFFECT_NOCLOSEFOR: { + try { + static_.noCloseFor = std::stoi(effect); + } catch (...) { Log::logger->log(Log::ERR, "CWindowRuleApplicator::applyStaticRule: invalid no close for {}", effect); } + break; + } + } + } + + return SRuleResult{}; +} + +void CWindowRuleApplicator::readStaticRules(bool preRead) { + if (!m_window) + return; + + static_ = {}; + + std::vector> execRules; + bool tagsWereChanged = false; + + for (const auto& r : ruleEngine()->rules()) { + if (r->type() != RULE_TYPE_WINDOW) + continue; + + auto wr = reinterpretPointerCast(r); + + if (!wr->matches(m_window.lock(), true)) + continue; + + if (wr->isExecRule()) { + execRules.emplace_back(wr); + continue; + } + + applyStaticRule(wr); + const auto RES = applyDynamicRule(wr); + tagsWereChanged = tagsWereChanged || RES.tagsChanged; + } + + // recheck some props people might wanna use for static rules. + std::underlying_type_t propsToRecheck = RULE_PROP_NONE; + 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); + if (!preRead) + ruleEngine()->unregisterRule(wr); + } +} + +void CWindowRuleApplicator::propertiesChanged(std::underlying_type_t props) { + if (!m_window || !m_window->m_isMapped || m_window->isHidden()) + return; + + bool needsRelayout = false; + std::unordered_set effectsNeedingRecheck = resetProps(props); + + for (const auto& r : ruleEngine()->rules()) { + if (r->type() != RULE_TYPE_WINDOW) + continue; + + const auto WR = reinterpretPointerCast(r); + + if (!(WR->getPropertiesMask() & props) && !setsIntersect(WR->effectsSet(), effectsNeedingRecheck)) + continue; + + if (!WR->matches(m_window.lock())) + continue; + + const auto RES = applyDynamicRule(WR); + needsRelayout = needsRelayout || RES.needsRelayout; + } + + m_window->updateWindowData(); + m_window->updateWindowDecos(); + m_window->updateDecorationValues(); + + if (needsRelayout) + g_pDecorationPositioner->forceRecalcFor(m_window.lock()); + + // for plugins + Event::bus()->m_events.window.updateRules.emit(m_window.lock()); +} diff --git a/src/desktop/rule/windowRule/WindowRuleApplicator.hpp b/src/desktop/rule/windowRule/WindowRuleApplicator.hpp new file mode 100644 index 00000000..5c1d4fd1 --- /dev/null +++ b/src/desktop/rule/windowRule/WindowRuleApplicator.hpp @@ -0,0 +1,151 @@ +#pragma once + +#include +#include + +#include "WindowRuleEffectContainer.hpp" +#include "../../DesktopTypes.hpp" +#include "../Rule.hpp" +#include "../../types/OverridableVar.hpp" +#include "../../../helpers/math/Math.hpp" +#include "../../../helpers/TagKeeper.hpp" +#include "../../../config/ConfigDataValues.hpp" + +namespace Desktop::Rule { + class CWindowRule; + + enum eIdleInhibitMode : uint8_t { + IDLEINHIBIT_NONE = 0, + IDLEINHIBIT_ALWAYS, + IDLEINHIBIT_FULLSCREEN, + IDLEINHIBIT_FOCUS + }; + + class CWindowRuleApplicator { + public: + CWindowRuleApplicator(PHLWINDOW w); + ~CWindowRuleApplicator() = default; + + CWindowRuleApplicator(const CWindowRuleApplicator&) = delete; + CWindowRuleApplicator(CWindowRuleApplicator&) = delete; + CWindowRuleApplicator(CWindowRuleApplicator&&) = delete; + + void propertiesChanged(std::underlying_type_t props); + std::unordered_set resetProps(std::underlying_type_t props, + Types::eOverridePriority prio = Types::PRIORITY_WINDOW_RULE); + void readStaticRules(bool preRead = false); + + // static props + struct { + std::string monitor, workspace, group; + + std::optional floating; + 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 content; + std::optional noCloseFor; + + std::string size, position; + + std::vector suppressEvent; + } static_; + + struct SCustomPropContainer { + CWindowRuleEffectContainer::storageType idx = WINDOW_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, eff) \ + private: \ + std::pair, std::underlying_type_t> m_##name = {def, RULE_PROP_NONE}; \ + \ + public: \ + Types::COverridableVar& name() { \ + return m_##name.first; \ + } \ + void name##Override(const Types::COverridableVar& other) { \ + m_##name.first = other; \ + } \ + eWindowRuleEffect name##Effect() { \ + return eff; \ + } + + // dynamic props + DEFINE_PROP(Types::SAlphaValue, alpha, Types::SAlphaValue{}, WINDOW_RULE_EFFECT_OPACITY) + DEFINE_PROP(Types::SAlphaValue, alphaInactive, Types::SAlphaValue{}, WINDOW_RULE_EFFECT_OPACITY) + DEFINE_PROP(Types::SAlphaValue, alphaFullscreen, Types::SAlphaValue{}, WINDOW_RULE_EFFECT_OPACITY) + + DEFINE_PROP(bool, allowsInput, false, WINDOW_RULE_EFFECT_ALLOWS_INPUT) + DEFINE_PROP(bool, decorate, true, WINDOW_RULE_EFFECT_DECORATE) + DEFINE_PROP(bool, focusOnActivate, false, WINDOW_RULE_EFFECT_FOCUS_ON_ACTIVATE) + DEFINE_PROP(bool, keepAspectRatio, false, WINDOW_RULE_EFFECT_KEEP_ASPECT_RATIO) + DEFINE_PROP(bool, nearestNeighbor, false, WINDOW_RULE_EFFECT_NEAREST_NEIGHBOR) + DEFINE_PROP(bool, noAnim, false, WINDOW_RULE_EFFECT_NO_ANIM) + DEFINE_PROP(bool, noBlur, false, WINDOW_RULE_EFFECT_NO_BLUR) + DEFINE_PROP(bool, noDim, false, WINDOW_RULE_EFFECT_NO_DIM) + DEFINE_PROP(bool, noFocus, false, WINDOW_RULE_EFFECT_NO_FOCUS) + DEFINE_PROP(bool, noMaxSize, false, WINDOW_RULE_EFFECT_NO_MAX_SIZE) + DEFINE_PROP(bool, noShadow, false, WINDOW_RULE_EFFECT_NO_SHADOW) + DEFINE_PROP(bool, noShortcutsInhibit, false, WINDOW_RULE_EFFECT_NO_SHORTCUTS_INHIBIT) + DEFINE_PROP(bool, opaque, false, WINDOW_RULE_EFFECT_OPAQUE) + DEFINE_PROP(bool, dimAround, false, WINDOW_RULE_EFFECT_DIM_AROUND) + DEFINE_PROP(bool, RGBX, false, WINDOW_RULE_EFFECT_FORCE_RGBX) + DEFINE_PROP(bool, syncFullscreen, true, WINDOW_RULE_EFFECT_SYNC_FULLSCREEN) + DEFINE_PROP(bool, tearing, false, WINDOW_RULE_EFFECT_IMMEDIATE) + DEFINE_PROP(bool, xray, false, WINDOW_RULE_EFFECT_XRAY) + DEFINE_PROP(bool, renderUnfocused, false, WINDOW_RULE_EFFECT_RENDER_UNFOCUSED) + DEFINE_PROP(bool, noFollowMouse, false, WINDOW_RULE_EFFECT_NO_FOLLOW_MOUSE) + DEFINE_PROP(bool, noScreenShare, false, WINDOW_RULE_EFFECT_NO_SCREEN_SHARE) + DEFINE_PROP(bool, noVRR, false, WINDOW_RULE_EFFECT_NO_VRR) + DEFINE_PROP(bool, persistentSize, false, WINDOW_RULE_EFFECT_PERSISTENT_SIZE) + DEFINE_PROP(bool, stayFocused, false, WINDOW_RULE_EFFECT_STAY_FOCUSED) + + DEFINE_PROP(int, idleInhibitMode, false, WINDOW_RULE_EFFECT_IDLE_INHIBIT) + + DEFINE_PROP(Hyprlang::INT, borderSize, {std::string("general:border_size") COMMA sc(0) COMMA std::nullopt}, WINDOW_RULE_EFFECT_BORDER_SIZE) + DEFINE_PROP(Hyprlang::INT, rounding, {std::string("decoration:rounding") COMMA sc(0) COMMA std::nullopt}, WINDOW_RULE_EFFECT_ROUNDING) + + DEFINE_PROP(Hyprlang::FLOAT, roundingPower, {std::string("decoration:rounding_power")}, WINDOW_RULE_EFFECT_ROUNDING_POWER) + DEFINE_PROP(Hyprlang::FLOAT, scrollMouse, {std::string("input:scroll_factor")}, WINDOW_RULE_EFFECT_SCROLL_MOUSE) + DEFINE_PROP(Hyprlang::FLOAT, scrollTouchpad, {std::string("input:touchpad:scroll_factor")}, WINDOW_RULE_EFFECT_SCROLL_TOUCHPAD) + + DEFINE_PROP(std::string, animationStyle, std::string(""), WINDOW_RULE_EFFECT_ANIMATION) + + 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) + + std::vector>> m_dynamicTags; + CTagKeeper m_tagKeeper; + +#undef COMMA +#undef DEFINE_PROP + + private: + PHLWINDOWREF m_window; + + struct SRuleResult { + bool needsRelayout = false; + bool tagsChanged = false; + }; + + SRuleResult applyDynamicRule(const SP& rule); + SRuleResult applyStaticRule(const SP& rule); + }; +}; diff --git a/src/desktop/rule/windowRule/WindowRuleEffectContainer.cpp b/src/desktop/rule/windowRule/WindowRuleEffectContainer.cpp new file mode 100644 index 00000000..660bf871 --- /dev/null +++ b/src/desktop/rule/windowRule/WindowRuleEffectContainer.cpp @@ -0,0 +1,76 @@ +#include "WindowRuleEffectContainer.hpp" + +using namespace Desktop; +using namespace Desktop::Rule; + +// +SP Rule::windowEffects() { + static SP container = makeShared(); + return container; +} + +static const std::vector EFFECT_STRINGS = { + "__internal_none", // + "float", // + "tile", // + "fullscreen", // + "maximize", // + "fullscreen_state", // + "move", // + "size", // + "center", // + "pseudo", // + "monitor", // + "workspace", // + "no_initial_focus", // + "pin", // + "group", // + "suppress_event", // + "content", // + "no_close_for", // + "rounding", // + "rounding_power", // + "persistent_size", // + "animation", // + "border_color", // + "idle_inhibit", // + "opacity", // + "tag", // + "max_size", // + "min_size", // + "border_size", // + "allows_input", // + "dim_around", // + "decorate", // + "focus_on_activate", // + "keep_aspect_ratio", // + "nearest_neighbor", // + "no_anim", // + "no_blur", // + "no_dim", // + "no_focus", // + "no_follow_mouse", // + "no_max_size", // + "no_shadow", // + "no_shortcuts_inhibit", // + "opaque", // + "force_rgbx", // + "sync_fullscreen", // + "immediate", // + "xray", // + "render_unfocused", // + "no_screen_share", // + "no_vrr", // + "scroll_mouse", // + "scroll_touchpad", // + "stay_focused", // + "__internal_last_static", // +}; + +// 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); + +CWindowRuleEffectContainer::CWindowRuleEffectContainer() : IEffectContainer(std::vector{EFFECT_STRINGS}) { + ; +} diff --git a/src/desktop/rule/windowRule/WindowRuleEffectContainer.hpp b/src/desktop/rule/windowRule/WindowRuleEffectContainer.hpp new file mode 100644 index 00000000..0827d462 --- /dev/null +++ b/src/desktop/rule/windowRule/WindowRuleEffectContainer.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include "../effect/EffectContainer.hpp" +#include "../../../helpers/memory/Memory.hpp" + +#pragma once + +namespace Desktop::Rule { + enum eWindowRuleEffect : uint8_t { + WINDOW_RULE_EFFECT_NONE = 0, + + // static + WINDOW_RULE_EFFECT_FLOAT, + WINDOW_RULE_EFFECT_TILE, + WINDOW_RULE_EFFECT_FULLSCREEN, + WINDOW_RULE_EFFECT_MAXIMIZE, + WINDOW_RULE_EFFECT_FULLSCREENSTATE, + WINDOW_RULE_EFFECT_MOVE, + WINDOW_RULE_EFFECT_SIZE, + WINDOW_RULE_EFFECT_CENTER, + WINDOW_RULE_EFFECT_PSEUDO, + WINDOW_RULE_EFFECT_MONITOR, + WINDOW_RULE_EFFECT_WORKSPACE, + WINDOW_RULE_EFFECT_NOINITIALFOCUS, + WINDOW_RULE_EFFECT_PIN, + WINDOW_RULE_EFFECT_GROUP, + WINDOW_RULE_EFFECT_SUPPRESSEVENT, + WINDOW_RULE_EFFECT_CONTENT, + WINDOW_RULE_EFFECT_NOCLOSEFOR, + + // dynamic + WINDOW_RULE_EFFECT_ROUNDING, + WINDOW_RULE_EFFECT_ROUNDING_POWER, + WINDOW_RULE_EFFECT_PERSISTENT_SIZE, + WINDOW_RULE_EFFECT_ANIMATION, + WINDOW_RULE_EFFECT_BORDER_COLOR, + WINDOW_RULE_EFFECT_IDLE_INHIBIT, + WINDOW_RULE_EFFECT_OPACITY, + WINDOW_RULE_EFFECT_TAG, + WINDOW_RULE_EFFECT_MAX_SIZE, + WINDOW_RULE_EFFECT_MIN_SIZE, + WINDOW_RULE_EFFECT_BORDER_SIZE, + WINDOW_RULE_EFFECT_ALLOWS_INPUT, + WINDOW_RULE_EFFECT_DIM_AROUND, + WINDOW_RULE_EFFECT_DECORATE, + WINDOW_RULE_EFFECT_FOCUS_ON_ACTIVATE, + WINDOW_RULE_EFFECT_KEEP_ASPECT_RATIO, + WINDOW_RULE_EFFECT_NEAREST_NEIGHBOR, + WINDOW_RULE_EFFECT_NO_ANIM, + WINDOW_RULE_EFFECT_NO_BLUR, + WINDOW_RULE_EFFECT_NO_DIM, + WINDOW_RULE_EFFECT_NO_FOCUS, + WINDOW_RULE_EFFECT_NO_FOLLOW_MOUSE, + WINDOW_RULE_EFFECT_NO_MAX_SIZE, + WINDOW_RULE_EFFECT_NO_SHADOW, + WINDOW_RULE_EFFECT_NO_SHORTCUTS_INHIBIT, + WINDOW_RULE_EFFECT_OPAQUE, + WINDOW_RULE_EFFECT_FORCE_RGBX, + WINDOW_RULE_EFFECT_SYNC_FULLSCREEN, + WINDOW_RULE_EFFECT_IMMEDIATE, + WINDOW_RULE_EFFECT_XRAY, + WINDOW_RULE_EFFECT_RENDER_UNFOCUSED, + WINDOW_RULE_EFFECT_NO_SCREEN_SHARE, + WINDOW_RULE_EFFECT_NO_VRR, + WINDOW_RULE_EFFECT_SCROLL_MOUSE, + WINDOW_RULE_EFFECT_SCROLL_TOUCHPAD, + WINDOW_RULE_EFFECT_STAY_FOCUSED, + + WINDOW_RULE_EFFECT_LAST_STATIC, + }; + + class CWindowRuleEffectContainer : public IEffectContainer { + public: + CWindowRuleEffectContainer(); + virtual ~CWindowRuleEffectContainer() = default; + }; + + SP windowEffects(); +}; \ No newline at end of file diff --git a/src/desktop/state/FocusState.cpp b/src/desktop/state/FocusState.cpp new file mode 100644 index 00000000..c1298766 --- /dev/null +++ b/src/desktop/state/FocusState.cpp @@ -0,0 +1,304 @@ +#include "FocusState.hpp" +#include "../view/Window.hpp" +#include "../../Compositor.hpp" +#include "../../protocols/XDGShell.hpp" +#include "../../render/Renderer.hpp" +#include "../../managers/EventManager.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() = default; + +struct SFullscreenWorkspaceFocusResult { + PHLWINDOW overrideFocusWindow = nullptr; +}; + +static SFullscreenWorkspaceFocusResult onFullscreenWorkspaceFocusWindow(PHLWINDOW pWindow, bool forceFSCycle) { + const auto FSWINDOW = pWindow->m_workspace->getFullscreenWindow(); + const auto FSMODE = pWindow->m_workspace->m_fullscreenMode; + + if (pWindow == FSWINDOW) + return {}; // no conflict + + 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 {}; + } + + static auto PONFOCUSUNDERFS = CConfigValue("misc:on_focus_under_fullscreen"); + + switch (*PONFOCUSUNDERFS) { + case 0: + // focus the fullscreen window instead + return {.overrideFocusWindow = FSWINDOW}; + case 2: + // undo fs, unless we force a cycle + if (!forceFSCycle) { + g_pCompositor->setWindowFullscreenInternal(FSWINDOW, FSMODE_NONE); + break; + } + [[fallthrough]]; + case 1: + // replace fullscreen + g_pCompositor->setWindowFullscreenInternal(FSWINDOW, FSMODE_NONE); + g_pCompositor->setWindowFullscreenInternal(pWindow, FSMODE); + break; + + default: Log::logger->log(Log::ERR, "Invalid misc:on_focus_under_fullscreen mode: {}", *PONFOCUSUNDERFS); break; + } + + return {}; +} + +void CFocusState::fullWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP surface, bool forceFSCycle) { + if (pWindow) { + if (!pWindow->m_workspace) + return; + + const auto CURRENT_FS_MODE = pWindow->m_workspace->m_hasFullscreenWindow ? pWindow->m_workspace->m_fullscreenMode : FSMODE_NONE; + if (CURRENT_FS_MODE != FSMODE_NONE) { + const auto RESULT = onFullscreenWorkspaceFocusWindow(pWindow, forceFSCycle); + if (RESULT.overrideFocusWindow) + pWindow = RESULT.overrideFocusWindow; + } + } + + 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()) { + Log::logger->log(Log::DEBUG, "Refusing focus to window shadowed by modal dialog"); + return; + } + + rawWindowFocus(pWindow, reason, surface); +} + +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()) { + Log::logger->log(Log::DEBUG, "Refusing a keyboard focus to a window because of a sessionlock"); + return; + } + + if (!g_pInputManager->m_exclusiveLSes.empty()) { + Log::logger->log(Log::DEBUG, "Refusing a keyboard focus to a window because of an exclusive ls"); + return; + } + } + + if (pWindow && pWindow->m_isX11 && pWindow->isX11OverrideRedirect() && !pWindow->m_xwaylandSurface->wantsFocus()) + return; + + // m_target on purpose, this avoids the group + if (pWindow) + g_layoutManager->bringTargetToTop(pWindow->m_target); + + if (!pWindow || !validMapped(pWindow)) { + + if (m_focusWindow.expired() && !pWindow) + return; + + const auto PLASTWINDOW = m_focusWindow.lock(); + m_focusWindow.reset(); + + if (PLASTWINDOW && PLASTWINDOW->m_isMapped) { + PLASTWINDOW->m_ruleApplicator->propertiesChanged(Rule::RULE_PROP_FOCUS); + PLASTWINDOW->updateDecorationValues(); + + g_pXWaylandManager->activateWindow(PLASTWINDOW, false); + } + + g_pSeatManager->setKeyboardFocus(nullptr); + + g_pEventManager->postEvent(SHyprIPCEvent{"activewindow", ","}); + g_pEventManager->postEvent(SHyprIPCEvent{"activewindowv2", ""}); + + Event::bus()->m_events.window.active.emit(nullptr, reason); + + m_focusSurface.reset(); + + g_pInputManager->recheckIdleInhibitorStatus(); + return; + } + + if (pWindow->m_ruleApplicator->noFocus().valueOrDefault()) { + Log::logger->log(Log::DEBUG, "Ignoring focus to nofocus window!"); + return; + } + + if (m_focusWindow.lock() == pWindow && g_pSeatManager->m_state.keyboardFocus == surface && g_pSeatManager->m_state.keyboardFocus) + return; + + if (pWindow->m_pinned) + pWindow->m_workspace = m_focusMonitor->m_activeWorkspace; + + const auto PMONITOR = pWindow->m_monitor.lock(); + + if (!pWindow->m_workspace || !pWindow->m_workspace->isVisible()) { + const auto PWORKSPACE = pWindow->m_workspace; + // This is to fix incorrect feedback on the focus history. + PWORKSPACE->m_lastFocusedWindow = pWindow; + if (PWORKSPACE->m_isSpecialWorkspace) + m_focusMonitor->changeWorkspace(PWORKSPACE, false, true); // if special ws, open on current monitor + else if (PMONITOR) + PMONITOR->changeWorkspace(PWORKSPACE, false, true); + // changeworkspace already calls focusWindow + return; + } + + const auto PLASTWINDOW = m_focusWindow.lock(); + m_focusWindow = pWindow; + + /* If special fallthrough is enabled, this behavior will be disabled, as I have no better idea of nicely tracking which + window focuses are "via keybinds" and which ones aren't. */ + if (PMONITOR && PMONITOR->m_activeSpecialWorkspace && PMONITOR->m_activeSpecialWorkspace != pWindow->m_workspace && !pWindow->m_pinned && !*PSPECIALFALLTHROUGH) + PMONITOR->setSpecialWorkspace(nullptr); + + // we need to make the PLASTWINDOW not equal to m_pLastWindow so that RENDERDATA is correct for an unfocused window + if (PLASTWINDOW && PLASTWINDOW->m_isMapped) { + PLASTWINDOW->m_ruleApplicator->propertiesChanged(Rule::RULE_PROP_FOCUS); + PLASTWINDOW->updateDecorationValues(); + + if (!pWindow->m_isX11 || !pWindow->isX11OverrideRedirect()) + g_pXWaylandManager->activateWindow(PLASTWINDOW, false); + } + + const auto PWINDOWSURFACE = surface ? surface : pWindow->wlSurface()->resource(); + + rawSurfaceFocus(PWINDOWSURFACE, pWindow); + + g_pXWaylandManager->activateWindow(pWindow, true); // sets the m_pLastWindow + + pWindow->m_ruleApplicator->propertiesChanged(Rule::RULE_PROP_FOCUS); + pWindow->onFocusAnimUpdate(); + pWindow->updateDecorationValues(); + + if (pWindow->m_isUrgent) + pWindow->m_isUrgent = false; + + // Send an event + 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()))}); + + Event::bus()->m_events.window.active.emit(pWindow, reason); + + g_pInputManager->recheckIdleInhibitorStatus(); + + if (*PFOLLOWMOUSE == 0) + g_pInputManager->sendMotionEventsToFocused(); + + 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->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)) { + Log::logger->log(Log::DEBUG, "surface {:x} won't receive kb focus because grab rejected it", rc(pSurface.get())); + return; + } + + const auto PLASTSURF = m_focusSurface.lock(); + + // Unfocus last surface if should + if (m_focusSurface && !pWindowOwner) + g_pXWaylandManager->activateSurface(m_focusSurface.lock(), false); + + if (!pSurface) { + g_pSeatManager->setKeyboardFocus(nullptr); + g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = ","}); + g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = ""}); + Event::bus()->m_events.input.keyboard.focus.emit(nullptr); + m_focusSurface.reset(); + return; + } + + if (g_pSeatManager->m_keyboard) + g_pSeatManager->setKeyboardFocus(pSurface); + + if (pWindowOwner) + Log::logger->log(Log::DEBUG, "Set keyboard focus to surface {:x}, with {}", rc(pSurface.get()), pWindowOwner); + else + Log::logger->log(Log::DEBUG, "Set keyboard focus to surface {:x}", rc(pSurface.get())); + + g_pXWaylandManager->activateSurface(pSurface, true); + m_focusSurface = pSurface; + + Event::bus()->m_events.input.keyboard.focus.emit(pSurface); + + const auto SURF = Desktop::View::CWLSurface::fromResource(pSurface); + const auto OLDSURF = Desktop::View::CWLSurface::fromResource(PLASTSURF); + + if (OLDSURF && OLDSURF->constraint()) + OLDSURF->constraint()->deactivate(); + + if (SURF && SURF->constraint()) + SURF->constraint()->activate(); +} + +void CFocusState::rawMonitorFocus(PHLMONITOR pMonitor) { + if (m_focusMonitor == pMonitor) + return; + + if (!pMonitor) { + m_focusMonitor.reset(); + return; + } + + const auto PWORKSPACE = pMonitor->m_activeWorkspace; + + const auto WORKSPACE_ID = PWORKSPACE ? std::to_string(PWORKSPACE->m_id) : std::to_string(WORKSPACE_INVALID); + const auto WORKSPACE_NAME = PWORKSPACE ? PWORKSPACE->m_name : "?"; + + g_pEventManager->postEvent(SHyprIPCEvent{.event = "focusedmon", .data = pMonitor->m_name + "," + WORKSPACE_NAME}); + g_pEventManager->postEvent(SHyprIPCEvent{.event = "focusedmonv2", .data = pMonitor->m_name + "," + WORKSPACE_ID}); + + Event::bus()->m_events.monitor.focused.emit(pMonitor); + m_focusMonitor = pMonitor; +} + +SP CFocusState::surface() { + return m_focusSurface.lock(); +} + +PHLWINDOW CFocusState::window() { + return m_focusWindow.lock(); +} + +PHLMONITOR CFocusState::monitor() { + return m_focusMonitor.lock(); +} + +void CFocusState::resetWindowFocus() { + m_focusWindow.reset(); + m_focusSurface.reset(); +} + +bool Desktop::isHardInputFocusReason(eFocusReason r) { + return r == FOCUS_REASON_NEW_WINDOW || r == FOCUS_REASON_KEYBIND || r == FOCUS_REASON_GHOSTS || r == FOCUS_REASON_CLICK || r == FOCUS_REASON_DESKTOP_STATE_CHANGE; +} diff --git a/src/desktop/state/FocusState.hpp b/src/desktop/state/FocusState.hpp new file mode 100644 index 00000000..71330a3e --- /dev/null +++ b/src/desktop/state/FocusState.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "../DesktopTypes.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(); + ~CFocusState() = default; + + CFocusState(CFocusState&&) = delete; + CFocusState(CFocusState&) = delete; + CFocusState(const CFocusState&) = delete; + + void fullWindowFocus(PHLWINDOW w, eFocusReason reason, SP surface = nullptr, bool forceFSCycle = false); + void rawWindowFocus(PHLWINDOW w, eFocusReason reason, SP surface = nullptr); + void rawSurfaceFocus(SP s, PHLWINDOW pWindowOwner = nullptr); + void rawMonitorFocus(PHLMONITOR m); + + void resetWindowFocus(); + + SP surface(); + PHLWINDOW window(); + PHLMONITOR monitor(); + + private: + WP m_focusSurface; + PHLWINDOWREF m_focusWindow; + PHLMONITORREF m_focusMonitor; + + CHyprSignalListener m_windowOpen, m_windowClose; + }; + + SP focusState(); +}; diff --git a/src/desktop/types/OverridableVar.hpp b/src/desktop/types/OverridableVar.hpp new file mode 100644 index 00000000..cdf27b89 --- /dev/null +++ b/src/desktop/types/OverridableVar.hpp @@ -0,0 +1,159 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "../../config/ConfigValue.hpp" + +namespace Desktop::Types { + + struct SAlphaValue { + float alpha = 1.F; + bool overridden = false; + + float applyAlpha(float a) const { + if (overridden) + return alpha; + else + return alpha * a; + }; + }; + + enum eOverridePriority : uint8_t { + PRIORITY_LAYOUT = 0, + PRIORITY_WORKSPACE_RULE, + PRIORITY_WINDOW_RULE, + PRIORITY_SET_PROP, + + PRIORITY_END, + }; + + template + T clampOptional(T const& value, std::optional const& min, std::optional const& max) { + return std::clamp(value, min.value_or(std::numeric_limits::min()), max.value_or(std::numeric_limits::max())); + } + + template || std::is_same_v || std::is_same_v> + class COverridableVar { + public: + COverridableVar(T const& value, eOverridePriority priority) { + m_values[priority] = value; + } + + COverridableVar(T const& value) : m_defaultValue{value} {} + COverridableVar(T const& value, std::optional const& min, std::optional const& max = std::nullopt) : m_defaultValue{value}, m_minValue{min}, m_maxValue{max} {} + COverridableVar(std::string const& value) + requires(Extended && !std::is_same_v) + : m_configValue(SP>(new CConfigValue(value))) {} + COverridableVar(std::string const& value, std::optional const& min, std::optional const& max = std::nullopt) + requires(Extended && !std::is_same_v) + : m_minValue(min), m_maxValue(max), m_configValue(SP>(new CConfigValue(value))) {} + + COverridableVar() = default; + ~COverridableVar() = default; + + COverridableVar& operator=(COverridableVar const& other) { + // Self-assignment check + if (this == &other) + return *this; + + for (size_t i = 0; i < PRIORITY_END; ++i) { + if constexpr (Extended && !std::is_same_v) + m_values[i] = other.m_values[i].has_value() ? clampOptional(*other.m_values[i], m_minValue, m_maxValue) : other.m_values[i]; + else + m_values[i] = other.m_values[i]; + } + + return *this; + } + + void set(T value, eOverridePriority priority) { + m_values[priority] = value; + } + + void unset(eOverridePriority priority) { + m_values[priority] = std::nullopt; + } + + bool hasValue() const { + return std::ranges::any_of(m_values, [](const auto& e) { return e.has_value(); }); + } + + T value() const { + for (const auto& v : m_values | std::ranges::views::reverse) { + if (v) + return *v; + } + throw std::bad_optional_access(); + } + + T valueOr(T const& other) const { + if (hasValue()) + return value(); + else + return other; + } + + T valueOrDefault() const + requires(Extended && !std::is_same_v) + { + if (hasValue()) + return value(); + else if (m_defaultValue.has_value()) + return m_defaultValue.value(); + else + return **std::any_cast>>(m_configValue); + } + + T valueOrDefault() const + requires(!Extended || std::is_same_v) + { + if (hasValue()) + return value(); + else if (!m_defaultValue.has_value()) + throw std::bad_optional_access(); + else + return m_defaultValue.value(); + } + + eOverridePriority getPriority() const { + for (int i = PRIORITY_END - 1; i >= 0; --i) { + if (m_values[i]) + return sc(i); + } + + throw std::bad_optional_access(); + } + + void increment(T const& other, eOverridePriority priority) { + if constexpr (std::is_same_v) + m_values[priority] = valueOr(false) ^ other; + else + m_values[priority] = clampOptional(valueOrDefault() + other, m_minValue, m_maxValue); + } + + void matchOptional(std::optional const& optValue, eOverridePriority priority) { + if (optValue.has_value()) + m_values[priority] = optValue.value(); + else + unset(priority); + } + + operator std::optional() { + if (hasValue()) + return value(); + else + return std::nullopt; + } + + private: + std::array, PRIORITY_END> m_values; + std::optional m_defaultValue; // used for toggling, so required for bool + std::optional m_minValue; + std::optional m_maxValue; + std::any m_configValue; // only there for select variables + }; + +} \ No newline at end of file diff --git a/src/desktop/view/GlobalViewMethods.cpp b/src/desktop/view/GlobalViewMethods.cpp new file mode 100644 index 00000000..83513e81 --- /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 00000000..551a42da --- /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 00000000..06884cff --- /dev/null +++ b/src/desktop/view/Group.cpp @@ -0,0 +1,351 @@ +#include "Group.hpp" +#include "Window.hpp" + +#include "../../render/decorations/CHyprGroupBarDecoration.hpp" +#include "../../layout/target/WindowGroupTarget.hpp" +#include "../../layout/target/WindowTarget.hpp" +#include "../../layout/target/Target.hpp" +#include "../../layout/space/Space.hpp" +#include "../../layout/LayoutManager.hpp" +#include "../../desktop/state/FocusState.hpp" +#include "../../Compositor.hpp" + +#include + +using namespace Desktop; +using namespace Desktop::View; + +std::vector>& View::groups() { + static std::vector> g; + return g; +} + +SP CGroup::create(std::vector&& windows) { + auto x = SP(new CGroup(std::move(windows))); + x->m_self = x; + x->m_target = Layout::CWindowGroupTarget::create(x); + groups().emplace_back(x); + + x->init(); + + return x; +} + +CGroup::CGroup(std::vector&& windows) : m_windows(std::move(windows)) { + ; +} + +void CGroup::init() { + // for proper group logic: + // - add all windows to us + // - replace the first window with our target + // - remove all window targets from layout + // - apply updates + + // FIXME: what if some windows are grouped? For now we only do 1-window but YNK + for (const auto& w : m_windows) { + RASSERT(!w->m_group, "CGroup: windows cannot contain grouped in init, this will explode"); + w->m_group = m_self.lock(); + } + + g_layoutManager->switchTargets(m_windows.at(0)->m_target, m_target); + + for (const auto& w : m_windows) { + w->m_target->setSpaceGhost(m_target->space()); + } + + for (const auto& w : m_windows) { + applyWindowDecosAndUpdates(w.lock()); + } + + updateWindowVisibility(); +} + +void CGroup::destroy() { + while (true) { + if (m_windows.size() == 1) { + remove(m_windows.at(0).lock()); + break; + } + + remove(m_windows.at(0).lock()); + } +} + +CGroup::~CGroup() { + if (m_target->space()) + m_target->assignToSpace(nullptr); + std::erase_if(groups(), [this](const auto& e) { return !e || e == m_self; }); +} + +bool CGroup::has(PHLWINDOW w) const { + return std::ranges::contains(m_windows, w); +} + +void CGroup::add(PHLWINDOW w) { + static auto INSERT_AFTER_CURRENT = CConfigValue("group:insert_after_current"); + + if (w->m_group) { + if (w->m_group == m_self) + return; + + const auto WINDOWS = w->m_group->windows(); + for (const auto& w : WINDOWS) { + w->m_group->remove(w.lock()); + add(w.lock()); + } + + return; + } + + if (w->layoutTarget()->space()) { + // remove the target from a space if it is in one + g_layoutManager->removeTarget(w->layoutTarget()); + } + + w->m_group = m_self.lock(); + w->m_target->setSpaceGhost(m_target->space()); + w->m_target->setFloating(m_target->floating()); + + if (*INSERT_AFTER_CURRENT) { + m_windows.insert(m_windows.begin() + m_current + 1, w); + m_current++; + } else { + m_windows.emplace_back(w); + m_current = m_windows.size() - 1; + } + + applyWindowDecosAndUpdates(w); + updateWindowVisibility(); + m_target->recalc(); +} + +void CGroup::remove(PHLWINDOW w, Math::eDirection dir) { + std::optional idx; + for (size_t i = 0; i < m_windows.size(); ++i) { + if (m_windows.at(i) == w) { + idx = i; + break; + } + } + + if (!idx) + return; + + if ((m_current >= *idx && idx != 0) || (m_current >= m_windows.size() - 1 && m_current > 0)) + m_current--; + + auto g = m_self.lock(); // keep ref to avoid uaf after w->m_group.reset() + + w->m_group.reset(); + removeWindowDecos(w); + + w->setHidden(false); + + const bool REMOVING_GROUP = m_windows.size() <= 1; + + if (REMOVING_GROUP) { + w->m_target->assignToSpace(nullptr); + g_layoutManager->switchTargets(m_target, w->m_target); + } + + // we do it after the above because switchTargets expects this to be a valid group + m_windows.erase(m_windows.begin() + *idx); + + if (!m_windows.empty()) + updateWindowVisibility(); + + // do this here: otherwise the new current is hidden and workspace rules get wrong data + if (!REMOVING_GROUP) { + std::optional focalPoint; + if (dir != Math::DIRECTION_DEFAULT) { + const auto box = m_target->position(); + switch (dir) { + case Math::DIRECTION_RIGHT: focalPoint = Vector2D(box.x + box.w, box.y + box.h / 2.0); break; + case Math::DIRECTION_LEFT: focalPoint = Vector2D(box.x, box.y + box.h / 2.0); break; + case Math::DIRECTION_DOWN: focalPoint = Vector2D(box.x + box.w / 2.0, box.y + box.h); break; + case Math::DIRECTION_UP: focalPoint = Vector2D(box.x + box.w / 2.0, box.y); break; + default: break; + } + } + w->m_target->assignToSpace(m_target->space(), focalPoint); + } +} + +void CGroup::moveCurrent(bool next) { + size_t idx = m_current; + + if (next) { + idx++; + if (idx >= m_windows.size()) + idx = 0; + } else { + if (idx == 0) + idx = m_windows.size() - 1; + else + idx--; + } + + setCurrent(idx); +} + +void CGroup::setCurrent(size_t idx) { + if (idx == m_current) + return; + + const auto FS_STATE = m_target->fullscreenMode(); + const auto WASFOCUS = Desktop::focusState()->window() == current(); + auto oldWindow = m_windows.at(m_current).lock(); + + if (FS_STATE != FSMODE_NONE) + g_pCompositor->setWindowFullscreenInternal(oldWindow, FSMODE_NONE); + + m_current = std::clamp(idx, sc(0), m_windows.size() - 1); + updateWindowVisibility(); + + auto newWindow = m_windows.at(m_current).lock(); + + if (FS_STATE != FSMODE_NONE) { + g_pCompositor->setWindowFullscreenInternal(newWindow, FS_STATE); + newWindow->m_target->warpPositionSize(); + oldWindow->m_target->setPositionGlobal(newWindow->m_target->position()); // TODO: this is a hack and sucks + } + + if (WASFOCUS) + Desktop::focusState()->rawWindowFocus(current(), FOCUS_REASON_DESKTOP_STATE_CHANGE); +} + +void CGroup::setCurrent(PHLWINDOW w) { + if (w == current()) + return; + + for (size_t i = 0; i < m_windows.size(); ++i) { + if (m_windows.at(i) == w) { + setCurrent(i); + return; + } + } +} + +size_t CGroup::getCurrentIdx() const { + return m_current; +} + +PHLWINDOW CGroup::head() const { + return m_windows.front().lock(); +} + +PHLWINDOW CGroup::tail() const { + return m_windows.back().lock(); +} + +PHLWINDOW CGroup::current() const { + return m_windows.at(m_current).lock(); +} + +PHLWINDOW CGroup::next() const { + return (m_current >= m_windows.size() - 1 ? m_windows.front() : m_windows.at(m_current + 1)).lock(); +} + +PHLWINDOW CGroup::fromIndex(size_t idx) const { + if (idx >= m_windows.size()) + return nullptr; + + return m_windows.at(idx).lock(); +} + +const std::vector& CGroup::windows() const { + return m_windows; +} + +void CGroup::applyWindowDecosAndUpdates(PHLWINDOW x) { + x->addWindowDeco(makeUnique(x)); + + x->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); + x->updateWindowDecos(); + x->updateDecorationValues(); +} + +void CGroup::removeWindowDecos(PHLWINDOW x) { + x->removeWindowDeco(x->getDecorationByType(DECORATION_GROUPBAR)); + + x->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); + x->updateWindowDecos(); + x->updateDecorationValues(); +} + +void CGroup::updateWindowVisibility() { + for (size_t i = 0; i < m_windows.size(); ++i) { + if (i == m_current) { + auto& x = m_windows.at(i); + x->setHidden(false); + x->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); + x->updateWindowDecos(); + x->updateDecorationValues(); + } else + m_windows.at(i)->setHidden(true); + } + + m_target->recalc(); + + m_target->damageEntire(); +} + +size_t CGroup::size() const { + return m_windows.size(); +} + +bool CGroup::locked() const { + return m_locked; +} + +void CGroup::setLocked(bool x) { + m_locked = x; +} + +bool CGroup::denied() const { + return m_deny; +} + +void CGroup::setDenied(bool x) { + m_deny = x; +} + +void CGroup::updateWorkspace(PHLWORKSPACE ws) { + if (!ws) + return; + + for (const auto& w : windows()) { + w->m_monitor = ws->m_monitor; + w->moveToWorkspace(ws); + w->updateToplevel(); + w->updateWindowDecos(); + w->m_target->setSpaceGhost(ws->m_space); + } +} + +void CGroup::swapWithNext() { + const bool HAD_FOCUS = Desktop::focusState()->window() == m_windows.at(m_current); + + size_t idx = m_current + 1 >= m_windows.size() ? 0 : m_current + 1; + std::iter_swap(m_windows.begin() + m_current, m_windows.begin() + idx); + m_current = idx; + + updateWindowVisibility(); + + if (HAD_FOCUS) + Desktop::focusState()->fullWindowFocus(m_windows.at(m_current).lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE); +} + +void CGroup::swapWithLast() { + const bool HAD_FOCUS = Desktop::focusState()->window() == m_windows.at(m_current); + + size_t idx = m_current == 0 ? m_windows.size() - 1 : m_current - 1; + std::iter_swap(m_windows.begin() + m_current, m_windows.begin() + idx); + m_current = idx; + + updateWindowVisibility(); + + if (HAD_FOCUS) + Desktop::focusState()->fullWindowFocus(m_windows.at(m_current).lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE); +} diff --git a/src/desktop/view/Group.hpp b/src/desktop/view/Group.hpp new file mode 100644 index 00000000..36c4baae --- /dev/null +++ b/src/desktop/view/Group.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include "../DesktopTypes.hpp" +#include "../../helpers/math/Direction.hpp" + +#include + +namespace Layout { + class CWindowGroupTarget; +}; + +namespace Desktop::View { + class CGroup { + public: + static SP create(std::vector&& windows); + ~CGroup(); + + bool has(PHLWINDOW w) const; + + void add(PHLWINDOW w); + void remove(PHLWINDOW w, Math::eDirection dir = Math::DIRECTION_DEFAULT); + void moveCurrent(bool next); + void setCurrent(size_t idx); + void setCurrent(PHLWINDOW w); + size_t getCurrentIdx() const; + size_t size() const; + void destroy(); + void updateWorkspace(PHLWORKSPACE); + + void swapWithNext(); + void swapWithLast(); + + PHLWINDOW head() const; + PHLWINDOW tail() const; + PHLWINDOW current() const; + PHLWINDOW next() const; + + PHLWINDOW fromIndex(size_t idx) const; + + bool locked() const; + void setLocked(bool x); + + bool denied() const; + void setDenied(bool x); + + const std::vector& windows() const; + + SP m_target; + + private: + CGroup(std::vector&& windows); + + void applyWindowDecosAndUpdates(PHLWINDOW x); + void removeWindowDecos(PHLWINDOW x); + void init(); + void updateWindowVisibility(); + + WP m_self; + + std::vector m_windows; + + bool m_locked = false; + bool m_deny = false; + + size_t m_current = 0; + }; + + std::vector>& groups(); +}; \ No newline at end of file diff --git a/src/desktop/LayerSurface.cpp b/src/desktop/view/LayerSurface.cpp similarity index 61% rename from src/desktop/LayerSurface.cpp rename to src/desktop/view/LayerSurface.cpp index 6278078d..a10c9d4d 100644 --- a/src/desktop/LayerSurface.cpp +++ b/src/desktop/view/LayerSurface.cpp @@ -1,43 +1,33 @@ #include "LayerSurface.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/ConfigManager.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() ? g_pCompositor->m_lastMonitor.lock() : g_pCompositor->getMonitorFromName(resource->m_monitor); + 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); - if (!pMonitor) { - Debug::log(ERR, "New LS has no monitor??"); - return pLS; - } - - if (pMonitor->m_mirrorOf) - pMonitor = g_pCompositor->m_monitors.front(); - - pLS->m_self = pLS; - - pLS->m_namespace = resource->m_layerNamespace; - - pLS->m_layer = resource->m_current.layer; - pLS->m_popupHead = CPopup::create(pLS); - pLS->m_monitor = pMonitor; - pMonitor->m_layerSurfaceLayers[resource->m_current.layer].emplace_back(pLS); - - pLS->m_forceBlur = g_pConfigManager->shouldBlurLS(pLS->m_namespace); + pLS->m_ruleApplicator = makeUnique(pLS); + pLS->m_self = pLS; + pLS->m_namespace = resource->m_layerNamespace; + pLS->m_layer = std::clamp(resource->m_current.layer, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY); + pLS->m_popupHead = CPopup::create(pLS); g_pAnimationManager->createAnimation(0.f, pLS->m_alpha, g_pConfigManager->getAnimationPropertyConfig("fadeLayersIn"), pLS, AVARDAMAGE_ENTIRE); g_pAnimationManager->createAnimation(Vector2D(0, 0), pLS->m_realPosition, g_pConfigManager->getAnimationPropertyConfig("layersIn"), pLS, AVARDAMAGE_ENTIRE); @@ -47,34 +37,51 @@ PHLLS CLayerSurface::create(SP resource) { 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); + if (!pMonitor) { + Log::logger->log(Log::DEBUG, "LayerSurface {:x} (namespace {} layer {}) created on NO MONITOR ?!", rc(resource.get()), resource->m_layerNamespace, + sc(pLS->m_layer)); + + return pLS; + } + + if (pMonitor->m_mirrorOf) + pMonitor = g_pCompositor->m_monitors.front(); + + pLS->m_monitor = pMonitor; + pMonitor->m_layerSurfaceLayers[resource->m_current.layer].emplace_back(pLS); + + Log::logger->log(Log::DEBUG, "LayerSurface {:x} (namespace {} layer {}) created on monitor {}", rc(resource.get()), resource->m_layerNamespace, + sc(pLS->m_layer), pMonitor->m_name); 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_dimAround && m_monitor) + if (m_ruleApplicator->dimAround().valueOrDefault() && m_monitor) g_pHyprRenderer->damageMonitor(m_monitor.lock()); }); } -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(); + if (m_wlSurface) + m_wlSurface->unassign(); g_pHyprRenderer->makeEGLCurrent(); std::erase_if(g_pHyprOpenGL->m_layerFramebuffers, [&](const auto& other) { return other.first.expired() || other.first.lock() == m_self.lock(); }); @@ -85,20 +92,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; @@ -122,8 +152,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(); @@ -132,10 +162,13 @@ 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); m_layerSurface->m_surface->map(); @@ -149,13 +182,11 @@ void CLayerSurface::onMap() { if (!PMONITOR) return; - applyRules(); - PMONITOR->m_scheduledRecalc = true; 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; @@ -169,14 +200,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(); - g_pCompositor->focusSurface(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; } @@ -191,22 +222,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()); @@ -237,18 +268,19 @@ 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; // refocus if needed // vvvvvvvvvvvvv if there is a last focus and the last focus is not keyboard focusable, fallback to window - if (WASLASTFOCUS || (g_pCompositor->m_lastFocus && g_pCompositor->m_lastFocus->m_hlSurface && !g_pCompositor->m_lastFocus->m_hlSurface->keyboardFocusable())) { + if (WASLASTFOCUS || + (Desktop::focusState()->surface() && Desktop::focusState()->surface()->m_hlSurface && !Desktop::focusState()->surface()->m_hlSurface->keyboardFocusable())) { if (!g_pInputManager->refocusLastWindow(PMONITOR)) g_pInputManager->refocus(); - } else if (g_pCompositor->m_lastFocus && g_pCompositor->m_lastFocus != m_surface->resource()) - g_pSeatManager->setKeyboardFocus(g_pCompositor->m_lastFocus.lock()); + } 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}; g_pHyprRenderer->damageBox(geomFixed); @@ -291,31 +323,21 @@ 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 @@ -357,8 +379,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); } @@ -374,7 +396,7 @@ void CLayerSurface::onCommit() { if (WASLASTFOCUS && m_layerSurface->m_current.interactivity == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE) { // moveMouseUnified won't focus non interactive layers but it won't unfocus them either, // so unfocus the surface here. - g_pCompositor->focusSurface(nullptr); + Desktop::focusState()->rawSurfaceFocus(nullptr); g_pInputManager->refocusLastWindow(m_monitor.lock()); } else if (WASLASTFOCUS && WASEXCLUSIVE && m_layerSurface->m_current.interactivity == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND) { g_pInputManager->simulateMouseMovement(); @@ -382,97 +404,20 @@ void CLayerSurface::onCommit() { // if now exclusive and not previously g_pSeatManager->setGrab(nullptr); g_pInputManager->releaseAllMouseButtons(); - g_pCompositor->focusSurface(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); -} - -void CLayerSurface::applyRules() { - m_noAnimations = false; - m_forceBlur = false; - m_ignoreAlpha = false; - m_dimAround = false; - m_noScreenShare = false; - m_ignoreAlphaValue = 0.f; - m_xray = -1; - m_animationStyle.reset(); - - for (auto const& rule : g_pConfigManager->getMatchingRules(m_self.lock())) { - switch (rule->m_ruleType) { - case CLayerRule::RULE_NOANIM: { - m_noAnimations = true; - break; - } - case CLayerRule::RULE_BLUR: { - m_forceBlur = true; - break; - } - case CLayerRule::RULE_BLURPOPUPS: { - m_forceBlurPopups = true; - break; - } - case CLayerRule::RULE_IGNOREALPHA: - case CLayerRule::RULE_IGNOREZERO: { - const auto FIRST_SPACE_POS = rule->m_rule.find_first_of(' '); - std::string alphaValue = ""; - if (FIRST_SPACE_POS != std::string::npos) - alphaValue = rule->m_rule.substr(FIRST_SPACE_POS + 1); - - try { - m_ignoreAlpha = true; - if (!alphaValue.empty()) - m_ignoreAlphaValue = std::stof(alphaValue); - } catch (...) { Debug::log(ERR, "Invalid value passed to ignoreAlpha"); } - break; - } - case CLayerRule::RULE_DIMAROUND: { - m_dimAround = true; - break; - } - case CLayerRule::RULE_NOSCREENSHARE: { - m_noScreenShare = true; - break; - } - case CLayerRule::RULE_XRAY: { - CVarList vars{rule->m_rule, 0, ' '}; - m_xray = configStringToInt(vars[1]).value_or(false); - - break; - } - case CLayerRule::RULE_ANIMATION: { - CVarList vars{rule->m_rule, 2, 's'}; - m_animationStyle = vars[1]; - break; - } - case CLayerRule::RULE_ORDER: { - CVarList vars{rule->m_rule, 2, 's'}; - try { - m_order = std::stoi(vars[1]); - } catch (...) { Debug::log(ERR, "Invalid value passed to order"); } - break; - } - case CLayerRule::RULE_ABOVELOCK: { - m_aboveLockscreen = true; - - CVarList vars{rule->m_rule, 0, ' '}; - m_aboveLockscreenInteractable = configStringToInt(vars[1]).value_or(false); - - break; - } - default: break; - } - } + g_pCompositor->setPreferredScaleForSurface(m_wlSurface->resource(), PMONITOR->m_scale); + g_pCompositor->setPreferredTransformForSurface(m_wlSurface->resource(), PMONITOR->m_transform); } bool CLayerSurface::isFadedOut() { @@ -487,7 +432,7 @@ int CLayerSurface::popupsCount() { return 0; int no = -1; // we have one dummy - m_popupHead->breadthfirst([](WP p, void* data) { *sc(data) += 1; }, &no); + m_popupHead->breadthfirst([](WP p, void* data) { *sc(data) += 1; }, &no); return no; } @@ -499,10 +444,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 00000000..5faa9e5a --- /dev/null +++ b/src/desktop/view/LayerSurface.hpp @@ -0,0 +1,106 @@ +#pragma once + +#include +#include "../../defines.hpp" +#include "WLSurface.hpp" +#include "View.hpp" +#include "../rule/layerRule/LayerRuleApplicator.hpp" +#include "../../helpers/AnimatedVariable.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; + + 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 69% rename from src/desktop/Popup.cpp rename to src/desktop/view/Popup.cpp index c3794c6c..f6f681d5 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/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 "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,11 +49,56 @@ 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); @@ -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,7 +142,7 @@ 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)); + Log::logger->log(Log::DEBUG, "New popup at {:x}", rc(this)); } void CPopup::onDestroy() { @@ -103,8 +155,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,7 +169,7 @@ 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; }); @@ -142,7 +199,7 @@ void CPopup::onMap() { //unconstrain(); sendScale(); - m_resource->m_surface->m_surface->enter(PMONITOR->m_self.lock()); + m_wlSurface->resource()->breadthfirst([PMONITOR](SP s, const Vector2D& offset, void* d) { s->enter(PMONITOR->m_self.lock()); }, nullptr); if (!m_layerOwner.expired() && m_layerOwner->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP) g_pHyprOpenGL->markBlurDirtyForMonitor(g_pCompositor->getMonitorFromID(m_layerOwner->m_layer)); @@ -151,7 +208,7 @@ void CPopup::onMap() { 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 +216,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), @@ -219,7 +276,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; } @@ -234,7 +291,7 @@ void CPopup::onCommit(bool ignoreSiblings) { 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; } @@ -266,7 +323,7 @@ void CPopup::onCommit(bool ignoreSiblings) { } 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; @@ -282,18 +339,17 @@ 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() { +Vector2D CPopup::coordsRelativeToParent() const { Vector2D offset; if (!m_resource) @@ -304,7 +360,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 +369,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()) @@ -347,41 +403,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 +440,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,18 +448,18 @@ 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::at(const Vector2D& globalCoords, bool allowsInput) { + std::vector> popups; + breadthfirst([&popups](SP popup, void* data) { popups.push_back(popup); }, &popups); for (auto const& p : popups | std::views::reverse) { if (!p->m_resource || !p->m_mapped) @@ -427,7 +478,7 @@ WP CPopup::at(const Vector2D& globalCoords, bool allowsInput) { if (BOX.containsPoint(globalCoords)) 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()); + 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)) return p; } @@ -440,7 +491,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 00000000..1654fc60 --- /dev/null +++ b/src/desktop/view/Popup.hpp @@ -0,0 +1,106 @@ +#pragma once + +#include +#include "Subsurface.hpp" +#include "View.hpp" +#include "../../helpers/signal/Signal.hpp" +#include "../../helpers/memory/Memory.hpp" +#include "../../helpers/AnimatedVariable.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; + 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); + + // + WP m_self; + bool m_mapped = false; + + // fade in-out + PHLANIMVAR m_alpha; + bool m_fadingOut = false; + + 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; + + // + 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; + 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 00000000..a4a5b78b --- /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 00000000..c6141fb2 --- /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 69% rename from src/desktop/Subsurface.cpp rename to src/desktop/view/Subsurface.cpp index ba7340f1..c601c00e 100644 --- a/src/desktop/Subsurface.cpp +++ b/src/desktop/view/Subsurface.cpp @@ -1,55 +1,101 @@ #include "Subsurface.hpp" -#include "../events/Events.hpp" -#include "../Compositor.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(); }); @@ -59,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); } @@ -78,14 +124,14 @@ 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() { for (auto const& n : m_children) { const auto COORDS = n->coordsGlobal(); - g_pHyprRenderer->damageSurface(n->m_wlSurface->resource(), COORDS.x, COORDS.y); + g_pHyprRenderer->damageSurface(n->wlSurface()->resource(), COORDS.x, COORDS.y); } } @@ -96,7 +142,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; } @@ -104,7 +150,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(); @@ -163,7 +209,7 @@ void CSubsurface::onMap() { void CSubsurface::onUnmap() { damageLastArea(); - if (m_wlSurface->resource() == g_pCompositor->m_lastFocus) + if (m_wlSurface->resource() == Desktop::focusState()->surface()) g_pInputManager->releaseAllMouseButtons(); g_pInputManager->simulateMouseMovement(); @@ -186,13 +232,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()) @@ -214,14 +260,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 00000000..ab74f48c --- /dev/null +++ b/src/desktop/view/Subsurface.hpp @@ -0,0 +1,77 @@ +#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(); + + 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 = {}; + + // 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 00000000..17a10c64 --- /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 00000000..4d777c36 --- /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 a7b654f0..ae8a22e2 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 00000000..3c5e3a38 --- /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 00000000..abf4da95 --- /dev/null +++ b/src/desktop/view/Window.cpp @@ -0,0 +1,2508 @@ +#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 "../history/WindowHistoryTracker.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 "../../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 + +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, 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_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, 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_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(); + + 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; }); +} + +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) { + CBox surfaceExtents = {0, 0, 0, 0}; + // TODO: this could be better, perhaps make a getFullWindowRegion? + m_popupHead->breadthfirst( + [](WP popup, void* data) { + if (!popup->wlSurface() || !popup->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() 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 (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_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) + m_events.resize.emit(); + }); + + m_realPosition->setUpdateCallback([this](auto) { + if (m_isMapped && m_monitor != m_prevMonitor) { + m_prevMonitor = m_monitor; + m_events.monitorChanged.emit(); + } + }); + + m_movingFromWorkspaceAlpha->setValueAndWarp(1.F); + + m_reportedSize = m_pendingReportedSize; + 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) + 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 ? g_pConfigManager->getWorkspaceRuleFor(PWORKSPACE) : SWorkspaceRule{}; + updateWindowData(WORKSPACERULE); +} + +void CWindow::updateWindowData(const SWorkspaceRule& workspaceRule) { + if (workspaceRule.noBorder.value_or(false)) + m_ruleApplicator->borderSize().matchOptional(std::optional(0), Desktop::Types::PRIORITY_WORKSPACE_RULE); + else if (workspaceRule.borderSize) + m_ruleApplicator->borderSize().matchOptional(workspaceRule.borderSize, Desktop::Types::PRIORITY_WORKSPACE_RULE); + else + m_ruleApplicator->borderSize().matchOptional(std::nullopt, Desktop::Types::PRIORITY_WORKSPACE_RULE); + m_ruleApplicator->decorate().matchOptional(workspaceRule.decorate, 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() const { + 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_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; +} + +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()); +} + +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->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(); + + 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() { + 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(); + + const bool GROUPLOCKED = m_group ? m_group->locked() : false; + if (m_self == Desktop::focusState()->window()) { + const auto* const ACTIVECOLOR = !m_group ? (!(m_groupRules & GROUP_DENY) ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); + setBorderColor(m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR)); + } else { + const auto* const INACTIVECOLOR = !m_group ? (!(m_groupRules & GROUP_DENY) ? INACTIVECOL : NOGROUPINACTIVECOL) : (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); + setBorderColor(m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR)); + } + + // opacity + 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(g_pConfigManager->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; + + m_ruleApplicator->readStaticRules(); + { + 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()); + } + + // 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); + } + + updateWindowData(); + + // 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}); + + 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); + g_pConfigManager->storeFloatingSize(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) + 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 LOGICALPOS = g_pXWaylandManager->xwaylandToWaylandCoords(m_xwaylandSurface->m_geometry.pos()); + + const auto PMONITOR = m_monitor.lock(); + const auto XWLSCALE = (*PXWLFORCESCALEZERO && PMONITOR) ? PMONITOR->m_scale : 1.0; + const auto LOGICALGEOSIZE = m_xwaylandSurface->m_geometry.size() / XWLSCALE; + + if (abs(std::floor(POS.x) - LOGICALPOS.x) > 2 || abs(std::floor(POS.y) - LOGICALPOS.y) > 2 || abs(std::floor(SIZ.x) - LOGICALGEOSIZE.x) > 2 || + abs(std::floor(SIZ.y) - LOGICALGEOSIZE.y) > 2) { + 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 00000000..d689ae3f --- /dev/null +++ b/src/desktop/view/Window.hpp @@ -0,0 +1,453 @@ +#pragma once + +#include +#include +#include + +#include "View.hpp" +#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; +struct SWorkspaceRule; + +class IWindowTransformer; + +namespace Layout { + class ITarget; + class CWindowTarget; +} + +namespace Desktop { + enum eFocusReason : uint8_t; +} + +namespace Desktop::View { + + class CGroup; + + enum eGroupRules : uint8_t { + // effective only during first map, except for _ALWAYS variant + GROUP_NONE = 0, + 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 : 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, + }; + + 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 + 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 + 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; + + // 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 SWorkspaceRule&); + 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 b732ba08..ae6df1f5 100644 --- a/src/devices/IKeyboard.cpp +++ b/src/devices/IKeyboard.cpp @@ -56,7 +56,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 +72,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); 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); @@ -99,8 +99,8 @@ void IKeyboard::setKeymap(const SStringRuleNames& rules) { g_pConfigManager->addParseError("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 = ""; @@ -129,12 +129,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 +145,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 +162,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 +174,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 +187,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 +206,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 +221,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 +241,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 +264,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/event/EventBus.cpp b/src/event/EventBus.cpp new file mode 100644 index 00000000..f06c3984 --- /dev/null +++ b/src/event/EventBus.cpp @@ -0,0 +1,8 @@ +#include "EventBus.hpp" + +using namespace Event; + +UP& Event::bus() { + static UP p = makeUnique(); + return p; +} diff --git a/src/event/EventBus.hpp b/src/event/EventBus.hpp new file mode 100644 index 00000000..a30288f0 --- /dev/null +++ b/src/event/EventBus.hpp @@ -0,0 +1,144 @@ +#pragma once + +#include "../helpers/memory/Memory.hpp" +#include "../helpers/signal/Signal.hpp" +#include "../helpers/math/Math.hpp" + +#include "../devices/IPointer.hpp" +#include "../devices/IKeyboard.hpp" +#include "../devices/Tablet.hpp" +#include "../devices/ITouch.hpp" + +#include "../desktop/DesktopTypes.hpp" + +#include "../SharedDefs.hpp" + +namespace Desktop { + enum eFocusReason : uint8_t; +} +namespace Event { + struct SCallbackInfo { + bool cancelled = false; /* on cancellable events, will cancel the event. */ + }; + + class CEventBus { + public: + CEventBus() = default; + ~CEventBus() = default; + + template + using Event = CSignalT; + + template + using Cancellable = CSignalT; + + struct { + Event<> ready; + Event<> tick; + + struct { + Event open; + Event openEarly; + Event destroy; + Event close; + Event kill; + Event active; + Event urgent; + Event title; + Event class_; + Event pin; + Event fullscreen; + Event updateRules; + Event moveToWorkspace; + } window; + + struct { + Event opened; + Event closed; + Event updateRules; + } layer; + + struct { + struct { + Cancellable move; + Cancellable button; + Cancellable axis; + } mouse; + + struct { + Cancellable key; + Event, const std::string&> layout; + Event> focus; + } keyboard; + + struct { + Cancellable axis; + Cancellable button; + Cancellable proximity; + Cancellable tip; + } tablet; + + struct { + Cancellable cancel; + Cancellable down; + Cancellable up; + Cancellable motion; + } touch; + } input; + + struct { + Event pre; + Event stage; + } render; + + struct { + Event state; + } screenshare; + + struct { + struct { + Cancellable begin; + Cancellable end; + Cancellable update; + } swipe; + + struct { + Cancellable begin; + Cancellable end; + Cancellable update; + } pinch; + } gesture; + + struct { + Event newMon; + Event preAdded; + Event added; + Event preRemoved; + Event removed; + Event preCommit; + Event focused; + + Event<> layoutChanged; + } monitor; + + struct { + Event moveToMonitor; + Event active; + Event created; + Event removed; + } workspace; + + struct { + Event<> preReload; + Event<> reloaded; + } config; + + struct { + Event submap; + } keybinds; + + } m_events; + }; + + UP& bus(); +}; diff --git a/src/events/Events.hpp b/src/events/Events.hpp deleted file mode 100644 index 2e564944..00000000 --- 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 16f71534..00000000 --- a/src/events/Windows.cpp +++ /dev/null @@ -1,1026 +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 "../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:new_window_takes_over_fullscreen"); - static auto PINITIALWSTRACKING = CConfigValue("misc:initial_workspace_tracking"); - - auto PMONITOR = g_pCompositor->m_lastMonitor.lock(); - if (!g_pCompositor->m_lastMonitor) { - g_pCompositor->setActiveMonitor(g_pCompositor->getMonitorFromVector({})); - PMONITOR = g_pCompositor->m_lastMonitor.lock(); - } - 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 - PWINDOW->m_matchedRules = g_pConfigManager->getMatchingRules(PWINDOW, false); - 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; - - for (auto const& r : PWINDOW->m_matchedRules) { - switch (r->m_ruleType) { - case CWindowRule::RULE_MONITOR: { - try { - const auto MONITORSTR = trim(r->m_rule.substr(r->m_rule.find(' '))); - - if (MONITORSTR == "unset") - PWINDOW->m_monitor = PMONITOR; - else { - const auto MONITOR = g_pCompositor->getMonitorFromString(MONITORSTR); - - if (MONITOR) - PWINDOW->m_monitor = MONITOR; - else { - Debug::log(ERR, "No monitor in monitor {} rule", MONITORSTR); - continue; - } - } - - 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; - } catch (std::exception& e) { Debug::log(ERR, "Rule monitor failed, rule: {} -> {} | err: {}", r->m_rule, r->m_value, e.what()); } - break; - } - case CWindowRule::RULE_WORKSPACE: { - // check if it isn't unset - const auto WORKSPACERQ = r->m_rule.substr(r->m_rule.find_first_of(' ') + 1); - - 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, r->m_value); - requestedFSMonitor = MONITOR_INVALID; - break; - } - case CWindowRule::RULE_FLOAT: { - PWINDOW->m_isFloating = true; - break; - } - case CWindowRule::RULE_TILE: { - PWINDOW->m_isFloating = false; - break; - } - case CWindowRule::RULE_PSEUDO: { - PWINDOW->m_isPseudotiled = true; - break; - } - case CWindowRule::RULE_NOINITIALFOCUS: { - PWINDOW->m_noInitialFocus = true; - break; - } - case CWindowRule::RULE_FULLSCREENSTATE: { - const auto ARGS = CVarList(r->m_rule.substr(r->m_rule.find_first_of(' ') + 1), 2, ' '); - int internalMode, clientMode; - try { - internalMode = std::stoi(ARGS[0]); - } catch (std::exception& e) { internalMode = 0; } - try { - clientMode = std::stoi(ARGS[1]); - } catch (std::exception& e) { clientMode = 0; } - requestedFSState = SFullscreenState{.internal = sc(internalMode), .client = sc(clientMode)}; - break; - } - case CWindowRule::RULE_SUPPRESSEVENT: { - CVarList vars(r->m_rule, 0, 's', true); - for (size_t i = 1; i < vars.size(); ++i) { - if (vars[i] == "fullscreen") - PWINDOW->m_suppressedEvents |= SUPPRESS_FULLSCREEN; - else if (vars[i] == "maximize") - PWINDOW->m_suppressedEvents |= SUPPRESS_MAXIMIZE; - else if (vars[i] == "activate") - PWINDOW->m_suppressedEvents |= SUPPRESS_ACTIVATE; - else if (vars[i] == "activatefocus") - PWINDOW->m_suppressedEvents |= SUPPRESS_ACTIVATE_FOCUSONLY; - else if (vars[i] == "fullscreenoutput") - PWINDOW->m_suppressedEvents |= SUPPRESS_FULLSCREEN_OUTPUT; - else - Debug::log(ERR, "Error while parsing suppressevent windowrule: unknown event type {}", vars[i]); - } - break; - } - case CWindowRule::RULE_PIN: { - PWINDOW->m_pinned = true; - break; - } - case CWindowRule::RULE_FULLSCREEN: { - requestedInternalFSMode = FSMODE_FULLSCREEN; - break; - } - case CWindowRule::RULE_MAXIMIZE: { - requestedInternalFSMode = FSMODE_MAXIMIZED; - break; - } - case CWindowRule::RULE_STAYFOCUSED: { - PWINDOW->m_stayFocused = true; - break; - } - case CWindowRule::RULE_GROUP: { - if (PWINDOW->m_groupRules & GROUP_OVERRIDE) - continue; - - // `group` is a shorthand of `group set` - if (trim(r->m_rule) == "group") { - PWINDOW->m_groupRules |= GROUP_SET; - continue; - } - - CVarList vars(r->m_rule, 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; - } - break; - } - case CWindowRule::RULE_CONTENT: { - const CVarList VARS(r->m_rule, 0, ' '); - try { - PWINDOW->setContentType(NContentType::fromString(VARS[1])); - } catch (std::exception& e) { Debug::log(ERR, "Rule \"{}\" failed with: {}", r->m_rule, e.what()); } - break; - } - case CWindowRule::RULE_NOCLOSEFOR: { - const CVarList VARS(r->m_rule, 0, ' '); - try { - PWINDOW->m_closeableSince = Time::steadyNow() + std::chrono::milliseconds(std::stoull(VARS[1])); - } catch (std::exception& e) { Debug::log(ERR, "Rule \"{}\" failed with: {}", r->m_rule, e.what()); } - break; - } - default: break; - } - - PWINDOW->applyDynamicRule(r); - } - - // 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; - - CVarList WORKSPACEARGS = CVarList(requestedWorkspace, 0, ' '); - - 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 = g_pCompositor->m_lastMonitor.lock(); - } - - 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)}); - - if (PWINDOW->m_isFloating) { - g_pLayoutManager->getCurrentLayout()->onWindowCreated(PWINDOW); - PWINDOW->m_createdOverFullscreen = true; - - // size and move rules - for (auto const& r : PWINDOW->m_matchedRules) { - switch (r->m_ruleType) { - case CWindowRule::RULE_SIZE: { - try { - auto stringToFloatClamp = [](const std::string& VALUE, const float CURR, const float REL) { - if (VALUE.starts_with('<')) - return std::min(CURR, stringToPercentage(VALUE.substr(1, VALUE.length() - 1), REL)); - else if (VALUE.starts_with('>')) - return std::max(CURR, stringToPercentage(VALUE.substr(1, VALUE.length() - 1), REL)); - - return stringToPercentage(VALUE, REL); - }; - - const auto VALUE = r->m_rule.substr(r->m_rule.find(' ') + 1); - const auto SIZEXSTR = VALUE.substr(0, VALUE.find(' ')); - const auto SIZEYSTR = VALUE.substr(VALUE.find(' ') + 1); - - const auto MAXSIZE = PWINDOW->requestedMaxSize(); - - const float SIZEX = SIZEXSTR == "max" ? std::clamp(MAXSIZE.x, MIN_WINDOW_SIZE, PMONITOR->m_size.x) : - stringToFloatClamp(SIZEXSTR, PWINDOW->m_realSize->goal().x, PMONITOR->m_size.x); - - const float SIZEY = SIZEYSTR == "max" ? std::clamp(MAXSIZE.y, MIN_WINDOW_SIZE, PMONITOR->m_size.y) : - stringToFloatClamp(SIZEYSTR, PWINDOW->m_realSize->goal().y, PMONITOR->m_size.y); - - Debug::log(LOG, "Rule size, applying to {}", PWINDOW); - - PWINDOW->clampWindowSize(Vector2D{SIZEXSTR.starts_with("<") ? 0 : SIZEX, SIZEYSTR.starts_with("<") ? 0 : SIZEY}, Vector2D{SIZEX, SIZEY}); - - PWINDOW->setHidden(false); - } catch (...) { Debug::log(LOG, "Rule size failed, rule: {} -> {}", r->m_rule, r->m_value); } - break; - } - case CWindowRule::RULE_MOVE: { - try { - auto value = r->m_rule.substr(r->m_rule.find(' ') + 1); - - const bool ONSCREEN = value.starts_with("onscreen"); - - if (ONSCREEN) - value = value.substr(value.find_first_of(' ') + 1); - - const bool CURSOR = value.starts_with("cursor"); - - if (CURSOR) - value = value.substr(value.find_first_of(' ') + 1); - - const auto POSXSTR = value.substr(0, value.find(' ')); - const auto POSYSTR = value.substr(value.find(' ') + 1); - - int posX = 0; - int posY = 0; - - if (POSXSTR.starts_with("100%-")) { - const bool subtractWindow = POSXSTR.starts_with("100%-w-"); - const auto POSXRAW = (subtractWindow) ? POSXSTR.substr(7) : POSXSTR.substr(5); - posX = - PMONITOR->m_size.x - (!POSXRAW.contains('%') ? std::stoi(POSXRAW) : std::stof(POSXRAW.substr(0, POSXRAW.length() - 1)) * 0.01 * PMONITOR->m_size.x); - - if (subtractWindow) - posX -= PWINDOW->m_realSize->goal().x; - - if (CURSOR) - Debug::log(ERR, "Cursor is not compatible with 100%-, ignoring cursor!"); - } else if (!CURSOR) { - posX = !POSXSTR.contains('%') ? std::stoi(POSXSTR) : std::stof(POSXSTR.substr(0, POSXSTR.length() - 1)) * 0.01 * PMONITOR->m_size.x; - } else { - // cursor - if (POSXSTR == "cursor") { - posX = g_pInputManager->getMouseCoordsInternal().x - PMONITOR->m_position.x; - } else { - posX = g_pInputManager->getMouseCoordsInternal().x - PMONITOR->m_position.x + - (!POSXSTR.contains('%') ? std::stoi(POSXSTR) : std::stof(POSXSTR.substr(0, POSXSTR.length() - 1)) * 0.01 * PWINDOW->m_realSize->goal().x); - } - } - - if (POSYSTR.starts_with("100%-")) { - const bool subtractWindow = POSYSTR.starts_with("100%-w-"); - const auto POSYRAW = (subtractWindow) ? POSYSTR.substr(7) : POSYSTR.substr(5); - posY = - PMONITOR->m_size.y - (!POSYRAW.contains('%') ? std::stoi(POSYRAW) : std::stof(POSYRAW.substr(0, POSYRAW.length() - 1)) * 0.01 * PMONITOR->m_size.y); - - if (subtractWindow) - posY -= PWINDOW->m_realSize->goal().y; - - if (CURSOR) - Debug::log(ERR, "Cursor is not compatible with 100%-, ignoring cursor!"); - } else if (!CURSOR) { - posY = !POSYSTR.contains('%') ? std::stoi(POSYSTR) : std::stof(POSYSTR.substr(0, POSYSTR.length() - 1)) * 0.01 * PMONITOR->m_size.y; - } else { - // cursor - if (POSYSTR == "cursor") { - posY = g_pInputManager->getMouseCoordsInternal().y - PMONITOR->m_position.y; - } else { - posY = g_pInputManager->getMouseCoordsInternal().y - PMONITOR->m_position.y + - (!POSYSTR.contains('%') ? std::stoi(POSYSTR) : std::stof(POSYSTR.substr(0, POSYSTR.length() - 1)) * 0.01 * PWINDOW->m_realSize->goal().y); - } - } - - if (ONSCREEN) { - int borderSize = PWINDOW->getRealBorderSize(); - - posX = std::clamp(posX, sc(PMONITOR->m_reservedTopLeft.x + borderSize), - std::max(sc(PMONITOR->m_size.x - PMONITOR->m_reservedBottomRight.x - PWINDOW->m_realSize->goal().x - borderSize), - sc(PMONITOR->m_reservedTopLeft.x + borderSize + 1))); - - posY = std::clamp(posY, sc(PMONITOR->m_reservedTopLeft.y + borderSize), - std::max(sc(PMONITOR->m_size.y - PMONITOR->m_reservedBottomRight.y - PWINDOW->m_realSize->goal().y - borderSize), - sc(PMONITOR->m_reservedTopLeft.y + borderSize + 1))); - } - - Debug::log(LOG, "Rule move, applying to {}", PWINDOW); - - *PWINDOW->m_realPosition = Vector2D(posX, posY) + PMONITOR->m_position; - - PWINDOW->setHidden(false); - } catch (...) { Debug::log(LOG, "Rule move failed, rule: {} -> {}", r->m_rule, r->m_value); } - break; - } - case CWindowRule::RULE_CENTER: { - auto RESERVEDOFFSET = Vector2D(); - const auto ARGS = CVarList(r->m_rule, 2, ' '); - if (ARGS[1] == "1") - RESERVEDOFFSET = (PMONITOR->m_reservedTopLeft - PMONITOR->m_reservedBottomRight) / 2.f; - - *PWINDOW->m_realPosition = PMONITOR->middle() - PWINDOW->m_realSize->goal() / 2.f + RESERVEDOFFSET; - break; - } - - default: break; - } - } - - // 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; - - for (auto const& r : PWINDOW->m_matchedRules) { - if (r->m_ruleType != CWindowRule::RULE_SIZE) - continue; - - try { - const auto VALUE = r->m_rule.substr(r->m_rule.find(' ') + 1); - const auto SIZEXSTR = VALUE.substr(0, VALUE.find(' ')); - const auto SIZEYSTR = VALUE.substr(VALUE.find(' ') + 1); - - const auto MAXSIZE = PWINDOW->requestedMaxSize(); - - const float SIZEX = SIZEXSTR == "max" ? std::clamp(MAXSIZE.x, MIN_WINDOW_SIZE, PMONITOR->m_size.x) : stringToPercentage(SIZEXSTR, PMONITOR->m_size.x); - - const float SIZEY = SIZEYSTR == "max" ? std::clamp(MAXSIZE.y, MIN_WINDOW_SIZE, PMONITOR->m_size.y) : stringToPercentage(SIZEYSTR, PMONITOR->m_size.y); - - Debug::log(LOG, "Rule size (tiled), applying to {}", PWINDOW); - - setPseudo = true; - PWINDOW->m_pseudoSize = Vector2D(SIZEX, SIZEY); - - PWINDOW->setHidden(false); - } catch (...) { Debug::log(LOG, "Rule size failed, rule: {} -> {}", r->m_rule, r->m_value); } - } - - if (!setPseudo) - PWINDOW->m_pseudoSize = PWINDOW->m_realSize->goal() - Vector2D(10, 10); - } - - const auto PFOCUSEDWINDOWPREV = g_pCompositor->m_lastWindow.lock(); - - if (PWINDOW->m_windowData.allowsInput.valueOrDefault()) { // if default value wasn't set to false getPriority() would throw an exception - PWINDOW->m_windowData.noFocus = CWindowOverridableVar(false, PWINDOW->m_windowData.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(g_pCompositor->m_lastFocus.lock()); - 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_windowData.noFocus.valueOrDefault() && !PWINDOW->m_noInitialFocus && - (!PWINDOW->isX11OverrideRedirect() || (PWINDOW->m_isX11 && PWINDOW->m_xwaylandSurface->wantsFocus())) && !workspaceSilent && (!PFORCEFOCUS || PFORCEFOCUS == PWINDOW) && - !g_pInputManager->isConstrained()) { - g_pCompositor->focusWindow(PWINDOW); - PWINDOW->m_activeInactiveAlpha->setValueAndWarp(*PACTIVEALPHA); - PWINDOW->m_dimPercent->setValueAndWarp(PWINDOW->m_windowData.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_windowData.syncFullscreen = CWindowOverridableVar(false, PRIORITY_WINDOW_RULE); - g_pCompositor->setWindowFullscreenState(PWINDOW, requestedFSState.value()); - } else if (requestedInternalFSMode.has_value() && requestedClientFSMode.has_value() && !PWINDOW->m_windowData.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(); - - if (workspaceSilent) { - if (validMapped(PFOCUSEDWINDOWPREV)) { - g_pCompositor->focusWindow(PFOCUSEDWINDOWPREV); - PFOCUSEDWINDOWPREV->updateWindowDecos(); // need to for some reason i cba to find out why - } else if (!PFOCUSEDWINDOWPREV) - g_pCompositor->focusWindow(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 - g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW); - // 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 && - std::ranges::any_of(PWINDOW->m_matchedRules, [](const auto& r) { return r->m_ruleType == CWindowRule::RULE_PERSISTENTSIZE; })) { - 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 == g_pCompositor->m_lastWindow.lock()) { - wasLastWindow = true; - g_pCompositor->m_lastWindow.reset(); - g_pCompositor->m_lastFocus.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 != g_pCompositor->m_lastWindow.lock() && PWINDOWCANDIDATE) { - g_pCompositor->focusWindow(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 == g_pCompositor->m_lastWindow.lock() || !g_pCompositor->m_lastWindow.lock()) { - 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 == g_pCompositor->m_lastWindow.lock()) { - g_pCompositor->m_lastWindow.reset(); - g_pCompositor->m_lastFocus.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 (g_pCompositor->m_lastWindow.lock() && g_pCompositor->m_lastWindow->getPID() != PWINDOW->getPID()) - return; - - if (!PWINDOW->m_xwaylandSurface->wantsFocus()) - return; - - g_pCompositor->focusWindow(PWINDOW); - return; - } - - if (PWINDOW == g_pCompositor->m_lastWindow.lock() || (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 e7d5fd8c..f0bdc5a8 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 6257dcb0..8c2c7cd7 100644 --- a/src/helpers/AsyncDialogBox.cpp +++ b/src/helpers/AsyncDialogBox.cpp @@ -4,6 +4,8 @@ #include #include #include "../managers/eventLoop/EventLoopManager.hpp" +#include "../desktop/rule/windowRule/WindowRule.hpp" +#include "../desktop/rule/Engine.hpp" using namespace Hyprutils::OS; @@ -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 5f94be0d..1bdeba14 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/CursorShapes.hpp b/src/helpers/CursorShapes.hpp index cb95cd8b..3882c593 100644 --- a/src/helpers/CursorShapes.hpp +++ b/src/helpers/CursorShapes.hpp @@ -3,7 +3,7 @@ #include // clang-format off -constexpr std::array CURSOR_SHAPE_NAMES = { +constexpr std::array CURSOR_SHAPE_NAMES = { "invalid", "default", "context-menu", @@ -39,5 +39,7 @@ constexpr std::array CURSOR_SHAPE_NAMES = { "all-scroll", "zoom-in", "zoom-out", + "dnd-ask", + "all-resize" }; // clang-format on diff --git a/src/helpers/Drm.cpp b/src/helpers/Drm.cpp new file mode 100644 index 00000000..207b5e3d --- /dev/null +++ b/src/helpers/Drm.cpp @@ -0,0 +1,20 @@ +#include +#include "Drm.hpp" + +bool DRM::sameGpu(int fd1, int fd2) { + drmDevice* devA = nullptr; + drmDevice* devB = nullptr; + + if (drmGetDevice2(fd1, 0, &devA) != 0) + return false; + if (drmGetDevice2(fd2, 0, &devB) != 0) { + drmFreeDevice(&devA); + return false; + } + + bool same = drmDevicesEqual(devA, devB); + + drmFreeDevice(&devA); + drmFreeDevice(&devB); + return same; +} diff --git a/src/helpers/Drm.hpp b/src/helpers/Drm.hpp new file mode 100644 index 00000000..bc56b1ee --- /dev/null +++ b/src/helpers/Drm.hpp @@ -0,0 +1,5 @@ +#pragma once + +namespace DRM { + bool sameGpu(int fd1, int fd2); +} diff --git a/src/helpers/Format.cpp b/src/helpers/Format.cpp index ce64c113..7660934e 100644 --- a/src/helpers/Format.cpp +++ b/src/helpers/Format.cpp @@ -1,163 +1,191 @@ #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_ARGB8888, + .glInternalFormat = GL_RGBA8, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_BYTE, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_XRGB8888, + .bytesPerBlock = 4, + .swizzle = {SWIZZLE_BGRA}, }, { - .drmFormat = DRM_FORMAT_XRGB8888, - .flipRB = true, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_BYTE, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_XRGB8888, - .bytesPerBlock = 4, + .drmFormat = DRM_FORMAT_XRGB8888, + .glInternalFormat = GL_RGBA8, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_BYTE, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_XRGB8888, + .bytesPerBlock = 4, + .swizzle = {SWIZZLE_BGR1}, }, { - .drmFormat = DRM_FORMAT_XBGR8888, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_BYTE, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_XBGR8888, - .bytesPerBlock = 4, + .drmFormat = DRM_FORMAT_XBGR8888, + .glInternalFormat = GL_RGBA8, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_BYTE, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_XBGR8888, + .bytesPerBlock = 4, + .swizzle = {SWIZZLE_RGB1}, }, { - .drmFormat = DRM_FORMAT_ABGR8888, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_BYTE, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_XBGR8888, - .bytesPerBlock = 4, + .drmFormat = DRM_FORMAT_ABGR8888, + .glInternalFormat = GL_RGBA8, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_BYTE, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_XBGR8888, + .bytesPerBlock = 4, + .swizzle = {SWIZZLE_RGBA}, }, { - .drmFormat = DRM_FORMAT_BGR888, - .glFormat = GL_RGB, - .glType = GL_UNSIGNED_BYTE, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_BGR888, - .bytesPerBlock = 3, + .drmFormat = DRM_FORMAT_BGR888, + .glInternalFormat = GL_RGB8, + .glFormat = GL_RGB, + .glType = GL_UNSIGNED_BYTE, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_BGR888, + .bytesPerBlock = 3, + .swizzle = {SWIZZLE_RGB1}, }, { - .drmFormat = DRM_FORMAT_RGBX4444, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_SHORT_4_4_4_4, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_RGBX4444, - .bytesPerBlock = 2, + .drmFormat = DRM_FORMAT_RGBX4444, + .glInternalFormat = GL_RGBA4, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_SHORT_4_4_4_4, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_RGBX4444, + .bytesPerBlock = 2, + .swizzle = {SWIZZLE_RGB1}, }, { - .drmFormat = DRM_FORMAT_RGBA4444, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_SHORT_4_4_4_4, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_RGBX4444, - .bytesPerBlock = 2, + .drmFormat = DRM_FORMAT_RGBA4444, + .glInternalFormat = GL_RGBA4, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_SHORT_4_4_4_4, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_RGBX4444, + .bytesPerBlock = 2, + .swizzle = {SWIZZLE_RGBA}, }, { - .drmFormat = DRM_FORMAT_RGBX5551, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_SHORT_5_5_5_1, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_RGBX5551, - .bytesPerBlock = 2, + .drmFormat = DRM_FORMAT_RGBX5551, + .glInternalFormat = GL_RGB5_A1, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_SHORT_5_5_5_1, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_RGBX5551, + .bytesPerBlock = 2, + .swizzle = {SWIZZLE_RGB1}, }, { - .drmFormat = DRM_FORMAT_RGBA5551, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_SHORT_5_5_5_1, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_RGBX5551, - .bytesPerBlock = 2, + .drmFormat = DRM_FORMAT_RGBA5551, + .glInternalFormat = GL_RGB5_A1, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_SHORT_5_5_5_1, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_RGBX5551, + .bytesPerBlock = 2, + .swizzle = {SWIZZLE_RGBA}, }, { - .drmFormat = DRM_FORMAT_RGB565, - .glFormat = GL_RGB, - .glType = GL_UNSIGNED_SHORT_5_6_5, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_RGB565, - .bytesPerBlock = 2, + .drmFormat = DRM_FORMAT_RGB565, + .glInternalFormat = GL_RGB565, + .glFormat = GL_RGB, + .glType = GL_UNSIGNED_SHORT_5_6_5, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_RGB565, + .bytesPerBlock = 2, + .swizzle = {SWIZZLE_RGB1}, }, { - .drmFormat = DRM_FORMAT_XBGR2101010, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_INT_2_10_10_10_REV, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_XBGR2101010, - .bytesPerBlock = 4, + .drmFormat = DRM_FORMAT_XBGR2101010, + .glInternalFormat = GL_RGB10_A2, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_INT_2_10_10_10_REV, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_XBGR2101010, + .bytesPerBlock = 4, + .swizzle = {SWIZZLE_RGB1}, }, { - .drmFormat = DRM_FORMAT_ABGR2101010, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_INT_2_10_10_10_REV, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_XBGR2101010, - .bytesPerBlock = 4, + .drmFormat = DRM_FORMAT_ABGR2101010, + .glInternalFormat = GL_RGB10_A2, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_INT_2_10_10_10_REV, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_XBGR2101010, + .bytesPerBlock = 4, + .swizzle = {SWIZZLE_RGBA}, }, { - .drmFormat = DRM_FORMAT_XRGB2101010, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_INT_2_10_10_10_REV, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_XRGB2101010, - .bytesPerBlock = 4, + .drmFormat = DRM_FORMAT_XRGB2101010, + .glInternalFormat = GL_RGB10_A2, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_INT_2_10_10_10_REV, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_XRGB2101010, + .bytesPerBlock = 4, + .swizzle = {SWIZZLE_BGR1}, }, { - .drmFormat = DRM_FORMAT_ARGB2101010, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_INT_2_10_10_10_REV, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_XRGB2101010, - .bytesPerBlock = 4, + .drmFormat = DRM_FORMAT_ARGB2101010, + .glInternalFormat = GL_RGB10_A2, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_INT_2_10_10_10_REV, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_XRGB2101010, + .bytesPerBlock = 4, + .swizzle = {SWIZZLE_BGRA}, }, { - .drmFormat = DRM_FORMAT_XBGR16161616F, - .glFormat = GL_RGBA, - .glType = GL_HALF_FLOAT, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_XBGR16161616F, - .bytesPerBlock = 8, + .drmFormat = DRM_FORMAT_XBGR16161616F, + .glInternalFormat = GL_RGBA16F, + .glFormat = GL_RGBA, + .glType = GL_HALF_FLOAT, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_XBGR16161616F, + .bytesPerBlock = 8, + .swizzle = {SWIZZLE_RGB1}, }, { - .drmFormat = DRM_FORMAT_ABGR16161616F, - .glFormat = GL_RGBA, - .glType = GL_HALF_FLOAT, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_XBGR16161616F, - .bytesPerBlock = 8, + .drmFormat = DRM_FORMAT_ABGR16161616F, + .glInternalFormat = GL_RGBA16F, + .glFormat = GL_RGBA, + .glType = GL_HALF_FLOAT, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_XBGR16161616F, + .bytesPerBlock = 8, + .swizzle = {SWIZZLE_RGBA}, }, { - .drmFormat = DRM_FORMAT_XBGR16161616, - .glFormat = GL_RGBA16UI, - .glType = GL_UNSIGNED_SHORT, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_XBGR16161616, - .bytesPerBlock = 8, + .drmFormat = DRM_FORMAT_XBGR16161616, + .glInternalFormat = GL_RGBA16UI, + .glFormat = GL_RGBA_INTEGER, + .glType = GL_UNSIGNED_SHORT, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_XBGR16161616, + .bytesPerBlock = 8, + .swizzle = {SWIZZLE_RGBA}, }, { - .drmFormat = DRM_FORMAT_ABGR16161616, - .glFormat = GL_RGBA16UI, - .glType = GL_UNSIGNED_SHORT, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_XBGR16161616, - .bytesPerBlock = 8, + .drmFormat = DRM_FORMAT_ABGR16161616, + .glInternalFormat = GL_RGBA16UI, + .glFormat = GL_RGBA_INTEGER, + .glType = GL_UNSIGNED_SHORT, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_XBGR16161616, + .bytesPerBlock = 8, + .swizzle = {SWIZZLE_RGBA}, }, { .drmFormat = DRM_FORMAT_YVYU, @@ -170,24 +198,28 @@ inline const std::vector GLES3_FORMATS = { .blockSize = {2, 1}, }, { - .drmFormat = DRM_FORMAT_R8, - .bytesPerBlock = 1, + .drmFormat = DRM_FORMAT_R8, + .glInternalFormat = GL_R8, + .glFormat = GL_RED, + .glType = GL_UNSIGNED_BYTE, + .bytesPerBlock = 1, + .swizzle = {SWIZZLE_R001}, }, { - .drmFormat = DRM_FORMAT_GR88, - .bytesPerBlock = 2, + .drmFormat = DRM_FORMAT_GR88, + .glInternalFormat = GL_RG8, + .glFormat = GL_RG, + .glType = GL_UNSIGNED_BYTE, + .bytesPerBlock = 2, + .swizzle = {SWIZZLE_RG01}, }, { - .drmFormat = DRM_FORMAT_RGB888, - .bytesPerBlock = 3, - }, - { - .drmFormat = DRM_FORMAT_BGR888, - .bytesPerBlock = 3, - }, - { - .drmFormat = DRM_FORMAT_RGBX4444, - .bytesPerBlock = 2, + .drmFormat = DRM_FORMAT_RGB888, + .glInternalFormat = GL_RGB8, + .glFormat = GL_RGB, + .glType = GL_UNSIGNED_BYTE, + .bytesPerBlock = 3, + .swizzle = {SWIZZLE_BGR1}, }, }; @@ -229,6 +261,26 @@ const SPixelFormat* NFormatUtils::getPixelFormatFromGL(uint32_t glFormat, uint32 return nullptr; } +bool NFormatUtils::isFormatYUV(uint32_t drmFormat) { + switch (drmFormat) { + case DRM_FORMAT_YUYV: + case DRM_FORMAT_YVYU: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_VYUY: + case DRM_FORMAT_AYUV: + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: + case DRM_FORMAT_YUV410: + case DRM_FORMAT_YUV411: + case DRM_FORMAT_YUV420: + case DRM_FORMAT_YUV422: + case DRM_FORMAT_YUV444: return true; + default: return false; + } +} + bool NFormatUtils::isFormatOpaque(DRMFormat drm) { const auto FMT = NFormatUtils::getPixelFormatFromDRM(drm); if (!FMT) @@ -245,32 +297,38 @@ int NFormatUtils::minStride(const SPixelFormat* const fmt, int32_t width) { return std::ceil((width * fmt->bytesPerBlock) / pixelsPerBlock(fmt)); } -uint32_t NFormatUtils::drmFormatToGL(DRMFormat drm) { - switch (drm) { - case DRM_FORMAT_XRGB8888: - case DRM_FORMAT_XBGR8888: return GL_RGBA; // doesn't matter, opengl is gucci in this case. - case DRM_FORMAT_XRGB2101010: - case DRM_FORMAT_XBGR2101010: return GL_RGB10_A2; - default: return GL_RGBA; - } - UNREACHABLE(); - return GL_RGBA; -} - -uint32_t NFormatUtils::glFormatToType(uint32_t gl) { - return gl != GL_RGBA ? GL_UNSIGNED_INT_2_10_10_10_REV : GL_UNSIGNED_BYTE; -} - std::string NFormatUtils::drmFormatName(DRMFormat drm) { - auto n = drmGetFormatName(drm); + auto n = drmGetFormatName(drm); + + if (!n) + return "unknown"; + std::string name = n; free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors) return name; } std::string NFormatUtils::drmModifierName(uint64_t mod) { - auto n = drmGetFormatModifierName(mod); + auto n = drmGetFormatModifierName(mod); + + if (!n) + return "unknown"; + std::string name = n; free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors) return name; } + +DRMFormat NFormatUtils::alphaFormat(DRMFormat prevFormat) { + switch (prevFormat) { + case DRM_FORMAT_XRGB8888: return DRM_FORMAT_ARGB8888; + case DRM_FORMAT_XBGR8888: return DRM_FORMAT_ABGR8888; + case DRM_FORMAT_BGRX8888: return DRM_FORMAT_BGRA8888; + case DRM_FORMAT_RGBX8888: return DRM_FORMAT_RGBA8888; + case DRM_FORMAT_XRGB2101010: return DRM_FORMAT_ARGB2101010; + case DRM_FORMAT_XBGR2101010: return DRM_FORMAT_ABGR2101010; + case DRM_FORMAT_RGBX1010102: return DRM_FORMAT_RGBA1010102; + case DRM_FORMAT_BGRX1010102: return DRM_FORMAT_BGRA1010102; + default: return 0; + } +} diff --git a/src/helpers/Format.hpp b/src/helpers/Format.hpp index fe68f763..02925e22 100644 --- a/src/helpers/Format.hpp +++ b/src/helpers/Format.hpp @@ -2,22 +2,43 @@ #include #include +#include #include "math/Math.hpp" #include using DRMFormat = uint32_t; using SHMFormat = uint32_t; +#define SWIZZLE_A1GB {GL_ALPHA, GL_ONE, GL_GREEN, GL_BLUE} +#define SWIZZLE_ABG1 {GL_ALPHA, GL_BLUE, GL_GREEN, GL_ONE} +#define SWIZZLE_ABGR {GL_ALPHA, GL_BLUE, GL_GREEN, GL_RED} +#define SWIZZLE_ARGB {GL_ALPHA, GL_RED, GL_GREEN, GL_BLUE} +#define SWIZZLE_B1RG {GL_BLUE, GL_ONE, GL_RED, GL_GREEN} +#define SWIZZLE_BARG {GL_BLUE, GL_ALPHA, GL_RED, GL_GREEN} +#define SWIZZLE_BGR1 {GL_BLUE, GL_GREEN, GL_RED, GL_ONE} +#define SWIZZLE_BGRA {GL_BLUE, GL_GREEN, GL_RED, GL_ALPHA} +#define SWIZZLE_G1AB {GL_GREEN, GL_ONE, GL_ALPHA, GL_BLUE} +#define SWIZZLE_GBA1 {GL_GREEN, GL_BLUE, GL_ALPHA, GL_ONE} +#define SWIZZLE_GBAR {GL_GREEN, GL_BLUE, GL_ALPHA, GL_RED} +#define SWIZZLE_GRAB {GL_GREEN, GL_RED, GL_ALPHA, GL_BLUE} +#define SWIZZLE_R001 {GL_RED, GL_ZERO, GL_ZERO, GL_ONE} +#define SWIZZLE_R1BG {GL_RED, GL_ONE, GL_BLUE, GL_GREEN} +#define SWIZZLE_RABG {GL_RED, GL_ALPHA, GL_BLUE, GL_GREEN} +#define SWIZZLE_RG01 {GL_RED, GL_GREEN, GL_ZERO, GL_ONE} +#define SWIZZLE_GR01 {GL_GREEN, GL_RED, GL_ZERO, GL_ONE} +#define SWIZZLE_RGB1 {GL_RED, GL_GREEN, GL_BLUE, GL_ONE} +#define SWIZZLE_RGBA {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA} + struct SPixelFormat { - DRMFormat drmFormat = 0; /* DRM_FORMAT_INVALID */ - bool flipRB = false; - int glInternalFormat = 0; - int glFormat = 0; - int glType = 0; - bool withAlpha = true; - DRMFormat alphaStripped = 0; /* DRM_FORMAT_INVALID */ - uint32_t bytesPerBlock = 0; - Vector2D blockSize; + DRMFormat drmFormat = 0; /* DRM_FORMAT_INVALID */ + int glInternalFormat = 0; + int glFormat = 0; + int glType = 0; + bool withAlpha = true; + DRMFormat alphaStripped = 0; /* DRM_FORMAT_INVALID */ + uint32_t bytesPerBlock = 0; + Vector2D blockSize; + std::optional> swizzle = std::nullopt; }; using SDRMFormat = Aquamarine::SDRMFormat; @@ -28,11 +49,11 @@ namespace NFormatUtils { const SPixelFormat* getPixelFormatFromDRM(DRMFormat drm); const SPixelFormat* getPixelFormatFromGL(uint32_t glFormat, uint32_t glType, bool alpha); + bool isFormatYUV(uint32_t drmFormat); bool isFormatOpaque(DRMFormat drm); int pixelsPerBlock(const SPixelFormat* const fmt); int minStride(const SPixelFormat* const fmt, int32_t width); - uint32_t drmFormatToGL(DRMFormat drm); - uint32_t glFormatToType(uint32_t gl); std::string drmFormatName(DRMFormat drm); std::string drmModifierName(uint64_t mod); + DRMFormat alphaFormat(DRMFormat prevFormat); }; diff --git a/src/helpers/MainLoopExecutor.cpp b/src/helpers/MainLoopExecutor.cpp index 8632d93b..c7b5f910 100644 --- a/src/helpers/MainLoopExecutor.cpp +++ b/src/helpers/MainLoopExecutor.cpp @@ -11,10 +11,7 @@ static int onDataRead(int fd, uint32_t mask, void* data) { CMainLoopExecutor::CMainLoopExecutor(std::function&& callback) : m_fn(std::move(callback)) { int fds[2]; - pipe(fds); - - RASSERT(fds[0] != 0, "CMainLoopExecutor: failed to open a pipe"); - RASSERT(fds[1] != 0, "CMainLoopExecutor: failed to open a pipe"); + RASSERT(pipe(fds) == 0, "CMainLoopExecutor: failed to open a pipe"); m_event = wl_event_loop_add_fd(g_pEventLoopManager->m_wayland.loop, fds[0], WL_EVENT_READABLE, ::onDataRead, this); diff --git a/src/helpers/MiscFunctions.cpp b/src/helpers/MiscFunctions.cpp index 4cf3c671..34b06c2e 100644 --- a/src/helpers/MiscFunctions.cpp +++ b/src/helpers/MiscFunctions.cpp @@ -3,6 +3,8 @@ #include #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 "fs/FsUtils.hpp" @@ -18,6 +20,7 @@ #include #include #include +#include #ifdef HAS_EXECINFO #include #endif @@ -102,7 +105,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 {}; } } @@ -146,8 +149,8 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { } else if (in.starts_with("empty")) { const bool same_mon = in.substr(5).contains("m"); const bool next = in.substr(5).contains("n"); - if ((same_mon || next) && !g_pCompositor->m_lastMonitor) { - Debug::log(ERR, "Empty monitor workspace on monitor null!"); + if ((same_mon || next) && !Desktop::focusState()->monitor()) { + Log::logger->log(Log::ERR, "Empty monitor workspace on monitor null!"); return {WORKSPACE_INVALID}; } @@ -155,12 +158,12 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { if (same_mon) { for (auto const& rule : g_pConfigManager->getAllWorkspaceRules()) { const auto PMONITOR = g_pCompositor->getMonitorFromString(rule.monitor); - if (PMONITOR && (PMONITOR->m_id != g_pCompositor->m_lastMonitor->m_id)) + if (PMONITOR && (PMONITOR->m_id != Desktop::focusState()->monitor()->m_id)) invalidWSes.insert(rule.workspaceId); } } - WORKSPACEID id = next ? g_pCompositor->m_lastMonitor->activeWorkspaceID() : 0; + WORKSPACEID id = next ? Desktop::focusState()->monitor()->activeWorkspaceID() : 0; while (++id < LONG_MAX) { const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(id); if (!invalidWSes.contains(id) && (!PWORKSPACE || PWORKSPACE->getWindows() == 0)) { @@ -169,15 +172,15 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { } } } else if (in.starts_with("prev")) { - if (!g_pCompositor->m_lastMonitor) + if (!Desktop::focusState()->monitor()) return {WORKSPACE_INVALID}; - const auto PWORKSPACE = g_pCompositor->m_lastMonitor->m_activeWorkspace; + const auto PWORKSPACE = Desktop::focusState()->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}; @@ -185,18 +188,18 @@ 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 (!g_pCompositor->m_lastMonitor || !g_pCompositor->m_lastMonitor->m_activeWorkspace) { - Debug::log(ERR, "no active monitor or workspace for 'next'"); + if (!Desktop::focusState()->monitor() || !Desktop::focusState()->monitor()->m_activeWorkspace) { + Log::logger->log(Log::ERR, "no active monitor or workspace for 'next'"); return {WORKSPACE_INVALID}; } - auto PCURRENTWORKSPACE = g_pCompositor->m_lastMonitor->m_activeWorkspace; + auto PCURRENTWORKSPACE = Desktop::focusState()->monitor()->m_activeWorkspace; WORKSPACEID nextId = PCURRENTWORKSPACE->m_id + 1; @@ -209,8 +212,8 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { } else { if (in[0] == 'r' && (in[1] == '-' || in[1] == '+' || in[1] == '~') && isNumber(in.substr(2))) { bool absolute = in[1] == '~'; - if (!g_pCompositor->m_lastMonitor) { - Debug::log(ERR, "Relative monitor workspace on monitor null!"); + if (!Desktop::focusState()->monitor()) { + Log::logger->log(Log::ERR, "Relative monitor workspace on monitor null!"); return {WORKSPACE_INVALID}; } @@ -227,14 +230,14 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { // Collect all the workspaces we can't jump to. for (auto const& ws : g_pCompositor->getWorkspaces()) { - if (ws->m_isSpecialWorkspace || (ws->m_monitor != g_pCompositor->m_lastMonitor)) { + if (ws->m_isSpecialWorkspace || (ws->m_monitor != Desktop::focusState()->monitor())) { // Can't jump to this workspace invalidWSes.insert(ws->m_id); } } for (auto const& rule : g_pConfigManager->getAllWorkspaceRules()) { const auto PMONITOR = g_pCompositor->getMonitorFromString(rule.monitor); - if (!PMONITOR || PMONITOR->m_id == g_pCompositor->m_lastMonitor->m_id) { + if (!PMONITOR || PMONITOR->m_id == Desktop::focusState()->monitor()->m_id) { // Can't be invalid continue; } @@ -245,7 +248,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { // Prepare all named workspaces in case when we need them std::vector namedWSes; for (auto const& ws : g_pCompositor->getWorkspaces()) { - if (ws->m_isSpecialWorkspace || (ws->m_monitor != g_pCompositor->m_lastMonitor) || ws->m_id >= 0) + if (ws->m_isSpecialWorkspace || (ws->m_monitor != Desktop::focusState()->monitor()) || ws->m_id >= 0) continue; namedWSes.push_back(ws->m_id); @@ -272,7 +275,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { } else { // Just take a blind guess at where we'll probably end up - WORKSPACEID activeWSID = g_pCompositor->m_lastMonitor->m_activeWorkspace ? g_pCompositor->m_lastMonitor->m_activeWorkspace->m_id : 1; + WORKSPACEID activeWSID = Desktop::focusState()->monitor()->m_activeWorkspace ? Desktop::focusState()->monitor()->m_activeWorkspace->m_id : 1; WORKSPACEID predictedWSID = activeWSID + remains; int remainingWSes = 0; char walkDir = in[1]; @@ -371,8 +374,8 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { bool onAllMonitors = in[0] == 'e'; bool absolute = in[1] == '~'; - if (!g_pCompositor->m_lastMonitor) { - Debug::log(ERR, "Relative monitor workspace on monitor null!"); + if (!Desktop::focusState()->monitor()) { + Log::logger->log(Log::ERR, "Relative monitor workspace on monitor null!"); return {WORKSPACE_INVALID}; } @@ -389,7 +392,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { std::vector validWSes; for (auto const& ws : g_pCompositor->getWorkspaces()) { - if (ws->m_isSpecialWorkspace || (ws->m_monitor != g_pCompositor->m_lastMonitor && !onAllMonitors)) + if (ws->m_isSpecialWorkspace || (ws->m_monitor != Desktop::focusState()->monitor() && !onAllMonitors)) continue; validWSes.push_back(ws->m_id); @@ -414,7 +417,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { remains = remains < 0 ? -((-remains) % validWSes.size()) : remains % validWSes.size(); // get the current item - WORKSPACEID activeWSID = g_pCompositor->m_lastMonitor->m_activeWorkspace ? g_pCompositor->m_lastMonitor->m_activeWorkspace->m_id : 1; + WORKSPACEID activeWSID = Desktop::focusState()->monitor()->m_activeWorkspace ? Desktop::focusState()->monitor()->m_activeWorkspace->m_id : 1; for (ssize_t i = 0; i < sc(validWSes.size()); i++) { if (validWSes[i] == activeWSID) { currentItem = i; @@ -437,14 +440,14 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { result.name = g_pCompositor->getWorkspaceByID(validWSes[currentItem])->m_name; } else { if (in[0] == '+' || in[0] == '-') { - if (g_pCompositor->m_lastMonitor) { - const auto PLUSMINUSRESULT = getPlusMinusKeywordResult(in, g_pCompositor->m_lastMonitor->activeWorkspaceID()); + if (Desktop::focusState()->monitor()) { + const auto PLUSMINUSRESULT = getPlusMinusKeywordResult(in, Desktop::focusState()->monitor()->activeWorkspaceID()); if (!PLUSMINUSRESULT.has_value()) return {WORKSPACE_INVALID}; 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)) @@ -523,12 +526,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"); @@ -554,16 +557,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) { @@ -766,17 +769,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(); @@ -871,7 +867,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 @@ -975,3 +971,13 @@ std::string getBuiltSystemLibraryNames() { result += std::format("Aquamarine: built against {}, system has {}\n", AQUAMARINE_VERSION, getSystemLibraryVersion("aquamarine")); return result; } + +bool truthy(const std::string& str) { + if (str == "1") + return true; + + std::string cpy = str; + std::ranges::transform(cpy, cpy.begin(), ::tolower); + + return cpy.starts_with("true") || cpy.starts_with("yes") || cpy.starts_with("on"); +} diff --git a/src/helpers/MiscFunctions.hpp b/src/helpers/MiscFunctions.hpp index 5feb2de9..437cfcb4 100644 --- a/src/helpers/MiscFunctions.hpp +++ b/src/helpers/MiscFunctions.hpp @@ -36,7 +36,6 @@ std::optional getPlusMinusKeywordResult(std::string in double normalizeAngleRad(double ang); std::vector getBacktrace(); void throwError(const std::string& err); -bool envEnabled(const std::string& env); 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); @@ -46,6 +45,7 @@ std::expected binaryNameForPid(pid_t pid); std::string deviceNameToInternalString(std::string in); std::string getSystemLibraryVersion(const std::string& name); std::string getBuiltSystemLibraryNames(); +bool truthy(const std::string& str); template [[deprecated("use std::format instead")]] std::string getFormat(std::format_string fmt, Args&&... args) { diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index 74dd995b..07156ff1 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -2,6 +2,7 @@ #include "MiscFunctions.hpp" #include "../macros.hpp" #include "SharedDefs.hpp" +#include "../helpers/TransferFunction.hpp" #include "math/Math.hpp" #include "../protocols/ColorManagement.hpp" #include "../Compositor.hpp" @@ -22,16 +23,22 @@ #include "../protocols/core/DataDevice.hpp" #include "../render/Renderer.hpp" #include "../managers/EventManager.hpp" -#include "../managers/LayoutManager.hpp" +#include "../managers/screenshare/ScreenshareManager.hpp" #include "../managers/animation/AnimationManager.hpp" #include "../managers/animation/DesktopAnimationManager.hpp" #include "../managers/input/InputManager.hpp" #include "../hyprerror/HyprError.hpp" +#include "../layout/LayoutManager.hpp" +#include "../i18n/Engine.hpp" +#include "../helpers/cm/ColorManagement.hpp" #include "sync/SyncTimeline.hpp" #include "time/Time.hpp" -#include "../desktop/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/log/Logger.hpp" #include "debug/HyprNotificationOverlay.hpp" #include "MonitorFrameScheduler.hpp" #include @@ -48,7 +55,7 @@ using namespace Hyprutils::OS; using enum NContentType::eContentType; using namespace NColorManagement; -CMonitor::CMonitor(SP output_) : m_state(this), m_output(output_) { +CMonitor::CMonitor(SP output_) : m_state(this), m_output(output_), m_imageDescription(DEFAULT_IMAGE_DESCRIPTION) { g_pAnimationManager->createAnimation(0.f, m_specialFade, g_pConfigManager->getAnimationPropertyConfig("specialWorkspaceIn"), AVARDAMAGE_NONE); m_specialFade->setUpdateCallback([this](auto) { g_pHyprRenderer->damageMonitor(m_self.lock()); }); static auto PZOOMFACTOR = CConfigValue("cursor:zoom_factor"); @@ -69,23 +76,27 @@ CMonitor::~CMonitor() { } void CMonitor::onConnect(bool noRule) { - EMIT_HOOK_EVENT("preMonitorAdded", m_self.lock()); + Event::bus()->m_events.monitor.preAdded.emit(m_self.lock()); CScopeGuard x = {[]() { g_pCompositor->arrangeMonitors(); }}; m_zoomAnimProgress->setValueAndWarp(0.F); m_zoomAnimFrameCounter = 0; - g_pEventLoopManager->doLater([] { g_pConfigManager->ensurePersistentWorkspacesPresent(); }); + g_pEventLoopManager->doLater([] { + g_pConfigManager->ensurePersistentWorkspacesPresent(); + g_pCompositor->ensureWorkspacesOnAssignedMonitors(); + }); m_listeners.frame = m_output->events.frame.listen([this] { if (m_frameScheduler) m_frameScheduler->onFrame(); }); m_listeners.commit = m_output->events.commit.listen([this] { - if (true) { // FIXME: E->state->committed & WLR_OUTPUT_STATE_BUFFER - PROTO::screencopy->onOutputCommit(m_self.lock()); - PROTO::toplevelExport->onOutputCommit(m_self.lock()); - } + m_events.commit.emit(); + + // FIXME: E->state->committed & WLR_OUTPUT_STATE_BUFFER + if (true && Screenshare::mgr()) + Screenshare::mgr()->onOutputCommit(m_self.lock()); }); m_listeners.needsFrame = m_output->events.needsFrame.listen([this] { g_pCompositor->scheduleFrameForMonitor(m_self.lock(), Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); }); @@ -112,10 +123,12 @@ void CMonitor::onConnect(bool noRule) { ts = nullptr; } - if (!ts) - PROTO::presentation->onPresented(m_self.lock(), Time::steadyNow(), event.refresh, event.seq, event.flags); - else - PROTO::presentation->onPresented(m_self.lock(), Time::fromTimespec(event.when), event.refresh, event.seq, event.flags); + if (!ts) { + timespec mono{}; + clock_gettime(CLOCK_MONOTONIC, &mono); + PROTO::presentation->onPresented(m_self.lock(), mono, event.refresh, event.seq, event.flags & ~Aquamarine::IOutput::AQ_OUTPUT_PRESENT_HW_CLOCK); + } else + PROTO::presentation->onPresented(m_self.lock(), *ts, event.refresh, event.seq, event.flags); if (m_zoomAnimFrameCounter < 5) { m_zoomAnimFrameCounter++; @@ -144,14 +157,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; }); }); @@ -163,7 +176,7 @@ void CMonitor::onConnect(bool noRule) { if (m_createdByUser) return; - Debug::log(LOG, "Reapplying monitor rule for {} from a state request", m_name); + Log::logger->log(Log::DEBUG, "Reapplying monitor rule for {} from a state request", m_name); applyMonitorRule(&m_activeMonitorRule, true); return; } @@ -176,7 +189,11 @@ void CMonitor::onConnect(bool noRule) { m_forceSize = SIZE; SMonitorRule rule = m_activeMonitorRule; - rule.resolution = SIZE; + + if (SIZE == rule.resolution) + return; + + rule.resolution = SIZE; applyMonitorRule(&rule); }); @@ -218,7 +235,7 @@ void CMonitor::onConnect(bool noRule) { 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; @@ -227,7 +244,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()) @@ -264,11 +281,11 @@ void CMonitor::onConnect(bool noRule) { applyMonitorRule(&monitorRule, 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); @@ -276,10 +293,16 @@ void CMonitor::onConnect(bool noRule) { if (!valid(ws)) continue; - if (ws->m_lastMonitor == m_name || g_pCompositor->m_monitors.size() == 1 /* avoid lost workspaces on recover */) { + const auto CURRENTMON = ws->m_monitor.lock(); + const bool ORPHANED = !CURRENTMON || std::ranges::none_of(g_pCompositor->m_monitors, [&](const auto& mon) { return mon == CURRENTMON; }); + const bool RETURNING = ws->m_lastMonitor == m_name; + const bool RECOVERY = g_pCompositor->m_monitors.size() == 1 && ORPHANED; // temporarily recover orphaned workspaces + + if (RETURNING || RECOVERY) { g_pCompositor->moveWorkspaceToMonitor(ws, m_self.lock()); g_pDesktopAnimationManager->startAnimation(ws, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); - ws->m_lastMonitor = ""; + if (RETURNING) + ws->m_lastMonitor = ""; } } @@ -293,11 +316,11 @@ void CMonitor::onConnect(bool noRule) { if (!m_activeMonitorRule.mirrorOf.empty()) setMirror(m_activeMonitorRule.mirrorOf); - if (!g_pCompositor->m_lastMonitor) // set the last monitor if it isn't set yet - g_pCompositor->setActiveMonitor(m_self.lock()); + 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()); @@ -305,27 +328,27 @@ void CMonitor::onConnect(bool noRule) { // verify last mon valid bool found = false; for (auto const& m : g_pCompositor->m_monitors) { - if (m == g_pCompositor->m_lastMonitor) { + if (m == Desktop::focusState()->monitor()) { found = true; break; } } - 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) - g_pCompositor->setActiveMonitor(m_self.lock()); + Desktop::focusState()->rawMonitorFocus(m_self.lock()); g_pCompositor->scheduleFrameForMonitor(m_self.lock(), Aquamarine::IOutput::AQ_SCHEDULE_NEW_MONITOR); @@ -335,17 +358,17 @@ void CMonitor::onConnect(bool noRule) { g_pEventManager->postEvent(SHyprIPCEvent{"monitoradded", m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"monitoraddedv2", std::format("{},{},{}", m_id, m_name, m_shortDescription)}); - EMIT_HOOK_EVENT("monitorAdded", m_self.lock()); + Event::bus()->m_events.monitor.added.emit(m_self.lock()); } void CMonitor::onDisconnect(bool destroy) { - EMIT_HOOK_EVENT("preMonitorRemoved", m_self.lock()); + Event::bus()->m_events.monitor.preRemoved.emit(m_self.lock()); CScopeGuard x = {[this]() { if (g_pCompositor->m_isShuttingDown) return; g_pEventManager->postEvent(SHyprIPCEvent{"monitorremoved", m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"monitorremovedv2", std::format("{},{},{}", m_id, m_name, m_shortDescription)}); - EMIT_HOOK_EVENT("monitorRemoved", m_self.lock()); + Event::bus()->m_events.monitor.removed.emit(m_self.lock()); g_pCompositor->scheduleMonitorStateRecheck(); }}; @@ -354,7 +377,7 @@ 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) @@ -362,7 +385,7 @@ void CMonitor::onDisconnect(bool destroy) { // 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; } @@ -405,36 +428,41 @@ 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); } } else { - g_pCompositor->m_lastFocus.reset(); - g_pCompositor->m_lastWindow.reset(); - g_pCompositor->m_lastMonitor.reset(); + Desktop::focusState()->surface().reset(); + Desktop::focusState()->window().reset(); + Desktop::focusState()->monitor().reset(); } if (m_activeWorkspace) @@ -446,10 +474,10 @@ 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 (g_pCompositor->m_lastMonitor == m_self) - g_pCompositor->setActiveMonitor(BACKUPMON ? BACKUPMON : g_pCompositor->m_unsafeOutput.lock()); + if (Desktop::focusState()->monitor() == m_self) + Desktop::focusState()->rawMonitorFocus(BACKUPMON ? BACKUPMON : g_pCompositor->m_unsafeOutput.lock()); if (g_pHyprRenderer->m_mostHzMonitor == m_self) { int mostHz = 0; @@ -464,87 +492,121 @@ 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_DEFAULT: + case NTransferFunction::TF_GAMMA22: + case NTransferFunction::TF_FORCED_GAMMA22: return NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22; + case NTransferFunction::TF_SRGB: return NColorManagement::CM_TRANSFER_FUNCTION_SRGB; + + case NTransferFunction::TF_AUTO: // use global setting + switch (sdrEOTF) { + case NTransferFunction::TF_AUTO: return NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22; + default: return chooseTF(sdrEOTF); + } + + default: UNREACHABLE(); + } +} + +void CMonitor::applyCMType(NCMType::eCMType cmType, NTransferFunction::eTF cmSdrEotf) { + auto oldImageDescription = m_imageDescription; + const auto chosenSdrEotf = chooseTF(cmSdrEotf); + + const auto masteringPrimaries = getMasteringPrimaries(); + const NColorManagement::SImageDescription::SPCMasteringLuminances masteringLuminances = getMasteringLuminances(); + + const auto maxFALL = this->maxFALL(); + const auto maxCLL = this->maxCLL(); switch (cmType) { - case NCMType::CM_SRGB: m_imageDescription = {.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); } @@ -554,7 +616,7 @@ bool CMonitor::applyMonitorRule(SMonitorRule* 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; @@ -579,7 +641,7 @@ 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; } @@ -597,9 +659,9 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { && 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))) { + RULE->maxAvgLuminance == m_maxAvgLuminance && !std::memcmp(&m_customDrmMode, &RULE->drmMode, sizeof(m_customDrmMode)) && m_reservedArea == RULE->reservedArea) { - Debug::log(LOG, "Not applying a new rule to {} because it's already applied!", m_name); + Log::logger->log(Log::DEBUG, "Not applying a new rule to {} because it's already applied!", m_name); setMirror(RULE->mirrorOf); @@ -608,17 +670,18 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { bool autoScale = false; - if (RULE->scale > 0.1) { + if (RULE->scale > 0.1) m_scale = RULE->scale; - } else { + 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->transform; + m_autoDir = RULE->autoDir; + m_reservedArea = RULE->reservedArea; // accumulate requested modes in reverse order (cause inesrting at front is inefficient) std::vector> requestedModes; @@ -635,7 +698,7 @@ 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()); @@ -710,7 +773,7 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { // then if requested is custom, try custom mode first if (RULE->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})); @@ -730,13 +793,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); } } } @@ -748,7 +811,7 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { m_output->state->setCustomMode(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; } @@ -757,9 +820,9 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { m_output->state->setMode(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; } @@ -773,11 +836,11 @@ 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; } @@ -791,7 +854,7 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { m_output->state->setCustomMode(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; @@ -800,7 +863,7 @@ 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 @@ -811,9 +874,9 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { if (!m_state.test()) continue; - auto errorMessage = - std::format("Monitor {} failed to set any requested modes, falling back to mode {:X0}@{:.2f}Hz", m_name, mode->pixelSize, mode->refreshRate / 1000.f); - Debug::log(WARN, errorMessage); + 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)}}); + Log::logger->log(Log::WARN, errorMessage); g_pHyprNotificationOverlay->addNotification(errorMessage, CHyprColor(0xff0000ff), 5000, ICON_WARNING); m_refreshRate = mode->refreshRate / 1000.f; @@ -828,7 +891,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->resolution, RULE->refreshRate); return true; } @@ -856,9 +919,9 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { 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); + Log::logger->log(Log::DEBUG, "output {} succeeded basic test on format {}", m_name, fmt.first); if (RULE->enable10bit && fmt.first.contains("101010")) set10bit = true; break; @@ -870,29 +933,46 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { m_supportsWideColor = RULE->supportsHDR; m_supportsHDR = RULE->supportsHDR; - m_cmType = RULE->cmType; - switch (m_cmType) { - case NCMType::CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? NCMType::CM_WIDE : NCMType::CM_SRGB; break; - case NCMType::CM_EDID: m_cmType = m_output->parsedEDID.chromaticityCoords.has_value() ? NCMType::CM_EDID : NCMType::CM_SRGB; break; - case NCMType::CM_HDR: - case NCMType::CM_HDR_EDID: m_cmType = supportsHDR() ? m_cmType : NCMType::CM_SRGB; break; - default: break; + if (RULE->iccFile.empty()) { + // only apply explicit cm settings if we have no icc file + + m_cmType = RULE->cmType; + switch (m_cmType) { + case NCMType::CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? NCMType::CM_WIDE : NCMType::CM_SRGB; break; + case NCMType::CM_EDID: m_cmType = m_output->parsedEDID.chromaticityCoords.has_value() ? NCMType::CM_EDID : NCMType::CM_SRGB; break; + case NCMType::CM_HDR: + case NCMType::CM_HDR_EDID: m_cmType = supportsHDR() ? m_cmType : NCMType::CM_SRGB; break; + default: break; + } + + m_sdrEotf = RULE->sdrEotf; + + m_sdrMinLuminance = RULE->sdrMinLuminance; + m_sdrMaxLuminance = RULE->sdrMaxLuminance; + + m_minLuminance = RULE->minLuminance; + m_maxLuminance = RULE->maxLuminance; + m_maxAvgLuminance = RULE->maxAvgLuminance; + + applyCMType(m_cmType, m_sdrEotf); + + m_sdrSaturation = RULE->sdrSaturation; + m_sdrBrightness = RULE->sdrBrightness; + } else { + auto image = NColorManagement::SImageDescription::fromICC(RULE->iccFile); + if (!image) { + Log::logger->log(Log::ERR, "icc for {} ({}) failed: {}", m_name, RULE->iccFile, image.error()); + g_pConfigManager->addParseError(std::format("failed to apply icc {} to {}: {}", RULE->iccFile, m_name, image.error())); + } else { + m_imageDescription = CImageDescription::from(*image); + if (!m_imageDescription) { + Log::logger->log(Log::ERR, "icc for {} ({}) failed 2: {}", m_name, RULE->iccFile, image.error()); + g_pConfigManager->addParseError(std::format("failed to apply icc {} to {}: {}", RULE->iccFile, m_name, image.error())); + m_imageDescription = CImageDescription::from(SImageDescription{}); + } + } } - m_sdrEotf = RULE->sdrEotf; - - m_sdrMinLuminance = RULE->sdrMinLuminance; - m_sdrMaxLuminance = RULE->sdrMaxLuminance; - - m_minLuminance = RULE->minLuminance; - m_maxLuminance = RULE->maxLuminance; - m_maxAvgLuminance = RULE->maxAvgLuminance; - - applyCMType(m_cmType, m_sdrEotf); - - m_sdrSaturation = RULE->sdrSaturation; - m_sdrBrightness = RULE->sdrBrightness; - Vector2D logicalSize = m_pixelSize / m_scale; if (!*PDISABLESCALECHECKS && (logicalSize.x != std::round(logicalSize.x) || logicalSize.y != std::round(logicalSize.y))) { // invalid scale, will produce fractional pixels. @@ -930,17 +1010,19 @@ 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); + Log::logger->log(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"); 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(std::format("Invalid scale passed to monitor: {}, using suggested scale: {}", m_scale, searchScale), - CHyprColor(1.0, 0.0, 0.0, 1.0), 5000, ICON_WARNING); + g_pHyprNotificationOverlay->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; } @@ -950,7 +1032,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(); @@ -958,7 +1040,7 @@ 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); } @@ -972,6 +1054,8 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { m_damage.setSize(m_transformedSize); + updateVCGTRamps(); + // Set scale for all surfaces on this monitor, needed for some clients // but not on unsafe state to avoid crashes if (!g_pCompositor->m_unsafeState) { @@ -985,10 +1069,10 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { // reload to fix mirrors g_pConfigManager->m_wantsMonitorReload = true; - 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(); @@ -1022,8 +1106,10 @@ bool CMonitor::shouldSkipScheduleFrameOnMouseEvent() { static auto PMINRR = CConfigValue("cursor:min_refresh_rate"); // skip scheduling extra frames for fullsreen apps with vrr - const bool shouldSkip = inFullscreenMode() && (*PNOBREAK == 1 || (*PNOBREAK == 2 && m_activeWorkspace->getFullscreenWindow()->getContentType() == CONTENT_TYPE_GAME)) && - m_output->state->state().adaptiveSync; + const auto FS_WINDOW = getFullscreenWindow(); + const bool shouldRenderCursor = g_pHyprRenderer->shouldRenderCursor(); + const bool noBreak = FS_WINDOW && (*PNOBREAK == 1 || (*PNOBREAK == 2 && FS_WINDOW->getContentType() == CONTENT_TYPE_GAME)); + const bool shouldSkip = (!shouldRenderCursor || noBreak) && m_output->state->state().adaptiveSync; // keep requested minimum refresh rate if (shouldSkip && *PMINRR && m_lastPresentationTimer.getMillis() > 1000.0f / *PMINRR) { @@ -1081,18 +1167,18 @@ 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.", g_pConfigManager->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()) @@ -1115,12 +1201,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; } @@ -1196,7 +1282,7 @@ void CMonitor::setMirror(const std::string& mirrorOf) { g_pCompositor->scheduleMonitorStateRecheck(); - g_pCompositor->setActiveMonitor(g_pCompositor->m_monitors.front()); + Desktop::focusState()->rawMonitorFocus(g_pCompositor->m_monitors.front()); // Software lock mirrored monitor g_pPointerManager->lockSoftwareForMonitor(PMIRRORMON); @@ -1248,7 +1334,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; @@ -1276,17 +1362,18 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo // move pinned windows for (auto const& w : g_pCompositor->m_windows) { if (w->m_workspace == POLDWORKSPACE && w->m_pinned) - w->moveToWorkspace(pWorkspace); + w->layoutTarget()->assignToSpace(pWorkspace->m_space); } - if (!noFocus && !g_pCompositor->m_lastMonitor->m_activeSpecialWorkspace && - !(g_pCompositor->m_lastWindow.lock() && g_pCompositor->m_lastWindow->m_pinned && g_pCompositor->m_lastWindow->m_monitor == m_self)) { + if (!noFocus && !Desktop::focusState()->monitor()->m_activeSpecialWorkspace && + !(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) { static auto PFOLLOWMOUSE = CConfigValue("input:follow_mouse"); auto pWindow = pWorkspace->m_hasFullscreenWindow ? pWorkspace->getFullscreenWindow() : pWorkspace->getLastFocusedWindow(); 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(); @@ -1295,17 +1382,23 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo pWindow = pWorkspace->getFirstWindow(); } - g_pCompositor->focusWindow(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(); @@ -1346,17 +1439,23 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { g_pDesktopAnimationManager->startAnimation(m_activeSpecialWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_OUT, false); g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + m_name}); + + // Reset layer surface state when closing special workspace + for (auto const& ls : g_pCompositor->m_layers) { + if (ls->m_monitor == m_self) + ls->m_aboveFullscreen = false; + } } m_activeSpecialWorkspace.reset(); if (POLDSPECIAL) POLDSPECIAL->m_events.activeChanged.emit(); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id); + g_layoutManager->recalculateMonitor(m_self.lock()); - if (!(g_pCompositor->m_lastWindow.lock() && g_pCompositor->m_lastWindow->m_pinned && g_pCompositor->m_lastWindow->m_monitor == m_self)) { + if (!(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) { if (const auto PLAST = m_activeWorkspace->getLastFocusedWindow(); PLAST) - g_pCompositor->focusWindow(PLAST); + Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND); else g_pInputManager->refocus(); } @@ -1381,10 +1480,17 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { const auto PMONITORWORKSPACEOWNER = pWorkspace->m_monitor.lock(); if (const auto PMWSOWNER = pWorkspace->m_monitor.lock(); PMWSOWNER && PMWSOWNER->m_activeSpecialWorkspace == pWorkspace) { PMWSOWNER->m_activeSpecialWorkspace.reset(); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PMWSOWNER->m_id); + g_layoutManager->recalculateMonitor(PMWSOWNER); + g_pHyprRenderer->damageMonitor(PMWSOWNER); g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + PMWSOWNER->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + PMWSOWNER->m_name}); + // Reset layer surfaces on the old monitor when special workspace is stolen + for (auto const& ls : g_pCompositor->m_layers) { + if (ls->m_monitor == PMWSOWNER) + ls->m_aboveFullscreen = false; + } + const auto PACTIVEWORKSPACE = PMWSOWNER->m_activeWorkspace; g_pDesktopAnimationManager->setFullscreenFadeAnimation(PACTIVEWORKSPACE, PACTIVEWORKSPACE && PACTIVEWORKSPACE->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : @@ -1398,6 +1504,12 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { m_activeSpecialWorkspace = pWorkspace; m_activeSpecialWorkspace->m_visible = true; + // Reset layer surface state when opening special workspace + for (auto const& ls : g_pCompositor->m_layers) { + if (ls->m_monitor == m_self) + ls->m_aboveFullscreen = false; + } + if (POLDSPECIAL) POLDSPECIAL->m_events.activeChanged.emit(); @@ -1428,17 +1540,16 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { } else pos = pos - PMONFROMMIDDLE->m_position + m_position; - *w->m_realPosition = pos; - w->m_position = pos; + w->layoutTarget()->setPositionGlobal(CBox{pos, w->layoutTarget()->position().size()}); } } } - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id); + g_layoutManager->recalculateMonitor(m_self.lock()); - if (!(g_pCompositor->m_lastWindow.lock() && g_pCompositor->m_lastWindow->m_pinned && g_pCompositor->m_lastWindow->m_monitor == m_self)) { + if (!(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) { if (const auto PLAST = pWorkspace->getLastFocusedWindow(); PLAST) - g_pCompositor->focusWindow(PLAST); + Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND); else g_pInputManager->refocus(); } @@ -1464,37 +1575,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() { @@ -1509,6 +1607,10 @@ CBox CMonitor::logicalBox() { return {m_position, m_size}; } +CBox CMonitor::logicalBoxMinusReserved() { + return m_reservedArea.apply(logicalBox()); +} + void CMonitor::scheduleDone() { if (m_doneScheduled) return; @@ -1542,7 +1644,7 @@ uint32_t CMonitor::isSolitaryBlocked(bool full) { return reasons; } - if (g_pHyprError->active() && g_pCompositor->m_lastMonitor == m_self) { + if (g_pHyprError->active() && Desktop::focusState()->monitor() == m_self) { reasons |= SC_ERRORBAR; if (!full) return reasons; @@ -1673,7 +1775,7 @@ 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; } } @@ -1681,7 +1783,7 @@ uint8_t CMonitor::isTearingBlocked(bool full) { if (g_pHyprOpenGL->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; } } @@ -1689,11 +1791,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; @@ -1735,12 +1841,6 @@ uint16_t CMonitor::isDSBlocked(bool full) { } } - if (m_tearingState.activelyTearing) { - reasons |= DS_BLOCK_TEARING; - if (!full) - return reasons; - } - if (!m_mirrors.empty() || isMirror()) { reasons |= DS_BLOCK_MIRROR; if (!full) @@ -1779,21 +1879,27 @@ uint16_t CMonitor::isDSBlocked(bool full) { // we can't scanout shm buffers. const auto params = PSURFACE->m_current.buffer->dmabuf(); - if (!params.success || !PSURFACE->m_current.texture->m_eglImage /* dmabuf */) { + if (!params.success || !PSURFACE->m_current.texture->isDMA() /* dmabuf */) { reasons |= DS_BLOCK_DMA; if (!full) return reasons; } - if (needsCM() && *PNONSHADER != CM_NS_IGNORE && !canNoShaderCM() && (!inHDR() || (PSURFACE->m_colorManagement.valid() && PSURFACE->m_colorManagement->isWindowsScRGB())) && - *PPASS != 1) + const bool surfaceIsHDR = PSURFACE->m_colorManagement.valid() && PSURFACE->m_colorManagement->isHDR(); + const bool surfaceIsScRGB = surfaceIsHDR && PSURFACE->m_colorManagement->isWindowsScRGB(); + + if (needsCM() && (*PNONSHADER != CM_NS_IGNORE || surfaceIsScRGB) && !canNoShaderCM() && + ((inHDR() && (*PPASS == 0 || !surfaceIsHDR || surfaceIsScRGB)) || (!inHDR() && (*PPASS != 1 || surfaceIsHDR)))) reasons |= DS_BLOCK_CM; return reasons; } bool CMonitor::attemptDirectScanout() { - const auto blockedReason = isDSBlocked(); + static const auto PSAME = CConfigValue("debug:ds_handle_same_buffer"); + static const auto PSAMEFIFO = CConfigValue("debug:ds_handle_same_buffer_fifo"); + + const auto blockedReason = isDSBlocked(); if (blockedReason) return false; @@ -1801,23 +1907,23 @@ bool CMonitor::attemptDirectScanout() { 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); + 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); 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; } @@ -1826,7 +1932,7 @@ bool CMonitor::attemptDirectScanout() { } //#TODO this entire bit is bootleg deluxe, above bit is to not make vrr go down the drain, returning early here means fifo gets forever locked. - if (PSURFACE->m_fifo) + if (PSURFACE->m_fifo && !m_tearingState.activelyTearing && *PSAMEFIFO) PSURFACE->m_stateQueue.unlockFirst(LOCK_REASON_FIFO); return true; @@ -1845,33 +1951,44 @@ bool CMonitor::attemptDirectScanout() { } 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"); + 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 (!DRM::sameGpu(m_output->getBackend()->preferredAllocator()->drmFD(), g_pCompositor->m_drm.fd) && g_pHyprOpenGL->explicitSyncSupported()) { + auto sync = CEGLSync::create(); + + if (sync->fd().isValid()) { + m_inFence = sync->takeFd(); + m_output->state->setExplicitInFence(m_inFence.get()); + } else + m_output->state->resetExplicitFences(); // good luck. + } else + m_output->state->resetExplicitFences(); // no need to do explicit sync here as surface current can only ever be ready to read 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; @@ -1927,7 +2044,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 @@ -1941,7 +2058,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; } @@ -1958,8 +2075,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() { @@ -1971,7 +2088,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 @@ -1981,11 +2098,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) { @@ -2001,8 +2129,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() { @@ -2014,20 +2150,50 @@ 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()) : DEFAULT_IMAGE_DESCRIPTION; +} + +NColorManagement::SPCPRimaries CMonitor::getMasteringPrimaries() { + return m_output->parsedEDID.chromaticityCoords.has_value() ? + NColorManagement::SPCPRimaries{ + .red = {.x = m_output->parsedEDID.chromaticityCoords->red.x, .y = m_output->parsedEDID.chromaticityCoords->red.y}, + .green = {.x = m_output->parsedEDID.chromaticityCoords->green.x, .y = m_output->parsedEDID.chromaticityCoords->green.y}, + .blue = {.x = m_output->parsedEDID.chromaticityCoords->blue.x, .y = m_output->parsedEDID.chromaticityCoords->blue.y}, + .white = {.x = m_output->parsedEDID.chromaticityCoords->white.x, .y = m_output->parsedEDID.chromaticityCoords->white.y}, + } : + NColorManagement::SPCPRimaries{}; +} + +NColorManagement::SImageDescription::SPCMasteringLuminances CMonitor::getMasteringLuminances() { + return { + .min = m_minLuminance >= 0 ? m_minLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredContentMinLuminance : 0), + .max = m_maxLuminance >= 0 ? m_maxLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredContentMaxLuminance : 0), + }; } bool CMonitor::needsCM() { @@ -2048,22 +2214,93 @@ bool CMonitor::canNoShaderCM() { if (SRC_DESC.value() == m_imageDescription) 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; + + const auto sdrEOTF = NTransferFunction::fromConfig(); // 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 ( + (SRC_DESC_VALUE.transferFunction == m_imageDescription->value().transferFunction || + (sdrEOTF == NTransferFunction::TF_FORCED_GAMMA22 && SRC_DESC_VALUE.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB && + m_imageDescription->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22)) && + SRC_DESC_VALUE.transferFunctionPower == m_imageDescription->value().transferFunctionPower && + (!inHDR() || SRC_DESC_VALUE.luminances == m_imageDescription->value().luminances) + // not used by shaders atm + // && SRC_DESC_VALUE.masteringLuminances == m_imageDescription->value().masteringLuminances && SRC_DESC_VALUE.maxCLL == m_imageDescription->value().maxCLL && SRC_DESC_VALUE.maxFALL == m_imageDescription->value().maxFALL + ); } bool CMonitor::doesNoShaderCM() { return m_noShaderCTM; } +static std::vector resampleInterleavedToKms(const SVCGTTable16& t, size_t gammaSize) { + std::vector out; + out.resize(gammaSize * 3); + + // + auto sample = [&](int c, float x) -> uint16_t { + const float maxX = t.entries - 1; + x = std::clamp(x, 0.F, maxX); + + const size_t i0 = (size_t)std::floor(x); + const size_t i1 = std::min(i0 + 1, (size_t)t.entries - 1); + const float f = x - sc(i0); + + const float v0 = sc(t.ch[c][i0]); + const float v1 = sc(t.ch[c][i1]); + const float v = v0 + ((v1 - v0) * f); + + int64_t vi = std::round(v); + vi = std::clamp(vi, sc(0), sc(65535)); + return sc(vi); + }; + + for (size_t i = 0; i < gammaSize; ++i) { + float x = sc(i) * sc(t.entries - 1) / sc(gammaSize - 1); + + const uint16_t r = sample(0, x); + const uint16_t g = sample(1, x); + const uint16_t b = sample(2, x); + + out[i * 3 + 0] = r; + out[i * 3 + 1] = g; + out[i * 3 + 2] = b; + } + + return out; +} + +void CMonitor::updateVCGTRamps() { + auto gammaSize = m_output->getGammaSize(); + + if (gammaSize <= 10) { + Log::logger->log(Log::DEBUG, "CMonitor::updateVCGTRamps: skipping, no gamma ramp for output"); + return; + } + + if (!m_imageDescription->value().icc.vcgt) { + if (m_vcgtRampsSet) + m_output->state->setGammaLut({}); + + m_vcgtRampsSet = false; + return; + } + + // build table + auto table = resampleInterleavedToKms(*m_imageDescription->value().icc.vcgt, gammaSize); + + m_output->state->setGammaLut(table); + + m_vcgtRampsSet = true; +} + +bool CMonitor::gammaRampsInUse() { + return m_vcgtRampsSet; +} + CMonitorState::CMonitorState(CMonitor* owner) : m_owner(owner) { ; } @@ -2071,7 +2308,7 @@ CMonitorState::CMonitorState(CMonitor* owner) : m_owner(owner) { void CMonitorState::ensureBufferPresent() { 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; } @@ -2082,7 +2319,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 } @@ -2091,7 +2328,7 @@ bool CMonitorState::commit() { if (!updateSwapchain()) return false; - EMIT_HOOK_EVENT("preMonitorCommit", m_owner->m_self.lock()); + Event::bus()->m_events.monitor.preCommit.emit(m_owner->m_self.lock()); ensureBufferPresent(); @@ -2113,7 +2350,7 @@ bool CMonitorState::updateSwapchain() { 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; } options.format = m_owner->m_drmFormat; diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 5be6a7eb..7467467a 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -11,16 +11,20 @@ #include "CMType.hpp" #include +#include "MonitorZoomController.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 "../helpers/TransferFunction.hpp" + class CMonitorFrameScheduler; // Enum for the different types of auto directions, e.g. auto-left, auto-up. @@ -37,25 +41,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 @@ -108,11 +114,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 +127,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; @@ -173,6 +180,7 @@ class CMonitor { // for direct scanout PHLWINDOWREF m_lastScanout; + bool m_directScanoutIsActive = false; // for cleanup logic. m_lastScanout.expired() can become true before the DS cleanup if client crashes/exits while DS is active. bool m_scanoutNeedsCursorUpdate = false; // for special fade/blur @@ -203,6 +211,7 @@ class CMonitor { } m_tearingState; struct { + CSignalT<> commit; CSignalT<> destroy; CSignalT<> connect; CSignalT<> disconnect; @@ -228,9 +237,8 @@ class CMonitor { DS_BLOCK_SURFACE = (1 << 8), DS_BLOCK_TRANSFORM = (1 << 9), DS_BLOCK_DMA = (1 << 10), - DS_BLOCK_TEARING = (1 << 11), - DS_BLOCK_FAILED = (1 << 12), - DS_BLOCK_CM = (1 << 13), + DS_BLOCK_FAILED = (1 << 11), + DS_BLOCK_CM = (1 << 12), DS_CHECKS_COUNT = 14, }; @@ -271,14 +279,15 @@ class CMonitor { TC_SUPPORT = (1 << 4), TC_CANDIDATE = (1 << 5), TC_WINDOW = (1 << 6), + TC_HW_CURSOR = (1 << 7), - TC_CHECKS_COUNT = 7, + TC_CHECKS_COUNT = 8, }; // methods void onConnect(bool noRule); void onDisconnect(bool destroy = false); - void applyCMType(NCMType::eCMType cmType, int cmSdrEotf); + void applyCMType(NCMType::eCMType cmType, NTransferFunction::eTF cmSdrEotf); bool applyMonitorRule(SMonitorRule* pMonitorRule, bool force = false); void addDamage(const pixman_region32_t* rg); void addDamage(const CRegion& rg); @@ -294,10 +303,10 @@ class CMonitor { void setSpecialWorkspace(const WORKSPACEID& id); void moveTo(const Vector2D& pos); Vector2D middle(); - void updateMatrix(); WORKSPACEID activeWorkspaceID(); WORKSPACEID activeSpecialWorkspaceID(); CBox logicalBox(); + CBox logicalBoxMinusReserved(); void scheduleDone(); uint32_t isSolitaryBlocked(bool full = false); void recheckSolitary(); @@ -316,26 +325,41 @@ class CMonitor { float minLuminance(float defaultValue = 0); int maxLuminance(int defaultValue = 80); int maxAvgLuminance(int defaultValue = 80); + float maxFALL(); + float maxCLL(); bool wantsWideColor(); bool wantsHDR(); bool inHDR(); + bool gammaRampsInUse(); - /// Has an active workspace with a real fullscreen window - bool inFullscreenMode(); - std::optional getFSImageDescription(); + // + const Mat3x3& getTransformMatrix(); + const Mat3x3& getScaleMatrix(); - bool needsCM(); + /// Has an active workspace with a real fullscreen window (includes special workspace) + bool inFullscreenMode(); + /// Get fullscreen window from active or special workspace + PHLWINDOW getFullscreenWindow(); + std::optional getFSImageDescription(); + + NColorManagement::SPCPRimaries getMasteringPrimaries(); + NColorManagement::SImageDescription::SPCMasteringLuminances getMasteringLuminances(); + + bool needsCM(); /// Can do CM without shader bool canNoShaderCM(); bool doesNoShaderCM(); 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; + + NColorManagement::PImageDescription m_imageDescription = NColorManagement::CImageDescription::from(NColorManagement::SImageDescription{}); + bool m_noShaderCTM = false; // sets drm CTM, restore needed // For the list lookup @@ -343,16 +367,19 @@ 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); + Mat3x3 m_projMatrix; private: + void updateMatrix(); + Mat3x3 m_projOutputMatrix; + void setupDefaultWS(const SMonitorRule&); WORKSPACEID findAvailableDefaultWS(); void commitDPMSState(bool state); + void updateVCGTRamps(); bool m_doneScheduled = false; + bool m_vcgtRampsSet = false; std::stack m_prevWorkSpaces; struct { @@ -364,8 +391,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 1e8a81e5..648e6dec 100644 --- a/src/helpers/MonitorFrameScheduler.cpp +++ b/src/helpers/MonitorFrameScheduler.cpp @@ -11,7 +11,7 @@ CMonitorFrameScheduler::CMonitorFrameScheduler(PHLMONITOR m) : m_monitor(m) { bool CMonitorFrameScheduler::newSchedulingEnabled() { static auto PENABLENEW = CConfigValue("render:new_render_scheduling"); - return *PENABLENEW && g_pHyprOpenGL->explicitSyncSupported(); + return *PENABLENEW && g_pHyprOpenGL->explicitSyncSupported() && m_monitor && !m_monitor->m_directScanoutIsActive; } void CMonitorFrameScheduler::onSyncFired() { @@ -24,12 +24,12 @@ void CMonitorFrameScheduler::onSyncFired() { if (std::chrono::duration_cast(hrc::now() - m_lastRenderBegun).count() / 1000.F < 1000.F / m_monitor->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.", m_monitor->m_name); m_renderAtFrame = true; return; } - Debug::log(TRACE, "CMonitorFrameScheduler: {} -> onSyncFired, missed.", m_monitor->m_name); + Log::logger->log(Log::TRACE, "CMonitorFrameScheduler: {} -> onSyncFired, missed.", m_monitor->m_name); // we are out. The frame is taking too long to render. Begin rendering immediately, but don't commit yet. m_pendingThird = true; @@ -56,11 +56,11 @@ void CMonitorFrameScheduler::onPresented() { 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.", m_monitor->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.", m_monitor->m_name); m_pendingThird = false; @@ -101,11 +101,11 @@ void CMonitorFrameScheduler::onFrame() { } 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.", m_monitor->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.", m_monitor->m_name); m_lastRenderBegun = hrc::now(); @@ -132,7 +132,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/MonitorZoomController.cpp b/src/helpers/MonitorZoomController.cpp new file mode 100644 index 00000000..d90f416f --- /dev/null +++ b/src/helpers/MonitorZoomController.cpp @@ -0,0 +1,97 @@ +#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 SCurrentRenderData& m_renderData) { + const auto m = m_renderData.pMonitor; + auto monbox = CBox(0, 0, m->m_size.x, m->m_size.y); + const auto ZOOM = 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 SCurrentRenderData& m_renderData) { + static auto PZOOMRIGID = CConfigValue("cursor:zoom_rigid"); + static auto PZOOMDETACHEDCAMERA = CConfigValue("cursor:zoom_detached_camera"); + const auto ZOOM = 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 = 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 00000000..4f7c9d7a --- /dev/null +++ b/src/helpers/MonitorZoomController.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "./math/Math.hpp" + +struct SCurrentRenderData; + +class CMonitorZoomController { + public: + bool m_resetCameraState = true; + + void applyZoomTransform(CBox& monbox, const SCurrentRenderData& m_renderData); + + private: + void zoomWithDetachedCamera(CBox& result, const SCurrentRenderData& 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 d914eecf..b6c207d8 100644 --- a/src/helpers/SdDaemon.cpp +++ b/src/helpers/SdDaemon.cpp @@ -38,10 +38,10 @@ int NSystemd::sdNotify(int unsetEnvironment, const char* state) { if (!addr) return 0; - // address length must be at most this; see man 7 unix - size_t addrLen = strnlen(addr, 107); + struct sockaddr_un unixAddr = {0}; + + size_t addrLen = strnlen(addr, sizeof(unixAddr.sun_path) - 1); - struct sockaddr_un unixAddr; unixAddr.sun_family = AF_UNIX; strncpy(unixAddr.sun_path, addr, addrLen); if (unixAddr.sun_path[0] == '@') diff --git a/src/helpers/TagKeeper.cpp b/src/helpers/TagKeeper.cpp index 3c7071d5..7f377657 100644 --- a/src/helpers/TagKeeper.cpp +++ b/src/helpers/TagKeeper.cpp @@ -1,6 +1,6 @@ #include "TagKeeper.hpp" -bool CTagKeeper::isTagged(const std::string& tag, bool strict) { +bool CTagKeeper::isTagged(const std::string& tag, bool strict) const { const bool NEGATIVE = tag.starts_with("negative"); const auto MATCH = NEGATIVE ? tag.substr(9) : tag; const bool TAGGED = m_tags.contains(MATCH) || (!strict && m_tags.contains(MATCH + "*")); @@ -38,6 +38,6 @@ bool CTagKeeper::applyTag(const std::string& tag, bool dynamic) { return true; } -bool CTagKeeper::removeDynamicTags() { - return std::erase_if(m_tags, [](const auto& tag) { return tag.ends_with("*"); }); +bool CTagKeeper::removeDynamicTag(const std::string& s) { + return std::erase_if(m_tags, [&s](const auto& tag) { return tag == s + "*"; }); } diff --git a/src/helpers/TagKeeper.hpp b/src/helpers/TagKeeper.hpp index f4732005..d18a0d29 100644 --- a/src/helpers/TagKeeper.hpp +++ b/src/helpers/TagKeeper.hpp @@ -5,9 +5,9 @@ class CTagKeeper { public: - bool isTagged(const std::string& tag, bool strict = false); + bool isTagged(const std::string& tag, bool strict = false) const; bool applyTag(const std::string& tag, bool dynamic = false); - bool removeDynamicTags(); + bool removeDynamicTag(const std::string& tag); const auto& getTags() const { return m_tags; diff --git a/src/helpers/TransferFunction.cpp b/src/helpers/TransferFunction.cpp new file mode 100644 index 00000000..074f4b19 --- /dev/null +++ b/src/helpers/TransferFunction.cpp @@ -0,0 +1,38 @@ +#include "TransferFunction.hpp" +#include "../config/ConfigValue.hpp" +#include "../event/EventBus.hpp" +#include +#include +#include + +using namespace NTransferFunction; + +static std::unordered_map const table = {{"default", TF_DEFAULT}, {"0", TF_DEFAULT}, {"auto", TF_AUTO}, {"srgb", TF_SRGB}, + {"3", TF_SRGB}, {"gamma22", TF_GAMMA22}, {"1", TF_GAMMA22}, {"gamma22force", TF_FORCED_GAMMA22}, + {"2", TF_FORCED_GAMMA22}}; + +eTF NTransferFunction::fromString(const std::string tfName) { + auto it = table.find(tfName); + if (it == table.end()) + return TF_DEFAULT; + return it->second; +} + +std::string NTransferFunction::toString(eTF tf) { + for (const auto& [key, value] : table) { + if (value == tf) + return key; + } + return ""; +} + +eTF NTransferFunction::fromConfig(bool useICC) { + if (useICC) + return TF_SRGB; + + static auto PSDREOTF = CConfigValue("render:cm_sdr_eotf"); + static auto sdrEOTF = NTransferFunction::fromString(*PSDREOTF); + static auto P = Event::bus()->m_events.config.reloaded.listen([]() { sdrEOTF = NTransferFunction::fromString(*PSDREOTF); }); + + return sdrEOTF; +} diff --git a/src/helpers/TransferFunction.hpp b/src/helpers/TransferFunction.hpp new file mode 100644 index 00000000..ae575158 --- /dev/null +++ b/src/helpers/TransferFunction.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +namespace NTransferFunction { + enum eTF : uint8_t { + TF_DEFAULT = 0, + TF_AUTO = 1, + TF_SRGB = 2, + TF_GAMMA22 = 3, + TF_FORCED_GAMMA22 = 4, + }; + + eTF fromString(const std::string tfName); + std::string toString(eTF tf); + + eTF fromConfig(bool useICC = false); +} diff --git a/src/helpers/WLClasses.hpp b/src/helpers/WLClasses.hpp index 9a22b77f..ec073787 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 00000000..bac9f25a --- /dev/null +++ b/src/helpers/cm/ColorManagement.cpp @@ -0,0 +1,193 @@ +#include "ColorManagement.hpp" +#include "../../macros.hpp" +#include +#include +#include + +using namespace NColorManagement; + +namespace NColorManagement { + // expected to be small + static std::vector> knownPrimaries; + static std::vector> knownDescriptions; + static std::map, Hyprgraphics::CMatrix3> primariesConversion; +} + +const SPCPRimaries& NColorManagement::getPrimaries(ePrimaries name) { + switch (name) { + case CM_PRIMARIES_SRGB: return NColorPrimaries::BT709; + case CM_PRIMARIES_BT2020: return NColorPrimaries::BT2020; + case CM_PRIMARIES_PAL_M: return NColorPrimaries::PAL_M; + case CM_PRIMARIES_PAL: return NColorPrimaries::PAL; + case CM_PRIMARIES_NTSC: return NColorPrimaries::NTSC; + case CM_PRIMARIES_GENERIC_FILM: return NColorPrimaries::GENERIC_FILM; + case CM_PRIMARIES_CIE1931_XYZ: return NColorPrimaries::CIE1931_XYZ; + case CM_PRIMARIES_DCI_P3: return NColorPrimaries::DCI_P3; + case CM_PRIMARIES_DISPLAY_P3: return NColorPrimaries::DISPLAY_P3; + case CM_PRIMARIES_ADOBE_RGB: return NColorPrimaries::ADOBE_RGB; + default: return NColorPrimaries::DEFAULT_PRIMARIES; + } +} + +CPrimaries::CPrimaries(const SPCPRimaries& primaries, const uint32_t primariesId) : m_id(primariesId), m_primaries(primaries) { + m_primaries2XYZ = m_primaries.toXYZ(); +} + +WP CPrimaries::from(const SPCPRimaries& primaries) { + for (const auto& known : knownPrimaries) { + if (known->value() == primaries) + return known; + } + + knownPrimaries.emplace_back(CUniquePointer(new CPrimaries(primaries, knownPrimaries.size() + 1))); + return knownPrimaries.back(); +} + +WP CPrimaries::from(const ePrimaries name) { + return from(getPrimaries(name)); +} + +WP CPrimaries::from(const uint32_t primariesId) { + ASSERT(primariesId <= knownPrimaries.size()); + return knownPrimaries[primariesId - 1]; +} + +const SPCPRimaries& CPrimaries::value() const { + return m_primaries; +} + +uint CPrimaries::id() const { + return m_id; +} + +const Hyprgraphics::CMatrix3& CPrimaries::toXYZ() const { + return m_primaries2XYZ; +} + +const Hyprgraphics::CMatrix3& CPrimaries::convertMatrix(const WP dst) const { + const auto cacheKey = std::make_pair(m_id, dst->m_id); + if (!primariesConversion.contains(cacheKey)) + primariesConversion.insert(std::make_pair(cacheKey, m_primaries.convertMatrix(dst->m_primaries))); + + return primariesConversion[cacheKey]; +} + +CImageDescription::CImageDescription(const SImageDescription& imageDescription, const uint32_t imageDescriptionId) : + m_id(imageDescriptionId), m_imageDescription(imageDescription) { + m_primariesId = CPrimaries::from(m_imageDescription.getPrimaries())->id(); +} + +PImageDescription CImageDescription::from(const SImageDescription& imageDescription) { + for (const auto& known : knownDescriptions) { + if (known->value() == imageDescription) + return known; + } + + knownDescriptions.emplace_back(UP(new CImageDescription(imageDescription, knownDescriptions.size() + 1))); + return knownDescriptions.back(); +} + +PImageDescription CImageDescription::from(const uint32_t imageDescriptionId) { + ASSERT(imageDescriptionId <= knownDescriptions.size()); + return knownDescriptions[imageDescriptionId - 1]; +} + +PImageDescription CImageDescription::with(const SImageDescription::SPCLuminances& luminances) const { + auto desc = m_imageDescription; + desc.luminances = luminances; + return CImageDescription::from(desc); +} + +const SImageDescription& CImageDescription::value() const { + return m_imageDescription; +} + +uint CImageDescription::id() const { + return m_id; +} + +WP CImageDescription::getPrimaries() const { + return CPrimaries::from(m_primariesId); +} + +static Mat3x3 diag3(const std::array& s) { + return Mat3x3{std::array{s[0], 0, 0, 0, s[1], 0, 0, 0, s[2]}}; +} + +static std::optional invertMat3(const Mat3x3& m) { + const auto ARR = m.getMatrix(); + const double a = ARR[0], b = ARR[1], c = ARR[2]; + const double d = ARR[3], e = ARR[4], f = ARR[5]; + const double g = ARR[6], h = ARR[7], i = ARR[8]; + + const double A = (e * i - f * h); + const double B = -(d * i - f * g); + const double C = (d * h - e * g); + const double D = -(b * i - c * h); + const double E = (a * i - c * g); + const double F = -(a * h - b * g); + const double G = (b * f - c * e); + const double H = -(a * f - c * d); + const double I = (a * e - b * d); + + const double det = a * A + b * B + c * C; + if (std::abs(det) < 1e-18) + return std::nullopt; + + const double invDet = 1.0 / det; + Mat3x3 inv{std::array{ + A * invDet, + D * invDet, + G * invDet, // + B * invDet, + E * invDet, + H * invDet, // + C * invDet, + F * invDet, + I * invDet, // + }}; + return inv; +} + +static std::array matByVec(const Mat3x3& M, const std::array& v) { + const auto ARR = M.getMatrix(); + return {ARR[0] * v[0] + ARR[1] * v[1] + ARR[2] * v[2], ARR[3] * v[0] + ARR[4] * v[1] + ARR[5] * v[2], ARR[6] * v[0] + ARR[7] * v[1] + ARR[8] * v[2]}; +} + +std::optional NColorManagement::rgbToXYZFromPrimaries(SPCPRimaries pr) { + const auto R = Hyprgraphics::xy2xyz(pr.red); + const auto G = Hyprgraphics::xy2xyz(pr.green); + const auto B = Hyprgraphics::xy2xyz(pr.blue); + const auto W = Hyprgraphics::xy2xyz(pr.white); + + // P has columns R,G,B + Mat3x3 P{std::array{R.x, G.x, B.x, R.y, G.y, B.y, R.z, G.z, B.z}}; + + auto invP = invertMat3(P); + if (!invP) + return std::nullopt; + + const auto S = matByVec(*invP, {W.x, W.y, W.z}); + + P.multiply(diag3(S)); // RGB->XYZ + + return P; +} + +Mat3x3 NColorManagement::adaptBradford(Hyprgraphics::CColor::xy srcW, Hyprgraphics::CColor::xy dstW) { + static const Mat3x3 Bradford{std::array{0.8951f, 0.2664f, -0.1614f, -0.7502f, 1.7135f, 0.0367f, 0.0389f, -0.0685f, 1.0296f}}; + static const Mat3x3 BradfordInv = invertMat3(Bradford).value(); + + const auto srcXYZ = Hyprgraphics::xy2xyz(srcW); + const auto dstXYZ = Hyprgraphics::xy2xyz(dstW); + + const auto srcLMS = matByVec(Bradford, {srcXYZ.x, srcXYZ.y, srcXYZ.z}); + const auto dstLMS = matByVec(Bradford, {dstXYZ.x, dstXYZ.y, dstXYZ.z}); + + const std::array scale{dstLMS[0] / srcLMS[0], dstLMS[1] / srcLMS[1], dstLMS[2] / srcLMS[2]}; + + Mat3x3 result = BradfordInv; + result.multiply(diag3(scale)).multiply(Bradford); + + return result; +} \ No newline at end of file diff --git a/src/protocols/types/ColorManagement.hpp b/src/helpers/cm/ColorManagement.hpp similarity index 55% rename from src/protocols/types/ColorManagement.hpp rename to src/helpers/cm/ColorManagement.hpp index 80cea49f..0103e2a4 100644 --- a/src/protocols/types/ColorManagement.hpp +++ b/src/helpers/cm/ColorManagement.hpp @@ -3,13 +3,22 @@ #include "color-management-v1.hpp" #include #include "../../helpers/memory/Memory.hpp" +#include "../../helpers/math/Math.hpp" + +#include +#include +#include #define SDR_MIN_LUMINANCE 0.2 #define SDR_MAX_LUMINANCE 80.0 +#define SDR_REF_LUMINANCE 80.0 #define HDR_MIN_LUMINANCE 0.005 #define HDR_MAX_LUMINANCE 10000.0 +#define HDR_REF_LUMINANCE 203.0 #define HLG_MAX_LUMINANCE 1000.0 +class ITexture; + namespace NColorManagement { enum eNoShader : uint8_t { CM_NS_DISABLE = 0, @@ -65,7 +74,6 @@ namespace NColorManagement { using SPCPRimaries = Hyprgraphics::SPCPRimaries; namespace NColorPrimaries { - static const auto DEFAULT_PRIMARIES = SPCPRimaries{}; static const auto BT709 = SPCPRimaries{ .red = {.x = 0.64, .y = 0.33}, @@ -73,30 +81,37 @@ namespace NColorManagement { .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}, @@ -104,7 +119,12 @@ namespace NColorManagement { .white = {.x = 0.3127, .y = 0.3290}, }; - // FIXME CIE1931_XYZ + 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}, @@ -119,6 +139,7 @@ namespace NColorManagement { .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}, @@ -127,32 +148,52 @@ namespace NColorManagement { }; } - const SPCPRimaries& getPrimaries(ePrimaries name); + struct SVCGTTable16 { + uint16_t channels = 0; + uint16_t entries = 0; + uint16_t entrySize = 0; + std::array, 3> ch; + }; + + const SPCPRimaries& getPrimaries(ePrimaries name); + std::optional rgbToXYZFromPrimaries(SPCPRimaries pr); + Mat3x3 adaptBradford(Hyprgraphics::CColor::xy srcW, Hyprgraphics::CColor::xy dstW); + + class CPrimaries { + public: + 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 { - uint32_t id = 0; // FIXME needs id setting + static std::expected fromICC(const std::filesystem::path& file); - struct SIccFile { - int fd = -1; - uint32_t length = 0; - uint32_t offset = 0; - bool operator==(const SIccFile& i2) const { - return fd == i2.fd; - } - } icc; + // + std::vector rawICC; - bool windowsScRGB = false; + eTransferFunction transferFunction = CM_TRANSFER_FUNCTION_GAMMA22; + float transferFunctionPower = 1.0f; + bool windowsScRGB = false; - eTransferFunction transferFunction = CM_TRANSFER_FUNCTION_SRGB; - float transferFunctionPower = 1.0f; - - bool primariesNameSet = false; - ePrimaries primariesNamed = CM_PRIMARIES_SRGB; + bool primariesNameSet = false; + ePrimaries primariesNamed = CM_PRIMARIES_SRGB; // primaries are stored as FP values with the same scale as standard defines (0.0 - 1.0) // wayland protocol expects int32_t values multiplied by 1000000 - // 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² @@ -173,14 +214,26 @@ namespace NColorManagement { } } masteringLuminances; + // Matrix data from ICC + struct SICCData { + bool present = false; + size_t lutSize = 33; + std::vector lutDataPacked; + SP lutTexture; + std::optional vcgt; + } icc; + uint32_t maxCLL = 0; uint32_t maxFALL = 0; bool operator==(const SImageDescription& d2) const { - return (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); + 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 { @@ -194,9 +247,9 @@ namespace NColorManagement { case CM_TRANSFER_FUNCTION_EXT_LINEAR: return 0; case CM_TRANSFER_FUNCTION_ST2084_PQ: case CM_TRANSFER_FUNCTION_HLG: return HDR_MIN_LUMINANCE; + case CM_TRANSFER_FUNCTION_BT1886: return 0.01; case CM_TRANSFER_FUNCTION_GAMMA22: case CM_TRANSFER_FUNCTION_GAMMA28: - case CM_TRANSFER_FUNCTION_BT1886: case CM_TRANSFER_FUNCTION_ST240: case CM_TRANSFER_FUNCTION_LOG_100: case CM_TRANSFER_FUNCTION_LOG_316: @@ -214,9 +267,9 @@ namespace NColorManagement { return SDR_MAX_LUMINANCE; // assume Windows scRGB. white color range 1.0 - 125.0 maps to SDR_MAX_LUMINANCE (80) - HDR_MAX_LUMINANCE (10000) case CM_TRANSFER_FUNCTION_ST2084_PQ: return HDR_MAX_LUMINANCE; case CM_TRANSFER_FUNCTION_HLG: return HLG_MAX_LUMINANCE; + case CM_TRANSFER_FUNCTION_BT1886: return 100; case CM_TRANSFER_FUNCTION_GAMMA22: case CM_TRANSFER_FUNCTION_GAMMA28: - case CM_TRANSFER_FUNCTION_BT1886: case CM_TRANSFER_FUNCTION_ST240: case CM_TRANSFER_FUNCTION_LOG_100: case CM_TRANSFER_FUNCTION_LOG_316: @@ -228,8 +281,71 @@ namespace NColorManagement { } }; - uint32_t findId() const; - uint32_t getId() const; - uint32_t updateId(); + 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; + } + }; }; -} \ No newline at end of file + + class CImageDescription { + public: + static WP from(const SImageDescription& imageDescription); + static WP from(const uint32_t imageDescriptionId); + + WP with(const SImageDescription::SPCLuminances& luminances) const; + + const SImageDescription& value() const; + uint32_t id() const; + + WP getPrimaries() const; + + private: + CImageDescription(const SImageDescription& imageDescription, const uint imageDescriptionId); + uint32_t m_id = 0; + uint32_t m_primariesId = 0; + SImageDescription m_imageDescription; + }; + + using PImageDescription = WP; + + static const auto DEFAULT_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{ + .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_SRGB, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_SRGB), + .luminances = {.min = SDR_MIN_LUMINANCE, .max = 80, .reference = 80}, + }); + + static const auto DEFAULT_HDR_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{ + .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020), + .luminances = {.min = HDR_MIN_LUMINANCE, .max = 10000, .reference = 203}, + }); + + static const auto SCRGB_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{ + .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 = SCRGB_IMAGE_DESCRIPTION; // TODO any reason to use something different? +} diff --git a/src/helpers/cm/ICC.cpp b/src/helpers/cm/ICC.cpp new file mode 100644 index 00000000..00140c62 --- /dev/null +++ b/src/helpers/cm/ICC.cpp @@ -0,0 +1,278 @@ +#include "ColorManagement.hpp" +#include "../math/Math.hpp" +#include +#include + +#include "../../debug/log/Logger.hpp" +#include "../../render/Texture.hpp" +#include "../../render/Renderer.hpp" + +#include +using namespace Hyprutils::Utils; + +#include + +using namespace NColorManagement; + +static std::vector readBinary(const std::filesystem::path& file) { + std::ifstream ifs(file, std::ios::binary); + if (!ifs.good()) + return {}; + + ifs.seekg(0, std::ios::end); + size_t len = ifs.tellg(); + ifs.seekg(0, std::ios::beg); + + if (len <= 0) + return {}; + + std::vector buf; + buf.resize(len); + ifs.read(reinterpret_cast(buf.data()), len); + + return buf; +} + +static uint16_t bigEndianU16(const uint8_t* p) { + return (uint16_t)((uint16_t)p[0] << 8 | (uint16_t)p[1]); +} + +static uint32_t bigEndianU32(const uint8_t* p) { + return (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | (uint32_t)p[3]; +} + +static constexpr cmsTagSignature makeSig(char a, char b, char c, char d) { + return sc(sc(a) << 24 | sc(b) << 16 | sc(c) << 8 | sc(d)); +} + +static constexpr cmsTagSignature VCGT_SIG = makeSig('v', 'c', 'g', 't'); + +// + +static std::expected, std::string> readVCGT16(cmsHPROFILE prof) { + if (!cmsIsTag(prof, VCGT_SIG)) + return std::nullopt; + + cmsUInt32Number n = cmsReadRawTag(prof, VCGT_SIG, nullptr, 0); + if (n < 8 + 4 + 2 + 2 + 2 + 2) // header + type + table header + return std::unexpected("Malformed vcgt tag"); + + std::vector raw(n); + if (cmsReadRawTag(prof, VCGT_SIG, raw.data(), n) != n) + return std::unexpected("Malformed vcgt tag"); + + // raw layout: + // 0 ... 3: 'vcgt' + // 4 ... 7: reserved + // 8 ... 11: gammaType (0 = table) + uint32_t gammaType = bigEndianU32(raw.data() + 8); + if (gammaType != 0) + return std::unexpected("VCGT formula type is not supported by Hyprland"); + + SVCGTTable16 table; + table.channels = bigEndianU16(raw.data() + 12); + table.entries = bigEndianU16(raw.data() + 14); + table.entrySize = bigEndianU16(raw.data() + 16); + // raw+18: reserved u16 + + Log::logger->log(Log::DEBUG, "readVCGT16: table has {} channels, {} entries, and entry size of {}", table.channels, table.entries, table.entrySize); + + if (table.channels != 3 || table.entrySize != 2 || table.entries == 0) + return std::unexpected("invalid vcgt table size"); + + size_t tableBytes = (size_t)table.channels * (size_t)table.entries * (size_t)table.entrySize; + + // VCGT is a piece of shit and some absolute fucking mongoloid idiots + // decided it'd be great to have both 18 and 20 + // FUCK YOU + size_t tableOff = 20; + + auto readTable = [&] -> void { + for (int c = 0; c < 3; ++c) { + table.ch[c].resize(table.entries); + for (uint16_t i = 0; i < table.entries; ++i) { + const uint8_t* p = raw.data() + tableOff + static_cast((c * table.entries + i) * 2); + table.ch[c][i] = bigEndianU16(p); // 0 ... 65535 + } + } + }; + + if (raw.size() < tableOff + tableBytes) { + tableOff = 18; + + if (raw.size() < tableOff + tableBytes) { + Log::logger->log(Log::ERR, "readVCGT16: table is too short, tag is invalid"); + return std::unexpected("table is too short"); + } + + Log::logger->log(Log::DEBUG, "readVCGT16: table is too short, but off = 18 fits. Attempting offset = 18"); + + readTable(); + } else { + readTable(); + + // if the table's last entry is suspiciously low, we more than likely read an 18 as a 20. + if (table.ch[0][table.entries - 1] < 30000) { + Log::logger->log(Log::DEBUG, "readVCGT16: table is likely offset 18 not 20, re-reading"); + + tableOff = 18; + + readTable(); + } + } + + if (table.ch[0][table.entries - 1] < 30000) { + Log::logger->log(Log::ERR, "readVCGT16: table is malformed, last value of a gamma ramp can't be {}", table.ch[0][table.entries - 1]); + return std::unexpected("invalid table values"); + } + + Log::logger->log(Log::DEBUG, "readVCGT16: red channel: [{}, {}, ... {}, {}]", table.ch[0][0], table.ch[0][1], table.ch[0][table.entries - 2], table.ch[0][table.entries - 1]); + Log::logger->log(Log::DEBUG, "readVCGT16: green channel: [{}, {}, ... {}, {}]", table.ch[1][0], table.ch[1][1], table.ch[1][table.entries - 2], table.ch[1][table.entries - 1]); + Log::logger->log(Log::DEBUG, "readVCGT16: blue channel: [{}, {}, ... {}, {}]", table.ch[2][0], table.ch[2][1], table.ch[2][table.entries - 2], table.ch[2][table.entries - 1]); + + return table; +} + +struct CmsProfileDeleter { + void operator()(cmsHPROFILE p) const { + if (p) + cmsCloseProfile(p); + } +}; +struct CmsTransformDeleter { + void operator()(cmsHTRANSFORM t) const { + if (t) + cmsDeleteTransform(t); + } +}; + +using UniqueProfile = std::unique_ptr, CmsProfileDeleter>; +using UniqueTransform = std::unique_ptr, CmsTransformDeleter>; + +static UniqueProfile createLinearSRGBProfile() { + cmsCIExyYTRIPLE prim{}; + // sRGB / Rec.709 primaries + prim.Red.x = 0.6400; + prim.Red.y = 0.3300; + prim.Red.Y = 1.0; + prim.Green.x = 0.3000; + prim.Green.y = 0.6000; + prim.Green.Y = 1.0; + prim.Blue.x = 0.1500; + prim.Blue.y = 0.0600; + prim.Blue.Y = 1.0; + + cmsCIExyY wp{}; + wp.x = 0.3127; + wp.y = 0.3290; + wp.Y = 1.0; // D65 + + cmsToneCurve* lin = cmsBuildGamma(nullptr, 1.0); + cmsToneCurve* curves[3] = {lin, lin, lin}; + + cmsHPROFILE p = cmsCreateRGBProfile(&wp, &prim, curves); + + cmsFreeToneCurve(lin); + return UniqueProfile{p}; +} + +static std::expected buildIcc3DLut(cmsHPROFILE profile, SImageDescription& image) { + UniqueProfile src = createLinearSRGBProfile(); + if (!src) + return std::unexpected("Failed to create linear sRGB profile"); + + // Rendering intent: RELATIVE_COLORIMETRIC is common for displays; add BPC to be safe. + const int intent = INTENT_RELATIVE_COLORIMETRIC; + const cmsUInt32Number flags = cmsFLAGS_BLACKPOINTCOMPENSATION | cmsFLAGS_HIGHRESPRECALC; // good quality precalc in LCMS + + // float->float transform (linear input, encoded output in dst device space) + UniqueTransform xform{cmsCreateTransform(src.get(), TYPE_RGB_FLT, profile, TYPE_RGB_FLT, intent, flags)}; + if (!xform) + return std::unexpected("Failed to create ICC transform"); + + Log::logger->log(Log::DEBUG, "Building a {}³ 3D LUT", image.icc.lutSize); + + image.icc.present = true; + image.icc.lutDataPacked.resize(image.icc.lutSize * image.icc.lutSize * image.icc.lutSize * 3); + + auto idx = [&image](int r, int g, int b) -> size_t { + // + return ((size_t)b * image.icc.lutSize * image.icc.lutSize + (size_t)g * image.icc.lutSize + (size_t)r) * 3; + }; + + for (size_t bz = 0; bz < image.icc.lutSize; ++bz) { + for (size_t gy = 0; gy < image.icc.lutSize; ++gy) { + for (size_t rx = 0; rx < image.icc.lutSize; ++rx) { + float in[3] = { + rx / float(image.icc.lutSize - 1), + gy / float(image.icc.lutSize - 1), + bz / float(image.icc.lutSize - 1), + }; + float outRGB[3]; + cmsDoTransform(xform.get(), in, outRGB, 1); + + outRGB[0] = std::clamp(outRGB[0], 0.F, 1.F); + outRGB[1] = std::clamp(outRGB[1], 0.F, 1.F); + outRGB[2] = std::clamp(outRGB[2], 0.F, 1.F); + + const size_t o = idx(rx, gy, bz); + image.icc.lutDataPacked[o + 0] = outRGB[0]; + image.icc.lutDataPacked[o + 1] = outRGB[1]; + image.icc.lutDataPacked[o + 2] = outRGB[2]; + } + } + } + + Log::logger->log(Log::DEBUG, "3D LUT constructed, size {}", image.icc.lutDataPacked.size()); + + // upload + image.icc.lutTexture = g_pHyprRenderer->createTexture(image.icc.lutDataPacked, image.icc.lutSize); + + return {}; +} + +std::expected SImageDescription::fromICC(const std::filesystem::path& file) { + static auto PVCGTENABLED = CConfigValue("render:icc_vcgt_enabled"); + + std::error_code ec; + if (!std::filesystem::exists(file, ec) || ec) + return std::unexpected("Invalid file"); + + SImageDescription image; + image.rawICC = readBinary(file); + + if (image.rawICC.empty()) + return std::unexpected("Failed to read file"); + + cmsHPROFILE prof = cmsOpenProfileFromFile(file.string().c_str(), "r"); + if (!prof) + return std::unexpected("CMS failed to open icc file"); + + CScopeGuard x([&prof] { cmsCloseProfile(prof); }); + + // only handle RGB (typical display profiles) + if (cmsGetColorSpace(prof) != cmsSigRgbData) + return std::unexpected("Only RGB display profiles are supported"); + + Log::logger->log(Log::DEBUG, "============= Begin ICC load ============="); + Log::logger->log(Log::DEBUG, "ICC size: {} bytes", image.rawICC.size()); + + if (const auto RET = buildIcc3DLut(prof, image); !RET) + return std::unexpected(RET.error()); + + if (*PVCGTENABLED) { + auto vcgtRes = readVCGT16(prof); + if (!vcgtRes) + return std::unexpected(vcgtRes.error()); + + image.icc.vcgt = *vcgtRes; + + if (!*vcgtRes) + Log::logger->log(Log::DEBUG, "ICC profile has no VCGT data"); + } else + Log::logger->log(Log::DEBUG, "Skipping VCGT load, disabled by config"); + + Log::logger->log(Log::DEBUG, "============= End ICC load ============="); + + return image; +} \ No newline at end of file diff --git a/src/helpers/env/Env.cpp b/src/helpers/env/Env.cpp new file mode 100644 index 00000000..606d5f72 --- /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 00000000..030fe736 --- /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 0bc2e685..60af7d44 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 00000000..e69de29b diff --git a/src/helpers/math/Direction.hpp b/src/helpers/math/Direction.hpp new file mode 100644 index 00000000..9905db4f --- /dev/null +++ b/src/helpers/math/Direction.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +namespace Math { + enum eDirection : int8_t { + DIRECTION_DEFAULT = -1, + DIRECTION_UP, + DIRECTION_RIGHT, + DIRECTION_DOWN, + DIRECTION_LEFT + }; + + inline eDirection fromChar(char x) { + switch (x) { + case 'r': return DIRECTION_RIGHT; + case 'l': return DIRECTION_LEFT; + case 't': + case 'u': return DIRECTION_UP; + case 'b': + case 'd': return DIRECTION_DOWN; + default: return DIRECTION_DEFAULT; + } + } + + inline const char* toString(eDirection d) { + switch (d) { + case DIRECTION_UP: return "up"; + case DIRECTION_DOWN: return "down"; + case DIRECTION_LEFT: return "left"; + case DIRECTION_RIGHT: return "right"; + default: return "default"; + } + } +}; \ No newline at end of file diff --git a/src/helpers/math/Expression.cpp b/src/helpers/math/Expression.cpp new file mode 100644 index 00000000..3c0bee91 --- /dev/null +++ b/src/helpers/math/Expression.cpp @@ -0,0 +1,22 @@ +#include "Expression.hpp" +#include "muParser.h" +#include "../../debug/log/Logger.hpp" + +using namespace Math; + +CExpression::CExpression() : m_parser(makeUnique()) { + ; +} + +void CExpression::addVariable(const std::string& name, double val) { + m_parser->DefineConst(name, val); +} + +std::optional CExpression::compute(const std::string& expr) { + try { + m_parser->SetExpr(expr); + return m_parser->Eval(); + } 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/Expression.hpp b/src/helpers/math/Expression.hpp new file mode 100644 index 00000000..1780e3ee --- /dev/null +++ b/src/helpers/math/Expression.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "../memory/Memory.hpp" +#include +#include + +namespace mu { + class Parser; +}; + +namespace Math { + class CExpression { + public: + CExpression(); + ~CExpression() = default; + + CExpression(const CExpression&) = delete; + CExpression(CExpression&) = delete; + CExpression(CExpression&&) = delete; + + void addVariable(const std::string& name, double val); + + std::optional compute(const std::string& expr); + + private: + UP m_parser; + }; +}; \ No newline at end of file diff --git a/src/helpers/math/Math.cpp b/src/helpers/math/Math.cpp index f927701c..d10997b5 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 367d8190..cc181434 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 66d7667f..fbc585d0 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 9fe2e406..5a233e48 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 791f5ea1..f454b784 100644 --- a/src/helpers/time/Time.cpp +++ b/src/helpers/time/Time.cpp @@ -5,7 +5,6 @@ using s_ns = std::pair; -// HAS to be a > b static s_ns timediff(const s_ns& a, const s_ns& b) { s_ns d; @@ -13,7 +12,7 @@ static s_ns timediff(const s_ns& a, const s_ns& b) { if (a.second >= b.second) d.second = a.second - b.second; else { - d.second = b.second - a.second; + d.second = (TIMESPEC_NSEC_PER_SEC + a.second) - b.second; d.first -= 1; } @@ -46,9 +45,9 @@ uint64_t Time::millis(const steady_tp& tp) { } s_ns Time::secNsec(const steady_tp& tp) { - const uint64_t sec = chr::duration_cast(tp.time_since_epoch()).count(); - const chr::steady_clock::duration nsecdur = tp - chr::steady_clock::time_point(chr::seconds(sec)); - return std::make_pair<>(sec, chr::duration_cast(nsecdur).count()); + const uint64_t sec = chr::duration_cast(tp.time_since_epoch()).count(); + const auto nsecdur = tp - chr::steady_clock::time_point(chr::seconds(sec)); + return {sec, chr::duration_cast(nsecdur).count()}; } uint64_t Time::millis(const system_tp& tp) { @@ -56,9 +55,9 @@ uint64_t Time::millis(const system_tp& tp) { } s_ns Time::secNsec(const system_tp& tp) { - const uint64_t sec = chr::duration_cast(tp.time_since_epoch()).count(); - const chr::steady_clock::duration nsecdur = tp - chr::system_clock::time_point(chr::seconds(sec)); - return std::make_pair<>(sec, chr::duration_cast(nsecdur).count()); + const uint64_t sec = chr::duration_cast(tp.time_since_epoch()).count(); + const auto nsecdur = tp - chr::system_clock::time_point(chr::seconds(sec)); + return {sec, chr::duration_cast(nsecdur).count()}; } // TODO: this is a mess, but C++ doesn't define what steady_clock is. @@ -69,12 +68,12 @@ s_ns Time::secNsec(const system_tp& tp) { // In general, this may shift the time around by a couple hundred ns. Doesn't matter, realistically. Time::steady_tp Time::fromTimespec(const timespec* ts) { - struct timespec mono, real; + timespec mono{}, real{}; clock_gettime(CLOCK_MONOTONIC, &mono); clock_gettime(CLOCK_REALTIME, &real); - Time::steady_tp now = Time::steadyNow(); - Time::system_tp nowSys = Time::systemNow(); - s_ns stdSteady, stdReal; + auto now = Time::steadyNow(); + auto nowSys = Time::systemNow(); + s_ns stdSteady, stdReal; stdSteady = Time::secNsec(now); stdReal = Time::secNsec(nowSys); @@ -84,7 +83,7 @@ Time::steady_tp Time::fromTimespec(const timespec* ts) { if (real.tv_nsec >= mono.tv_nsec) diff.second = real.tv_nsec - mono.tv_nsec; else { - diff.second = mono.tv_nsec - real.tv_nsec; + diff.second = TIMESPEC_NSEC_PER_SEC + real.tv_nsec - mono.tv_nsec; diff.first -= 1; } @@ -104,7 +103,7 @@ Time::steady_tp Time::fromTimespec(const timespec* ts) { } struct timespec Time::toTimespec(const steady_tp& tp) { - struct timespec mono, real; + timespec mono{}, real{}; clock_gettime(CLOCK_MONOTONIC, &mono); clock_gettime(CLOCK_REALTIME, &real); Time::steady_tp now = Time::steadyNow(); @@ -119,7 +118,7 @@ struct timespec Time::toTimespec(const steady_tp& tp) { if (real.tv_nsec >= mono.tv_nsec) diff.second = real.tv_nsec - mono.tv_nsec; else { - diff.second = mono.tv_nsec - real.tv_nsec; + diff.second = TIMESPEC_NSEC_PER_SEC + real.tv_nsec - mono.tv_nsec; diff.first -= 1; } @@ -137,3 +136,10 @@ struct timespec Time::toTimespec(const steady_tp& tp) { auto sum = timeadd(tpTime, diffFinal); return timespec{.tv_sec = sum.first, .tv_nsec = sum.second}; } + +Time::steady_dur Time::till(const timespec& ts) { + timespec mono{}; + clock_gettime(CLOCK_MONOTONIC, &mono); + const auto delay = (ts.tv_sec - mono.tv_sec) * 1000000000 + (ts.tv_nsec - mono.tv_nsec); + return std::chrono::nanoseconds(delay); +} diff --git a/src/helpers/time/Time.hpp b/src/helpers/time/Time.hpp index eb3b5771..ce99982b 100644 --- a/src/helpers/time/Time.hpp +++ b/src/helpers/time/Time.hpp @@ -17,6 +17,7 @@ namespace Time { steady_tp fromTimespec(const timespec*); struct timespec toTimespec(const steady_tp& tp); + steady_dur till(const timespec& ts); uint64_t millis(const steady_tp& tp); uint64_t millis(const system_tp& tp); diff --git a/src/helpers/varlist/VarList.hpp b/src/helpers/varlist/VarList.hpp index 4cdc1728..ca68751e 100644 --- a/src/helpers/varlist/VarList.hpp +++ b/src/helpers/varlist/VarList.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include //NOLINTNEXTLINE using namespace Hyprutils::String; diff --git a/src/hyprerror/HyprError.cpp b/src/hyprerror/HyprError.cpp index ffb716c4..60bf0a78 100644 --- a/src/hyprerror/HyprError.cpp +++ b/src/hyprerror/HyprError.cpp @@ -6,7 +6,8 @@ #include "../render/pass/TexPassElement.hpp" #include "../managers/animation/AnimationManager.hpp" #include "../render/Renderer.hpp" -#include "../managers/HookSystemManager.hpp" +#include "../desktop/state/FocusState.hpp" +#include "../event/EventBus.hpp" #include using namespace Hyprutils::Animation; @@ -14,23 +15,21 @@ using namespace Hyprutils::Animation; CHyprError::CHyprError() { g_pAnimationManager->createAnimation(0.f, m_fadeOpacity, g_pConfigManager->getAnimationPropertyConfig("fadeIn"), AVARDAMAGE_NONE); - static auto P = g_pHookSystem->hookDynamic("focusedMon", [&](void* self, SCallbackInfo& info, std::any param) { + static auto P = Event::bus()->m_events.monitor.focused.listen([&](PHLMONITOR mon) { if (!m_isCreated) return; - g_pHyprRenderer->damageMonitor(g_pCompositor->m_lastMonitor.lock()); + g_pHyprRenderer->damageMonitor(Desktop::focusState()->monitor()); m_monitorChanged = true; }); - static auto P2 = g_pHookSystem->hookDynamic("preRender", [&](void* self, SCallbackInfo& info, std::any param) { + static auto P2 = Event::bus()->m_events.render.pre.listen([&](PHLMONITOR mon) { if (!m_isCreated) return; if (m_fadeOpacity->isBeingAnimated() || m_monitorChanged) g_pHyprRenderer->damageBox(m_damageBox); }); - - m_texture = makeShared(); } void CHyprError::queueCreate(std::string message, const CHyprColor& color) { @@ -39,8 +38,8 @@ void CHyprError::queueCreate(std::string message, const CHyprColor& color) { } void CHyprError::createQueued() { - if (m_isCreated) - m_texture->destroyTexture(); + if (m_isCreated && m_texture) + m_texture.reset(); m_fadeOpacity->setConfig(g_pConfigManager->getAnimationPropertyConfig("fadeIn")); @@ -82,7 +81,8 @@ void CHyprError::createQueued() { const double X = PAD; const double Y = TOPBAR ? PAD : PMONITOR->m_pixelSize.y - HEIGHT - PAD; - m_damageBox = {0, 0, sc(PMONITOR->m_pixelSize.x), sc(HEIGHT) + sc(PAD) * 2}; + m_damageBox = {sc(PMONITOR->m_position.x), sc(PMONITOR->m_position.y + (TOPBAR ? 0 : PMONITOR->m_pixelSize.y - (HEIGHT + PAD * 2))), sc(PMONITOR->m_pixelSize.x), + sc(HEIGHT + PAD * 2)}; cairo_new_sub_path(CAIRO); cairo_arc(CAIRO, X + WIDTH - RADIUS, Y + RADIUS, RADIUS, -90 * DEGREES, 0 * DEGREES); @@ -110,6 +110,8 @@ void CHyprError::createQueued() { pango_font_description_set_style(pangoFD, PANGO_STYLE_NORMAL); pango_font_description_set_weight(pangoFD, PANGO_WEIGHT_NORMAL); pango_layout_set_font_description(layoutText, pangoFD); + pango_layout_set_width(layoutText, (WIDTH - 2 * (1 + RADIUS)) * PANGO_SCALE); + pango_layout_set_ellipsize(layoutText, PANGO_ELLIPSIZE_END); float yoffset = TOPBAR ? 0 : Y - PAD; int renderedcnt = 0; @@ -131,9 +133,8 @@ void CHyprError::createQueued() { pango_layout_set_text(layoutText, moreString.c_str(), -1); pango_cairo_show_layout(CAIRO, layoutText); } - m_queued = ""; - m_lastHeight = yoffset + PAD + 1 - (TOPBAR ? 0 : Y - PAD); + m_lastHeight = HEIGHT; pango_font_description_free(pangoFD); g_object_unref(layoutText); @@ -142,12 +143,13 @@ void CHyprError::createQueued() { // copy the data to an OpenGL texture we have const auto DATA = cairo_image_surface_get_data(CAIROSURFACE); - m_texture->allocate(); - m_texture->bind(); - m_texture->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); - m_texture->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); - m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); - m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); + auto tex = texture(); + tex->allocate(PMONITOR->m_pixelSize); + tex->bind(); + tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); + tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); + tex->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); + tex->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA); @@ -161,7 +163,16 @@ void CHyprError::createQueued() { g_pHyprRenderer->damageMonitor(PMONITOR); - g_pHyprRenderer->arrangeLayersForMonitor(PMONITOR->m_id); + for (const auto& m : g_pCompositor->m_monitors) { + m->m_reservedArea.resetType(Desktop::RESERVED_DYNAMIC_TYPE_ERROR_BAR); + } + + const auto RESERVED = (HEIGHT + PAD) / SCALE; + PMONITOR->m_reservedArea.addType(Desktop::RESERVED_DYNAMIC_TYPE_ERROR_BAR, Vector2D{0.0, TOPBAR ? RESERVED : 0.0}, Vector2D{0.0, !TOPBAR ? RESERVED : 0.0}); + + for (const auto& m : g_pCompositor->m_monitors) { + g_pHyprRenderer->arrangeLayersForMonitor(m->m_id); + } } void CHyprError::draw() { @@ -175,12 +186,14 @@ void CHyprError::draw() { if (!m_fadeOpacity->isBeingAnimated()) { if (m_fadeOpacity->value() == 0.f) { m_queuedDestroy = false; - m_texture->destroyTexture(); + if (m_texture) + m_texture.reset(); m_isCreated = false; m_queued = ""; for (auto& m : g_pCompositor->m_monitors) { g_pHyprRenderer->arrangeLayersForMonitor(m->m_id); + m->m_reservedArea.resetType(Desktop::RESERVED_DYNAMIC_TYPE_ERROR_BAR); } return; @@ -191,12 +204,13 @@ void CHyprError::draw() { } } - const auto PMONITOR = g_pHyprOpenGL->m_renderData.pMonitor; + const auto PMONITOR = g_pHyprOpenGL->m_renderData.pMonitor; - CBox monbox = {0, 0, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y}; + CBox monbox = {0, 0, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y}; - m_damageBox.x = sc(PMONITOR->m_position.x); - m_damageBox.y = sc(PMONITOR->m_position.y); + static auto BAR_POSITION = CConfigValue("debug:error_position"); + m_damageBox.x = sc(PMONITOR->m_position.x); + m_damageBox.y = sc(PMONITOR->m_position.y + (*BAR_POSITION == 0 ? 0 : PMONITOR->m_pixelSize.y - m_damageBox.height)); if (m_fadeOpacity->isBeingAnimated() || m_monitorChanged) g_pHyprRenderer->damageBox(m_damageBox); @@ -204,7 +218,7 @@ void CHyprError::draw() { m_monitorChanged = false; CTexPassElement::SRenderData data; - data.tex = m_texture; + data.tex = texture(); data.box = monbox; data.a = m_fadeOpacity->value(); @@ -225,3 +239,9 @@ bool CHyprError::active() { float CHyprError::height() { return m_lastHeight; } + +SP CHyprError::texture() { + if (!m_texture) + m_texture = g_pHyprRenderer->createTexture(); + return m_texture; +} \ No newline at end of file diff --git a/src/hyprerror/HyprError.hpp b/src/hyprerror/HyprError.hpp index f4bc43d8..48b9e805 100644 --- a/src/hyprerror/HyprError.hpp +++ b/src/hyprerror/HyprError.hpp @@ -18,13 +18,16 @@ class CHyprError { bool active(); float height(); // logical + // + SP texture(); + private: void createQueued(); std::string m_queued = ""; CHyprColor m_queuedColor; bool m_queuedDestroy = false; bool m_isCreated = false; - SP m_texture; + SP m_texture; PHLANIMVAR m_fadeOpacity; CBox m_damageBox = {0, 0, 0, 0}; float m_lastHeight = 0.F; diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp new file mode 100644 index 00000000..c68400eb --- /dev/null +++ b/src/i18n/Engine.cpp @@ -0,0 +1,1693 @@ +#include "Engine.hpp" + +#include +#include "../config/ConfigValue.hpp" + +using namespace I18n; +using namespace Hyprutils::I18n; + +static SP huEngine; +static std::string localeStr; + +// +SP I18n::i18nEngine() { + static SP engine = makeShared(); + return engine; +} + +I18n::CI18nEngine::CI18nEngine() { + huEngine = makeShared(); + huEngine->setFallbackLocale("en_US"); + localeStr = huEngine->getSystemLocale().locale(); + + // be_BY (Belarusian) + huEngine->registerEntry("be_BY", TXT_KEY_ANR_TITLE, "Праграма не адказвае"); + huEngine->registerEntry("be_BY", TXT_KEY_ANR_CONTENT, "Праграма {title} - {class} не адказвае.\nШто хочаце з ёй зрабіць?"); + huEngine->registerEntry("be_BY", TXT_KEY_ANR_OPTION_TERMINATE, "Прымусова спыніць"); + huEngine->registerEntry("be_BY", TXT_KEY_ANR_OPTION_WAIT, "Пачакаць"); + huEngine->registerEntry("be_BY", TXT_KEY_ANR_PROP_UNKNOWN, "(невядома)"); + + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Праграма {app} запытвае невядомы дазвол."); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Праграма {app} спрабуе здымаць экран.\n\nЦі хочаце дазволіць?"); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Праграма {app} спрабуе загрузіць плагін: {plugin}.\n\nХочаце дазволіць?"); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Выяўленая новая клавіятура: {keyboard}.\n\nХочаце дазволіць яе выкарыстанне?"); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(невядома)"); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_TITLE, "Запыт дазволу"); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Падказка: вы можаце задаць пастаянныя правілы для гэтага ў файле канфігурацыі Hyprland."); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_ALLOW, "Дазволіць"); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Дазволіць і запомніць"); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_ALLOW_ONCE, "Дазволіць аднойчы"); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_DENY, "Забараніць"); + huEngine->registerEntry("be_BY", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Невядомая праграма (Ідэнтыфікатар кліента wayland {wayland_id})"); + + huEngine->registerEntry("be_BY", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Выглядае, што вашая пераменная асяроддзя XDG_CURRENT_DESKTOP зададзеная звонку, цяперашняе значэнне: {value}.\nГэта можа выклікаць праблемы, калі " + "гэта не зроблена наўмысна."); + huEngine->registerEntry("be_BY", TXT_KEY_NOTIF_NO_GUIUTILS, + "У вашай сістэме не ўсталяваны hyprland-guiutils, што выкарыстоўваецца для некаторых дыялогавых вокнаў. Разгледзьце ўсталёўку пакета."); + huEngine->registerEntry("be_BY", 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("be_BY", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Макет манітораў наладжаны некарэктна. Манітор {name} накладаецца на іншы(я) манітор(ы).\nДля падрабязнасцей звярніцеся да Wiki (Старонка Monitors). " + "Гэта абавязкова створыць праблемы."); + huEngine->registerEntry("be_BY", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Манітор {name} не змог наладзіць ніводны з запатрабаваных рэжымаў, аварыйна ўжыты рэжым {mode}."); + huEngine->registerEntry("be_BY", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Няверна зададзены маштаб для манітора {name}: {scale}, ужываецца прапанаваны маштаб: {fixed_scale}"); + huEngine->registerEntry("be_BY", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Не атрымалася загрузіць плагін {name}: {error}"); + 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."); + + // 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?"); + huEngine->registerEntry("en_US", TXT_KEY_ANR_OPTION_TERMINATE, "Terminate"); + huEngine->registerEntry("en_US", TXT_KEY_ANR_OPTION_WAIT, "Wait"); + huEngine->registerEntry("en_US", TXT_KEY_ANR_PROP_UNKNOWN, "(unknown)"); + + 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)"); + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_TITLE, "Permission request"); + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Hint: you can set persistent rules for these in the Hyprland config file."); + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_ALLOW, "Allow"); + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Allow and remember"); + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_ALLOW_ONCE, "Allow once"); + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_DENY, "Deny"); + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Unknown application (wayland client ID {wayland_id})"); + + huEngine->registerEntry( + "en_US", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Your XDG_CURRENT_DESKTOP environment seems to be managed externally, and the current value is {value}.\nThis might cause issues unless it's intentional."); + huEngine->registerEntry("en_US", TXT_KEY_NOTIF_NO_GUIUTILS, + "Your system does not have hyprland-guiutils installed. This is a runtime dependency for some dialogs. Consider installing it."); + huEngine->registerEntry("en_US", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland failed to load {count} essential asset, blame your distro's packager for doing a bad job at packaging!"; + return "Hyprland failed to load {count} essential assets, blame your distro's packager for doing a bad job at packaging!"; + }); + huEngine->registerEntry("en_US", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Your monitor layout is set up incorrectly. Monitor {name} overlaps with other monitor(s) in the layout.\nPlease see the wiki (Monitors page) for " + "more. This will cause issues."); + huEngine->registerEntry("en_US", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Monitor {name} failed to set any requested modes, falling back to mode {mode}."); + huEngine->registerEntry("en_US", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Invalid scale passed to monitor {name}: {scale}, using suggested scale: {fixed_scale}"); + 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, "এপ্লিকেচনে উত্তৰ দিয়া নাই"); + huEngine->registerEntry("as_IN", TXT_KEY_ANR_CONTENT, "এপ্লিকেচন {title} - {class}-এ উত্তৰ দিয়া নাই।\nআপুনি এয়াৰ লগত কি কৰিব বিচাৰে?"); + huEngine->registerEntry("as_IN", TXT_KEY_ANR_OPTION_TERMINATE, "সমাপ্ত কৰক"); + huEngine->registerEntry("as_IN", TXT_KEY_ANR_OPTION_WAIT, "অপেক্ষা কৰক"); + huEngine->registerEntry("as_IN", TXT_KEY_ANR_PROP_UNKNOWN, "(অজ্ঞাত)"); + + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "এপ্লিকেচন {app}-এ এটা অজ্ঞাত অনুমতি বিচাৰিছে।"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "এটা এপ্লিকেচন {app}-এ আপোনাৰ স্ক্ৰীণ কেপচাৰ কৰিবলৈ চেষ্টা কৰিছে।\n\nআপুনি ইয়াক অনুমতি দিব বিচাৰেনে?"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_REQUEST_PLUGIN, + "এপ্লিকেচন {app}-এ এটা প্লাগিন লোড কৰিবলৈ চেষ্টা কৰিছে: {plugin}।\n\nআপুনি ইয়াক অনুমতি দিব বিচাৰেনে?"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "এটা নতুন কিবৰ্ড ধৰা পৰিছে: {keyboard}।\n\nআপুনি ইয়াক চলাবলৈ অনুমতি দিব বিচাৰেনে?"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(অজ্ঞাত)"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_TITLE, "অনুমতিৰ অনুৰোধ"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "ইঙ্গিত: আপুনি হাইপাৰলেণ্ড কনফিগ ফাইলত এইবোৰৰ বাবে স্থায়ী নিয়ম স্থাপন কৰিব পাৰে।"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_ALLOW, "অনুমতি দিয়ক"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "অনুমতি দি মনত ৰাখক"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_ALLOW_ONCE, "এবাৰ অনুমতি দিয়ক"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_DENY, "অস্বীকাৰ কৰক"); + huEngine->registerEntry("as_IN", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "অজ্ঞাত এপ্লিকেচন (ৱেইলেণ্ড ক্লায়েণ্ট আইডি {wayland_id})"); + + huEngine->registerEntry( + "as_IN", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "আপোনাৰ XDG_CURRENT_DESKTOP পৰিৱেশটো বাহ্যিকভাৱে পৰিচালিত হোৱা যেন লাগিছে, আৰু বৰ্তমানৰ মান হৈছে {value}।\nযদি ই ইচ্ছাকৃতভাৱে নহয়, তেনে হলে সমস্যাৰ সৃষ্টি হ'ব পাৰে।"); + huEngine->registerEntry("as_IN", TXT_KEY_NOTIF_NO_GUIUTILS, + "আপোনাৰ চিষ্টেমত hyprland-guiutils ইনষ্টল কৰা নাই। কিছুমান ডাইলগৰ বাবে ই এটা ৰানটাইম নিৰ্ভৰশীলতা। ইয়াক ইনষ্টল কৰাৰ কথা চিন্তা কৰক।"); + huEngine->registerEntry("as_IN", TXT_KEY_NOTIF_FAILED_ASSETS, + "হাইপাৰলেণ্ড {count}-টা প্ৰয়োজনীয় সম্পদ লোড কৰাত অসফল হৈছে, বেয়া পেকজিং কৰাৰ বাবে আপোনাৰ ডিষ্ট্ৰ'ৰ পেকেজাৰক দোষাৰোপ কৰক!"); + huEngine->registerEntry("as_IN", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "আপোনাৰ মনিটৰৰ লেআউট ভুলকৈ ছেট কৰা হৈছে। মনিটৰ {name} লেআউটত আন মনিটৰ(সমূহ)ৰ সৈতে ওপৰা-উপৰি হৈ আছে।\nঅধিক তথ্যৰ বাবে অনুগ্ৰহ কৰি ৱিকি (মনিটৰ পৃষ্ঠা) চাওক। ই " + "সমস্যাৰ সৃষ্টি কৰিব।"); + huEngine->registerEntry("as_IN", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "মনিটৰ {name}-এ কোনো অনুৰোধ কৰা মোড ছেট কৰাত অসফল হৈছে, মোড {mode}-লৈ ঘূৰি আহিছে।"); + huEngine->registerEntry("as_IN", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "মনিটৰ {name}: {scale}-লৈ অবৈধ মাপন দিয়া হৈছে, পৰামৰ্শ দিয়া মাপন ব্যৱহাৰ কৰা যাব: {fixed_scale}"); + huEngine->registerEntry("as_IN", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "প্লাগিন {name} লোড কৰাত অসফল হৈছে: {error}"); + huEngine->registerEntry("as_IN", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM শ্বেডাৰ ৰিলোড কৰাত অসফল হৈছে, rgba/rgbx-লৈ ঘূৰি আহিছে।"); + huEngine->registerEntry("as_IN", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "প্ৰসাৰিত ৰঙৰ বৰ্গ সক্ষম কৰা হৈছে কিন্তু ডিচপ্লে 10-বিট মোডত নাই।"); + + // de_DE (German) + huEngine->registerEntry("de_DE", TXT_KEY_ANR_TITLE, "Anwendung Reagiert Nicht"); + huEngine->registerEntry("de_DE", TXT_KEY_ANR_CONTENT, "Eine Anwendung {title} - {class} reagiert nicht.\nWas möchten Sie damit tun?"); + huEngine->registerEntry("de_DE", TXT_KEY_ANR_OPTION_TERMINATE, "Beenden"); + huEngine->registerEntry("de_DE", TXT_KEY_ANR_OPTION_WAIT, "Warten"); + huEngine->registerEntry("de_DE", TXT_KEY_ANR_PROP_UNKNOWN, "(unbekannt)"); + + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Eine Anwendung {app} fordert eine unbekannte Berechtigung an."); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Eine Anwendung {app} versucht Ihren Bildschrim aufzunehmen.\n\nMöchten Sie dies erlauben?"); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Eine Anwendung {app} versucht ein Plugin zu laden: {plugin}.\n\nMöchten Sie dies erlauben?"); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Eine neue Tastatur wurde erkannt: {keyboard}.\n\nMöchten Sie diese in Betrieb nehmen?"); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(unbekannt)"); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_TITLE, "Berechtigungsanfrage"); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Tip: Sie können dafür permanente Regeln in der Hyprland-Konfigurationsdatei festlegen."); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_ALLOW, "Erlauben"); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Erlauben und merken"); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_ALLOW_ONCE, "Einmal erlauben"); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_DENY, "Verweigern"); + huEngine->registerEntry("de_DE", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Unbekannte Anwendung (wayland client ID {wayland_id})"); + + huEngine->registerEntry("de_DE", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Ihre XDG_CURRENT_DESKTOP umgebung scheint extern gemanagt zu werden, und der aktuelle Wert ist {value}.\nDies könnte zu Problemen führen sofern es " + "nicht absichtlich so ist."); + huEngine->registerEntry("de_DE", TXT_KEY_NOTIF_NO_GUIUTILS, + "Ihr System hat hyprland-guiutils nicht installiert. Dies ist eine Laufzeitabhängigkeit für einige Dialoge. Es ist empfohlen diese zu installieren."); + huEngine->registerEntry("de_DE", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland konnte {count} essentielle Ressource nicht laden, geben Sie dem Packager ihrer Distribution die Schuld für ein schlechtes Package!"; + return "Hyprland konnte {count} essentielle Ressroucen nicht laden, geben Sie dem Packager ihrer Distribution die Schuld für ein schlechtes Package!"; + }); + huEngine->registerEntry( + "de_DE", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Ihr Bildschirmlayout ist fehlerhaft aufgesetzt. Der Bildschirm {name} überlappt mit anderen Bildschirm(en) im Layout.\nBitte siehe im Wiki (Monitors Seite) für " + "mehr Informationen. Dies wird zu Problemen führen."); + huEngine->registerEntry("de_DE", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Bildschirm {name} konnte keinen der angeforderten Modi setzen fällt auf den Modus {mode} zurück."); + huEngine->registerEntry("de_DE", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, + "Ungültiger Skalierungsfaktor {scale} für Bildschirm {name}, es wird der empfohlene Faktor {fixed_scale} verwendet."); + huEngine->registerEntry("de_DE", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Plugin {name} konnte nicht geladen werden: {error}"); + huEngine->registerEntry("de_DE", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM shader konnte nicht neu geladen werden und es wird auf rgba/rgbx zurückgefallen."); + huEngine->registerEntry("de_DE", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Bildschirm {name}: wide color gamut ist aktiviert aber der Bildschirm ist nicht im 10-bit Modus."); + + // de_CH (Swiss German) + huEngine->registerEntry("de_CH", TXT_KEY_ANR_TITLE, "Aawändig Reagiert Ned"); + huEngine->registerEntry("de_CH", TXT_KEY_ANR_CONTENT, "En Aawändig {title} - {class} reagiert ned.\nWas wend Sie demet mache?"); + huEngine->registerEntry("de_CH", TXT_KEY_ANR_OPTION_TERMINATE, "Beände"); + huEngine->registerEntry("de_CH", TXT_KEY_ANR_OPTION_WAIT, "Warte"); + huEngine->registerEntry("de_CH", TXT_KEY_ANR_PROP_UNKNOWN, "(onbekannt)"); + + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "En Aawändig {app} fordert en onbekannti Berächtigong aa."); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "En Aawändig {app} versuecht Ehre Beldscherm uufznäh.\n\nWend Sie das erlaube?"); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "En Aawändig {app} versuecht es Plugin z'lade: {plugin}.\n\nWend Sie das erlaube?"); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "En neui Tastatur esch erkönne worde: {keyboard}.\n\nWend sie die in Betreb nä?"); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(onbekannt)"); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_TITLE, "Berächtigongsaafrog"); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Tip: Sie chönd permanenti Regle deför i ehrere Hyprland-Konfigurationsdatei festlegge."); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_ALLOW, "Erlaube"); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Erlaube ond merke"); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_ALLOW_ONCE, "Einisch erlaube"); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_DENY, "Verweigere"); + huEngine->registerEntry("de_CH", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Onbekannti Aawändig (wayland client ID {wayland_id})"); + + huEngine->registerEntry( + "de_CH", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Ehri XDG_CURRENT_DESKTOP omgäbig schiint extern gmanagt z'wärde, ond de aktuelli Wärt esch {value}.\nDas chönnt zo Problem füehre sofärn das ned absechtlech so esch."); + huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_NO_GUIUTILS, + "Ehres System hed hyprland-guiutils ned installiert. Das esch en Laufziitabhängigkeit för es paar Dialog. Es werd empfohle sie z'installiere."); + huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_FAILED_ASSETS, + "Hyprland hed {count} essentielli Ressource ned chönne lade, gäbed Sie im Packager vo ehrere Distribution schold för es schlächts Package!"); + huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Ehres Beldschermlayout esch fählerhaft uufgsetzt. De Beldscherm {name} öberlappt met andere Beldscherm(e) im Layout.\nBitte lueged sie im Wiki " + "(Monitors Siite) för meh Informatione. Das werd zo Problem füehre."); + huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "De Beldscherm {name} hed keine vode aagforderete Modi chönne setze, ond fallt uf de Modus {mode} zrogg."); + huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, + "Ongöltige Skalierigsfaktor {scale} för de Beldscherm {name}, es werd de empfohleni Faktor {fixed_scale} verwändet."); + huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "S Plugin {name} hed ned chönne glade wärde: {error}"); + huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM shader hed ned chönne neu glade wärde, es werd uf rgba/rgbx zrogggfalle."); + huEngine->registerEntry("de_CH", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Beldscherm {name}: wide color gamut esch aktiviert aber de Beldscherm esch ned im 10-bit Modus."); + + // pt_BR (Brazilian Portuguese) + huEngine->registerEntry("pt_BR", TXT_KEY_ANR_TITLE, "O aplicativo não está respondendo"); + huEngine->registerEntry("pt_BR", TXT_KEY_ANR_CONTENT, "O aplicativo {title} - {class} não está respondendo.\nO que você deseja fazer?"); + huEngine->registerEntry("pt_BR", TXT_KEY_ANR_OPTION_TERMINATE, "Encerrar"); + huEngine->registerEntry("pt_BR", TXT_KEY_ANR_OPTION_WAIT, "Esperar"); + huEngine->registerEntry("pt_BR", TXT_KEY_ANR_PROP_UNKNOWN, "(Desconhecido)"); + + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "O aplicativo {app} está pedindo uma permissão desconhecida."); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "O aplicativo {app} está tentando capturar sua tela.\n\nVocê deseja permitir?"); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "O aplicativo {app} está tentando carregar um plugin: {plugin}.\n\nVocê deseja permitir?"); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Um novo teclado foi detectado: {keyboard}.\n\nVocê deseja permitir seu uso?"); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(Desconhecido)"); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_TITLE, "Solicitação de permissão"); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_PERSISTENCE_HINT, + "Dica: você pode definir regras persistentes para essas permissões no arquivo de configuração do Hyprland"); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_ALLOW, "Permitir"); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Permitir e lembrar"); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_ALLOW_ONCE, "Permitir uma vez"); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_DENY, "Negar"); + huEngine->registerEntry("pt_BR", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Aplicativo desconhecido (wayland client ID {wayland_id})"); + + huEngine->registerEntry("pt_BR", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Seu XDG_CURRENT_DESKTOP parece estar sendo gerenciado externamente, e atualmente é {value}.\nIsso pode causar problemas caso não seja intencional."); + huEngine->registerEntry("pt_BR", TXT_KEY_NOTIF_NO_GUIUTILS, + "Seu sistema não possui hyprland-guiutils instalado. Essa é uma dependência de execução para alguns diálogos. Considere instalá-lo."); + huEngine->registerEntry("pt_BR", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "O Hyprland falhou ao carregar {count} recurso essencial, culpe o empacotador da sua distro por fazer um péssimo trabalho!"; + return "O Hyprland falhou ao carregar {count} recursos essenciais, culpe o empacotador da sua distro por fazer um péssimo trabalho!"; + }); + huEngine->registerEntry("pt_BR", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Sua disposição de monitores está configurada incorretamente. O monitor {name} se sobrepõe a outro(s) monitor(es) na disposição.\nPor favor consulte " + "a wiki (Monitors page) para " + "mais informações. Isso vai causar problemas."); + huEngine->registerEntry("pt_BR", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "O monitor {name} falhou em definir qualquer um dos modos solicitados, voltando ao modo {mode}."); + huEngine->registerEntry("pt_BR", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, + "Um fator de escala inválido foi passado para o monitor {name}: {scale}, usando o fator sugerido: {fixed_scale}"); + huEngine->registerEntry("pt_BR", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Falha ao carregar o plugin {name}: {error}"); + huEngine->registerEntry("pt_BR", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Falha ao carregar o shader CM, voltando para rgba/rgbx."); + huEngine->registerEntry("pt_BR", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: o modo de gama de cores amplo está ativado, mas a tela não está configurada para 10 bits."); + + // es (Spanish) + huEngine->registerEntry("es", TXT_KEY_ANR_TITLE, "La aplicación no responde"); + huEngine->registerEntry("es", TXT_KEY_ANR_CONTENT, "La aplicación {title} - {class} no responde.\n¿Qué deseas hacer?"); + huEngine->registerEntry("es", TXT_KEY_ANR_OPTION_TERMINATE, "Forzar cierre"); + huEngine->registerEntry("es", TXT_KEY_ANR_OPTION_WAIT, "Esperar"); + huEngine->registerEntry("es", TXT_KEY_ANR_PROP_UNKNOWN, "(desconocido)"); + + huEngine->registerEntry("es", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Una aplicación {app} está solicitando un permiso desconocido."); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Una aplicación {app} está intentando capturar la pantalla.\n\n¿Deseas permitirlo?"); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Una aplicación {app} está intentando cargar un plugin: {plugin}.\n\n¿Deseas permitirlo?"); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Se ha detectado un nuevo teclado: {keyboard}.\n\n¿Deseas permitir su uso?"); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(desconocido)"); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_TITLE, "Solicitud de permiso"); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_PERSISTENCE_HINT, + "Sugerencia: puedes establecer reglas persistentes para estos permisos en el archivo de configuración de Hyprland."); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_ALLOW, "Permitir"); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Permitir y recordar"); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_ALLOW_ONCE, "Permitir una vez"); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_DENY, "Denegar"); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Aplicación desconocida (ID de cliente de Wayland: {wayland_id})"); + + huEngine->registerEntry( + "es", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "La variable de entorno XDG_CURRENT_DESKTOP parece gestionarse externamente; su valor actual es {value}.\nEsto podría causar problemas a menos que sea intencional."); + huEngine->registerEntry("es", TXT_KEY_NOTIF_NO_GUIUTILS, + "Tu sistema no tiene instalado 'hyprland-guiutils'. Es una dependencia en tiempo de ejecución para algunos diálogos. Considera instalarlo."); + huEngine->registerEntry("es", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "No se pudo cargar {count} recurso esencial. Contacta al empaquetador de tu distribución."; + return "No se pudieron cargar {count} recursos esenciales. Contacta al empaquetador de tu distribución."; + }); + huEngine->registerEntry("es", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "La configuración de tus monitores no es correcta. El monitor {name} se superpone con otros monitores en la disposición. Consulta la wiki (página " + "Monitors, en inglés) para más información. Esto provocará problemas."); + huEngine->registerEntry("es", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "El monitor {name} no pudo configurar ninguno de los modos solicitados y ha vuelto al modo {mode}."); + huEngine->registerEntry("es", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Se pasó una escala no válida al monitor {name}: {scale}; se usará la escala sugerida: {fixed_scale}"); + huEngine->registerEntry("es", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Error al cargar el plugin {name}: {error}"); + huEngine->registerEntry("es", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Error al recargar el shader CM; volviendo a rgba/rgbx."); + huEngine->registerEntry("es", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: la gama de color amplia está habilitada, pero la pantalla no está en modo de 10 bits."); + + // fa_IR (Persian) + huEngine->registerEntry("fa_IR", TXT_KEY_ANR_TITLE, "برنامه پاسخ نمی‌دهد"); + huEngine->registerEntry("fa_IR", TXT_KEY_ANR_CONTENT, "برنامه {title} - {class} پاسخی نمی‌دهد.\nمی‌خواهید چه کاری انجام شود؟"); + huEngine->registerEntry("fa_IR", TXT_KEY_ANR_OPTION_TERMINATE, "بستن برنامه"); + huEngine->registerEntry("fa_IR", TXT_KEY_ANR_OPTION_WAIT, "صبر کنید"); + huEngine->registerEntry("fa_IR", TXT_KEY_ANR_PROP_UNKNOWN, "(نامشخص)"); + + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "برنامه {app} در حال درخواست یک مجوز ناشناخته است."); + + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, + "برنامه {app} می‌خواهد صفحه‌نمایش شما را ضبط کند.\n\nآیا اجازه می‌دهید؟"); + + huEngine->registerEntry( + "fa_IR", TXT_KEY_PERMISSION_REQUEST_PLUGIN, + "برنامه {app} می‌خواهد پلاگین {plugin} را بارگذاری کند.\n\nآیا اجازه می‌دهید پلاگین بارگذاری " + "شود؟"); + + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, + "یک کیبورد جدید شناسایی شد: {keyboard}.\n\nآیا اجازه استفاده از آن را صادر می‌کنید؟"); + + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(نامشخص)"); + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_TITLE, "درخواست مجوز"); + + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_PERSISTENCE_HINT, + "نکته: می‌توانید قوانین دائمی مرتبط را در فایل تنظیمات هایپرلند تعریف کنید."); + + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_ALLOW, "اجازه"); + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "اجازه و ذخیره"); + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_ALLOW_ONCE, "اجازه یک‌بار"); + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_DENY, "عدم اجازه"); + + huEngine->registerEntry("fa_IR", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "برنامه ناشناخته (شناسه Wayland: {wayland_id})"); + + huEngine->registerEntry("fa_IR", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "متغیر XDG_CURRENT_DESKTOP توسط محیطی خارجی تنظیم شده است و مقدار فعلی آن {value} است.\n" + "اگر این کار عمدی نباشد ممکن است باعث ایجاد مشکل شود."); + + huEngine->registerEntry( + "fa_IR", TXT_KEY_NOTIF_NO_GUIUTILS, + "بستهٔ hyprland-guiutils در سیستم نصب نیست. این بسته برای برخی از پنجره‌ها و دیالوگ‌ها لازم است. نصب " + "آن " + "پیشنهاد " + "می‌شود."); + + huEngine->registerEntry("fa_IR", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "هایپرلند نتوانست یک فایل ضروری را بارگذاری کند؛ ممکن است بسته‌بندی توزیع مشکل داشته " + "باشد."; + return "هایپرلند نتوانست {count} فایل ضروری را بارگذاری کند؛ ممکن است بسته‌بندی توزیع مشکل داشته " + "باشد."; + }); + + huEngine->registerEntry("fa_IR", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "چیدمان مانیتورها صحیح نیست. مانیتور {name} با یک یا چند مانیتور دیگر تداخل دارد.\n" + "برای اطلاعات بیشتر به صفحهٔ مانیتورها در ویکی مراجعه کنید. این موضوع حتماً باعث مشکل " + "می‌شود."); + + huEngine->registerEntry( + "fa_IR", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, + "مانیتور {name} نتوانست هیچ‌کدام از حالت‌های درخواستی را اعمال کند؛ بازگشت به حالت {mode}."); + + huEngine->registerEntry("fa_IR", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "مقیاس واردشده برای مانیتور {name} نامعتبر است: {scale}. مقیاس پیشنهادی اعمال شد: {fixed_scale}"); + + huEngine->registerEntry("fa_IR", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "بارگذاری پلاگین {name} با خطا روبه‌رو شد: {error}"); + + huEngine->registerEntry("fa_IR", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "بارگذاری دوبارهٔ شیدر CM ناموفق بود؛ از حالت rgba/rgbx استفاده شد."); + + 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?"); + huEngine->registerEntry("fr_FR", TXT_KEY_ANR_OPTION_TERMINATE, "Forcer l'arrêt"); + huEngine->registerEntry("fr_FR", TXT_KEY_ANR_OPTION_WAIT, "Attendre"); + huEngine->registerEntry("fr_FR", TXT_KEY_ANR_PROP_UNKNOWN, "(inconnu)"); + + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Une application {app} demande une autorisation inconnue."); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Une application {app} tente de capturer votre écran.\n\nVoulez-vous l'autoriser?"); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Une application {app} tente de charger un module : {plugin}.\n\nVoulez-vous l'autoriser?"); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Un nouveau clavier a été détecté : {keyboard}.\n\nVoulez-vous l'autoriser à fonctionner?"); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(inconnu)"); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_TITLE, "Demande d'autorisation"); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Astuce: vous pouvez définir des règles persistantes dans le fichier de configuration de Hyprland."); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_ALLOW, "Autoriser"); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Autoriser et mémoriser"); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_ALLOW_ONCE, "Autoriser une fois"); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_DENY, "Refuser"); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Application inconnue (ID client wayland {wayland_id})"); + + huEngine->registerEntry("fr_FR", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Votre variable d'environnement XDG_CURRENT_DESKTOP semble être gérée de manière externe, et sa valeur actuelle est {value}.\nCela peut provoquer des " + "problèmes si ce n'est pas intentionnel."); + huEngine->registerEntry("fr_FR", TXT_KEY_NOTIF_NO_GUIUTILS, + "Votre système n'a pas hyprland-guiutils installé. C'est une dépendance d'éxécution pour certains dialogues. Envisagez de l'installer."); + huEngine->registerEntry("fr_FR", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland n'a pas pu charger {count} ressource essentielle, cela indique très probablement un problème dans le paquet de votre distribution."; + return "Hyprland n'a pas pu charger {count} ressources essentielles, cela indique très probablement un problème dans le paquet de votre distribution."; + }); + huEngine->registerEntry("fr_FR", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Votre disposition d'écrans est incorrecte. Le moniteur {name} chevauche un ou plusieurs autres.\nVeuillez consulter le wiki (page Moniteurs) pour" + "en savoir plus. Cela causera des problèmes."); + huEngine->registerEntry("fr_FR", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Le moniteur {name} n'a pu appliquer les modes demandés, retour au mode {mode}."); + huEngine->registerEntry("fr_FR", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Échelle invalide pour le moniteur {name}: {scale}. Utilisation de l'échelle suggérée: {fixed_scale}."); + huEngine->registerEntry("fr_FR", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Échec du chargement du module {name} : {error}"); + huEngine->registerEntry("fr_FR", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Le rechargement du shader CM a échoué, retour aux formats rgba/rgbx"); + huEngine->registerEntry("fr_FR", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Moniteur {name} : l'espace colorimétrique étendu est activé, mais l'écran n'est pas en mode 10-bits."); + + // hi_IN (Hindi) + huEngine->registerEntry("hi_IN", TXT_KEY_ANR_TITLE, "एप्लिकेशन प्रतिक्रिया नहीं दे रहा है"); + huEngine->registerEntry("hi_IN", TXT_KEY_ANR_CONTENT, + "एक एप्लिकेशन {title} - {class} प्रतिक्रिया नहीं दे रहा " + "है।\nआप इसके साथ क्या करना चाहेंगे?"); + huEngine->registerEntry("hi_IN", TXT_KEY_ANR_OPTION_TERMINATE, "समाप्त करें"); + huEngine->registerEntry("hi_IN", TXT_KEY_ANR_OPTION_WAIT, "इंतजार करें"); + huEngine->registerEntry("hi_IN", TXT_KEY_ANR_PROP_UNKNOWN, "(अज्ञात)"); + + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "एक एप्लिकेशन {app} एक अज्ञात अनुमति का अनुरोध कर रहा है।"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, + "एक एप्लिकेशन {app} आपकी स्क्रीन कैप्चर करने की " + "कोशिश कर रहा है।\n\nक्या आप इसे अनुमति देना चाहते हैं?"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_REQUEST_PLUGIN, + "एक एप्लिकेशन {app} एक प्लगइन लोड करने की कोशिश कर रहा है: " + "{plugin}.\n\nक्या आप इसे अनुमति देना चाहते हैं?"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, + "नया कीबोर्ड पाया गया: {keyboard}.\n\nक्या आप " + "इसे काम करने की अनुमति देना चाहते हैं?"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(अज्ञात)"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_TITLE, "अनुमति अनुरोध"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "संकेत: आप Hyprland कॉन्फ़िग फ़ाइल में इनके लिए स्थायी नियम सेट कर सकते हैं।"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_ALLOW, "अनुमति दें"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "अनुमति दें और याद रखें"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_ALLOW_ONCE, "एक बार अनुमति दें"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_DENY, "अस्वीकार करें"); + huEngine->registerEntry("hi_IN", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "अज्ञात एप्लिकेशन (wayland क्लाइंट ID {wayland_id})"); + + huEngine->registerEntry("hi_IN", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "आपका XDG_CURRENT_DESKTOP परिवेश बाहरी रूप से प्रबंधित लगता है, और वर्तमान मान " + "{value} है।\nयह समस्या पैदा कर सकता " + "है जब तक कि यह जानबूझकर न किया गया हो।"); + huEngine->registerEntry("hi_IN", TXT_KEY_NOTIF_NO_GUIUTILS, + "आपके सिस्टम में hyprland-guiutils इंस्टॉल नहीं है। यह कुछ संवादों के लिए एक रनटाइम " + "निर्भरता है। इसे इंस्टॉल करने पर विचार करें।"); + huEngine->registerEntry("hi_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("hi_IN", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "आपका मॉनिटर लेआउट गलत तरीके से सेट है। मॉनिटर {name} लेआउट में अन्य मॉनिटर(ओं) के " + "साथ ओवरलैप कर रहा है।\nकृपया विकि " + " (Monitors पेज) देखें। यह समस्याएँ पैदा करेगा।"); + huEngine->registerEntry("hi_IN", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, + "मॉनिटर {name} ने किसी भी अनुरोधित मोड को सेट करने में " + "विफल रहा, मोड {mode} पर वापस जा रहा है।"); + huEngine->registerEntry("hi_IN", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, + "मॉनिटर {name} को अवैध स्केल दिया गया: {scale}, सुझाया " + "गया स्केल इस्तेमाल किया जा रहा है: {fixed_scale}"); + huEngine->registerEntry("hi_IN", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "प्लगइन {name} लोड करने में विफल: {error}"); + 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?"); + huEngine->registerEntry("hr_HR", TXT_KEY_ANR_OPTION_TERMINATE, "Zaustavi"); + huEngine->registerEntry("hr_HR", TXT_KEY_ANR_OPTION_WAIT, "Pričekaj"); + huEngine->registerEntry("hr_HR", TXT_KEY_ANR_PROP_UNKNOWN, "(nepoznato)"); + + huEngine->registerEntry("hr_HR", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Aplikacija {app} zahtijeva nepoznatu dozvolu."); + huEngine->registerEntry("hr_HR", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Aplikacija {app} pokušava snimati vaš zaslon.\n\nŽeliš li dopustiti?"); + huEngine->registerEntry("hr_HR", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Aplikacija {app} pokušava učitati dodatak: {plugin}.\n\nŽeliš li dopustiti?"); + huEngine->registerEntry("hr_HR", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Otkrivena je nova tipkovnica: {keyboard}.\n\nŽeliš li omogućiti njen rad?"); + huEngine->registerEntry("hr_HR", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(nepoznato)"); + huEngine->registerEntry("hr_HR", TXT_KEY_PERMISSION_TITLE, "Zahtjev za dozvolu"); + huEngine->registerEntry("hr_HR", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Savjet: za ovo možeš postaviti trajna pravila u Hyprland konfiguracijskoj datoteci."); + huEngine->registerEntry("hr_HR", TXT_KEY_PERMISSION_ALLOW, "Dozvoli"); + huEngine->registerEntry("hr_HR", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Dozvoli i zapamti"); + huEngine->registerEntry("hr_HR", TXT_KEY_PERMISSION_ALLOW_ONCE, "Dozvoli samo ovaj put"); + huEngine->registerEntry("hr_HR", TXT_KEY_PERMISSION_DENY, "Uskrati"); + huEngine->registerEntry("hr_HR", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Nepoznata aplikacija (ID wayland klijenta {wayland_id})"); + + huEngine->registerEntry( + "hr_HR", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Izgleda da je tvoja XDG_CURRENT_DESKTOP okolina vanjski upravljana te je trenutna vrijednost {value}.\nOvo može izazvati problem, osim ako je namjerno."); + huEngine->registerEntry("hr_HR", TXT_KEY_NOTIF_NO_GUIUTILS, + "Na tvojem sustavu nije instaliran hyprland-guiutils. Ovo je ovisnost tijekom pokretanja nekih dijaloga. Preporučeno je da je instaliraš."); + huEngine->registerEntry("hr_HR", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo % 10 <= 1 && assetsNo % 100 != 11) + return "Hyprland nije uspio učitati {count} neophodnu komponentu, krivi pakera svoje distribucije za loš posao pakiranja!"; + else if (assetsNo % 10 <= 4 && assetsNo % 100 > 14) + return "Hyprland nije uspio učitati {count} neophodne komponente, krivi pakera svoje distribucije za loš posao pakiranja!"; + return "Hyprland nije uspio učitati {count} neophodnih komponenata, krivi pakera svoje distribucije za loš posao pakiranja!"; + }); + huEngine->registerEntry("hr_HR", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Raspored tvojih monitora je krivo postavljen. Monitor {name} preklapa se s ostalim monitorom/ima u rasporedu.\nProvjeri wiki (Monitors stranicu) za " + "više informacija. Ovo hoće izazvati probleme."); + huEngine->registerEntry("hr_HR", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Monitor {name} nije uspio odrediti zatražene načine rada, povratak na zadani način rada: {mode}."); + huEngine->registerEntry("hr_HR", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Nevažeći razmjer proslijeđen monitoru {name}: {scale}, koristi se predloženi razmjer: {fixed_scale}"); + huEngine->registerEntry("hr_HR", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Učitavanje dodatka {name} nije uspjelo: {error}"); + huEngine->registerEntry("hr_HR", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Ponovno učitavanje CM shadera nije uspjelo, povratak na zadano: rgba/rgbx."); + huEngine->registerEntry("hr_HR", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: široki raspon boja je omogućen, ali ekran nije u 10-bitnom načinu rada."); + + // it_IT (Italian) + huEngine->registerEntry("it_IT", TXT_KEY_ANR_TITLE, "L'applicazione non risponde"); + huEngine->registerEntry("it_IT", TXT_KEY_ANR_CONTENT, "Un'applicazione {title} - {class} non risponde.\nCosa vuoi fare?"); + huEngine->registerEntry("it_IT", TXT_KEY_ANR_OPTION_TERMINATE, "Termina"); + huEngine->registerEntry("it_IT", TXT_KEY_ANR_OPTION_WAIT, "Attendi"); + huEngine->registerEntry("it_IT", TXT_KEY_ANR_PROP_UNKNOWN, "(sconosciuto)"); + + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Un'applicazione {app} richiede un'autorizzazione sconosciuta."); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Un'applicazione {app} sta provando a catturare il tuo schermo.\n\nVuoi permetterglielo?"); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_REQUEST_PLUGIN, + "Un'applicazione {app} sta provando a caricare un plugin: {plugin}.\n\nVuoi permetterglielo?"); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "È stata rilevata una nuova tastiera: {keyboard}.\n\nLe vuoi permettere di operare?"); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(sconosciuto)"); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_TITLE, "Richiesta di autorizzazione"); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Consiglio: Puoi impostare una regola persistente nel tuo file di configurazione di Hyprland."); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_ALLOW, "Permetti"); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Permetti e ricorda"); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_ALLOW_ONCE, "Permetti una volta"); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_DENY, "Nega"); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Applicazione sconosciuta (wayland client ID {wayland_id})"); + + huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "L'ambiente XDG_CURRENT_DESKTOP sembra essere gestito esternamente, il valore attuale è {value}.\nSe non è voluto, potrebbe causare problemi."); + huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_NO_GUIUTILS, + "Sembra che hyprland-guiutils non sia installato. È una dipendenza richiesta per alcuni dialoghi che potresti voler installare."); + huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_FAILED_ASSETS, + "Hyprland non ha potuto caricare {count} asset, dai la colpa al packager della tua distribuzione per il suo cattivo lavoro!"); + huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "I tuoi schermi sono configurati incorrettamente. Lo schermo {name} si sovrappone con altri nel layout.\nConsulta la wiki (voce Schermi) per " + "altre informazioni. Questo causerà problemi."); + huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Lo schermo {name} non ha potuto impostare alcuna modalità richiesta, sarà usata la modalità {mode}."); + huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, + "Fattore di scala non valido per lo schermo {name}: {scale}, utilizzando il fattore suggerito: {fixed_scale}"); + huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Impossibile caricare il plugin {name}: {error}"); + huEngine->registerEntry("it_IT", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Impossibile ricaricare gli shader CM, sarà usato rgba/rgbx."); + 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_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_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_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_DENY, "却下"); + 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 がありません。このパッケージをインストールしてください。"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_FAILED_ASSETS, + "{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ģē"); + huEngine->registerEntry("lv_LV", TXT_KEY_ANR_CONTENT, "Lietotne {title} - {class} nereaģē.\nKo jūs vēlaties darīt?"); + huEngine->registerEntry("lv_LV", TXT_KEY_ANR_OPTION_TERMINATE, "Beigt procesu"); + huEngine->registerEntry("lv_LV", TXT_KEY_ANR_OPTION_WAIT, "Gaidīt"); + huEngine->registerEntry("lv_LV", TXT_KEY_ANR_PROP_UNKNOWN, "(nezināms)"); + + huEngine->registerEntry("lv_LV", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Lietotne {app} pieprasa nezināmu atļauju."); + huEngine->registerEntry("lv_LV", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Lietotne {app} mēģina lasīt no jūsu ekrāna.\n\nVai vēlaties to atļaut?"); + huEngine->registerEntry("lv_LV", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Lietotne {app} mēģina ielādēt spraudni: {plugin}.\n\nVai vēlaties to atļaut?"); + huEngine->registerEntry("lv_LV", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Ir atrasta jauna tastatūra: {keyboard}.\n\nVai vēlaties atļaut tās darbību?"); + huEngine->registerEntry("lv_LV", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(nezināms)"); + huEngine->registerEntry("lv_LV", TXT_KEY_PERMISSION_TITLE, "Atļaujas pieprasījums"); + huEngine->registerEntry("lv_LV", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Padoms: Hyprland konfigurācijas failā varat arī iestatīt atļaujas."); + huEngine->registerEntry("lv_LV", TXT_KEY_PERMISSION_ALLOW, "Atļaut"); + huEngine->registerEntry("lv_LV", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Atļaut un atcerēties"); + huEngine->registerEntry("lv_LV", TXT_KEY_PERMISSION_ALLOW_ONCE, "Atļaut vienreiz"); + huEngine->registerEntry("lv_LV", TXT_KEY_PERMISSION_DENY, "Aizliegt"); + huEngine->registerEntry("lv_LV", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Nezināma lietotne (Wayland klienta ID {wayland_id})"); + + huEngine->registerEntry("lv_LV", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Jūsu XDG_CURRENT_DESKTOP tiek ārēji pārvaldīts, tās vērtība ir {value}.\nTas var neapzināti izraisīt problēmas."); + huEngine->registerEntry("lv_LV", TXT_KEY_NOTIF_NO_GUIUTILS, "Jums nav instalēts hyprland-guiutils. Šī pakotne ir nepieciešama dažiem dialogiem. Apsveriet tās instalēšanu."); + huEngine->registerEntry("lv_LV", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland nevarēja ielādēt {count} būtisku resursu, vainojiet sava distro iepakotāju par sliktu iepakošanu!"; + return "Hyprland nevarēja ielādēt {count} būtiskus resursus, vainojiet sava distro iepakotāju par sliktu iepakošanu!"; + }); + huEngine->registerEntry( + "lv_LV", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Jūsu monitora izkārtojums ir nepareizi iestatīts. Monitors {name} pārklājas ar citiem izkārtojumā iestatītajiem monitoriem.\nLūdzu apskatieties (Monitoru lapā)," + "lai uzzinātu vairāk. Tas radīs problēmas."); + huEngine->registerEntry("lv_LV", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Monitoram {name} neizdevās iestatīt nevienu no pieprasītajiem režīmiem, izmantojam {mode}."); + huEngine->registerEntry("lv_LV", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Monitoram {name} ir nodots nederīgs mērogs: {scale}, izmantojam ieteikto mērogu: {fixed_scale}"); + huEngine->registerEntry("lv_LV", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Nevarēja ielādēt spraudni {name}: {error}"); + huEngine->registerEntry("lv_LV", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM šeiderus neizdevās pārlādēt, izmantojam rgba/rgbx."); + huEngine->registerEntry("lv_LV", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitors {name}: Ir iespējota plaša krāsu gamma, bet displejs nav 10-bitu režīmā."); + + // hu_HU (Hungarian) + huEngine->registerEntry("hu_HU", TXT_KEY_ANR_TITLE, "Az alkalmazás nem válaszol"); + huEngine->registerEntry("hu_HU", TXT_KEY_ANR_CONTENT, "A(z) {title} - {class} alkalmazás nem válaszol.\nMit szeretne tenni vele?"); + huEngine->registerEntry("hu_HU", TXT_KEY_ANR_OPTION_TERMINATE, "Leállítás"); + huEngine->registerEntry("hu_HU", TXT_KEY_ANR_OPTION_WAIT, "Várakozás"); + huEngine->registerEntry("hu_HU", TXT_KEY_ANR_PROP_UNKNOWN, "(ismeretlen)"); + + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "A(z) {app} alkalmazás ismeretlen engedélyt kér."); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "A(z) {app} alkalmazás megpróbálja rögzíteni a képernyőjét.\n\nEngedélyezi?"); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "A(z) {app} alkalmazás megpróbál egy bővítményt betölteni: {plugin}.\n\nEngedélyezi?"); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Új billentyűzetet észleltünk: {keyboard}.\n\nEngedélyezi a használatát?"); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(ismeretlen)"); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_TITLE, "Engedélykérés"); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Tipp: Állandó szabályokat állíthat be a Hyprland konfigurációs fájlban."); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_ALLOW, "Engedélyezés"); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Mindig engedélyez"); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_ALLOW_ONCE, "Egyszeri engedélyezés"); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_DENY, "Elutasítás"); + huEngine->registerEntry("hu_HU", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Ismeretlen alkalmazás (wayland kliens ID {wayland_id})"); + + huEngine->registerEntry("hu_HU", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Úgy tűnik, hogy az XDG_CURRENT_DESKTOP környezetet külsőleg kezelik, és a jelenlegi érték {value}.\nEz problémákat okozhat, hacsak nem szándékos."); + huEngine->registerEntry("hu_HU", TXT_KEY_NOTIF_NO_GUIUTILS, + "A rendszerében nincs telepítve a hyprland-guiutils. Ez egy futásidejű függőség néhány párbeszédablakhoz. Fontolja meg a telepítését."); + huEngine->registerEntry("hu_HU", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "A Hyprland nem tudta betölteni az 1 szükséges erőforrást. Kérjük, jelezze a hibát a disztribúció csomagolójának."; + return "A Hyprland nem tudott betölteni {count} szükséges erőforrást. Kérjük, jelezze a hibát a disztribúció csomagolójának."; + }); + huEngine->registerEntry( + "hu_HU", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "A monitor elrendezése helytelenül van beállítva. A(z) {name} monitor átfedi a többi monitort az elrendezésben.\nKérjük, további információkért tekintse meg a wikit " + "(Monitors oldal). Ez problémákat fog okozni."); + huEngine->registerEntry("hu_HU", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "A(z) {name} monitor nem tudta beállítani a kért módokat, visszaáll a(z) {mode} módra."); + huEngine->registerEntry("hu_HU", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Érvénytelen skálázás a(z) {name} monitorhoz: {scale}, a javasolt skálázás használata: {fixed_scale}"); + huEngine->registerEntry("hu_HU", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Nem sikerült betölteni a(z) {name} bővítményt: {error}"); + huEngine->registerEntry("hu_HU", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "A CM shader újratöltése sikertelen, visszaáll rgba/rgbx-re."); + huEngine->registerEntry("hu_HU", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: A széles színtartomány engedélyezve van, de a kijelző nem 10 bites módban van."); + + // ml_IN (Malayalam) + huEngine->registerEntry("ml_IN", TXT_KEY_ANR_TITLE, "ആപ്ലിക്കേഷൻ പ്രതികരിക്കുന്നില്ല"); + huEngine->registerEntry("ml_IN", TXT_KEY_ANR_CONTENT, "ആപ്ലിക്കേഷൻ {title} - {class} പ്രതികരിക്കുന്നില്ല.\nഇതിന് നിങ്ങൾ എന്ത് ചെയ്യാൻ ആഗ്രഹിക്കുന്നു?"); + huEngine->registerEntry("ml_IN", TXT_KEY_ANR_OPTION_TERMINATE, "അവസാനിപ്പിക്കുക"); + huEngine->registerEntry("ml_IN", TXT_KEY_ANR_OPTION_WAIT, "കാത്തിരിക്കുക"); + huEngine->registerEntry("ml_IN", TXT_KEY_ANR_PROP_UNKNOWN, "(അജ്ഞാതം)"); + + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "ആപ്ലിക്കേഷൻ {app} ഒരു അജ്ഞാത അനുമതി അഭ്യർത്ഥിക്കുന്നു."); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "ആപ്ലിക്കേഷൻ {app} നിങ്ങളുടെ സ്ക്രീൻ പകർത്താൻ ശ്രമിക്കുന്നു.\n\nനിങ്ങൾ അത് അനുവദിക്കണോ?"); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "ആപ്ലിക്കേഷൻ {app} ഒരു പ്ലഗിൻ ലോഡ് ചെയ്യാൻ ശ്രമിക്കുന്നു: {plugin}.\n\nഇത് അനുവദിക്കണോ?"); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "പുതിയ കീബോർഡ് കണ്ടെത്തി: {keyboard}.\n\nഇത് പ്രവർത്തിക്കാൻ അനുവദിക്കണോ?"); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(അജ്ഞാതം)"); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_TITLE, "അനുമതി അഭ്യർത്ഥന"); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "സൂചന: Hyprland കോൺഫിഗ് ഫയലിൽ സ്ഥിരനിയമങ്ങൾ സജ്ജമാക്കാം."); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_ALLOW, "അനുവദിക്കുക"); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "അനുവദിച്ച് ഓർക്കുക"); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_ALLOW_ONCE, "ഒന്നുതവണ അനുവദിക്കുക"); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_DENY, "നിരസിക്കുക"); + huEngine->registerEntry("ml_IN", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "അജ്ഞാത അപ്ലിക്കേഷൻ (wayland client ID {wayland_id})"); + + huEngine->registerEntry("ml_IN", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "നിങ്ങളുടെ XDG_CURRENT_DESKTOP പരിസ്ഥിതി പുറത്ത് നിന്ന് നിയന്ത്രിക്കപ്പെടുന്നു, ഇപ്പോഴത്തെ മൂല്യം " + "{value}.\nഇത് ഉദ്ദേശ്യമായല്ലെങ്കിൽ പ്രശ്നങ്ങൾ ഉണ്ടാകും."); + huEngine->registerEntry("ml_IN", TXT_KEY_NOTIF_NO_GUIUTILS, + "നിങ്ങളുടെ സിസ്റ്റത്തിൽ hyprland-guiutils ഇൻസ്റ്റാൾ ചെയ്തിട്ടില്ല. ഇത് ചില ഡയലോഗുകൾക്ക് ആവശ്യമായ " + "റൺടൈം ആശ്രയമാണ്. ഇൻസ്റ്റാൾ ചെയ്യുക."); + huEngine->registerEntry("ml_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("ml_IN", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "മോണിറ്റർ ലേയൗട്ട് തെറ്റാണ്. മോണിറ്റർ {name} മറ്റുള്ളവയുമായ് ഒതുങ്ങുന്നു.\nകൂടുതൽ വിവരങ്ങൾക്ക് Wiki " + "(Monitors page) കാണുക. ഇത് പ്രശ്നങ്ങൾ ഉണ്ടാക്കും."); + huEngine->registerEntry("ml_IN", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "മോണിറ്റർ {name} ആവശ്യപ്പെട്ട മോഡുകൾ സജ്ജമാക്കാൻ പരാജയപ്പെട്ടു, ഇപ്പോൾ {mode} ഉപയോഗിക്കുന്നു."); + huEngine->registerEntry("ml_IN", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "മോണിറ്റർ {name} ന് അസാധുവായ സ്കെയിൽ: {scale}, നിർദ്ദേശിച്ച സ്കെയിൽ: {fixed_scale}"); + huEngine->registerEntry("ml_IN", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "പ്ലഗിൻ {name} ലോഡ് ചെയ്യാൻ പരാജയപ്പെട്ടു: {error}"); + huEngine->registerEntry("ml_IN", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM ഷേഡർ റീലോഡ് പരാജയപ്പെട്ടു, rgba/rgbx ലേക്ക് മാറുന്നു."); + huEngine->registerEntry("ml_IN", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "മോണിറ്റർ {name}: വൈഡ് കളർ ഗാമട്ട് പ്രവർത്തനക്ഷമമാണെങ്കിലും, മോഡ് 10-bit അല്ല."); + + // nb_NO (Norwegian Bokmål) + huEngine->registerEntry("nb_NO", TXT_KEY_ANR_TITLE, "Applikasjonen svarer ikke"); + huEngine->registerEntry("nb_NO", TXT_KEY_ANR_CONTENT, "En applikasjon {title} - {class} svarer ikke.\nHva vil du gjøre med den?"); + huEngine->registerEntry("nb_NO", TXT_KEY_ANR_OPTION_TERMINATE, "Avslutt"); + huEngine->registerEntry("nb_NO", TXT_KEY_ANR_OPTION_WAIT, "Vent"); + huEngine->registerEntry("nb_NO", TXT_KEY_ANR_PROP_UNKNOWN, "(ukjent)"); + + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "En applikasjon {app} ber om en ukjent tillatelse."); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "En applikasjon {app} prøver å fange skjermen din.\n\nVil du tillate den?"); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "En applikasjon {app} prøver å laste en plugin: {plugin}.\n\nVil du tillate den?"); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Et nytt tastatur er oppdaget: {keyboard}.\n\nVil du tillate at det opererer?"); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(ukjent)"); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_TITLE, "Tillatelsesforespørsel"); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Hint: du kan angi vedvarende regler for disse i Hyprland konfigurasjonsfilen."); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_ALLOW, "Tillat"); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Tillat og husk"); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_ALLOW_ONCE, "Tillat en gang"); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_DENY, "Nekte"); + huEngine->registerEntry("nb_NO", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Ukjent applikasjon (wayland client ID {wayland_id})"); + + huEngine->registerEntry( + "nb_NO", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Ditt XDG_CURRENT_DESKTOP miljø ser ut til å være eksternt administrert, og den nåværende verdien er {value}.\nDette kan forårsake problemer med mindre det er bevisst."); + huEngine->registerEntry("nb_NO", TXT_KEY_NOTIF_NO_GUIUTILS, + "Ditt system har ikke hyprland-guiutils installert. Dette er en kjøretidsavhengighet for noen dialoger. Vurder å installere den."); + huEngine->registerEntry("nb_NO", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland kunne ikke laste {count} essensiell ressurs, skyld på distroens pakkeansvarlig for å ha gjort en dårlig jobb med pakkingen!"; + return "Hyprland kunne ikke laste {count} essensielle ressurser, skyld på distroens pakkeansvarlig for å ha gjort en dårlig jobb med pakkingen!"; + }); + huEngine->registerEntry("nb_NO", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Skjermoppsettet ditt er satt opp feil. Skjerm {name} overlapper med skjerm(er) i oppsettet.\nSjekk wiki (Skjerm oppsett siden) for " + "mer. Dette vil skape problemer."); + huEngine->registerEntry("nb_NO", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Skjerm {name} feilet å sette de forespurte modusene, faller tilbake til modus {mode}."); + huEngine->registerEntry("nb_NO", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Ugyldig skala sendt til skjerm {name}: {scale}, bruker foreslått skala: {fixed_scale}"); + huEngine->registerEntry("nb_NO", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Feilet å laste plugin {name}: {error}"); + huEngine->registerEntry("nb_NO", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM shader omlading feilet, faller tilbake til rgba/rgbx."); + huEngine->registerEntry("nb_NO", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Skjerm {name}: bredt fargespekter er aktivert, men skjermen er ikke i 10-bit modus."); + + // ne_NP (Nepali) + huEngine->registerEntry("ne_NP", TXT_KEY_ANR_TITLE, "एपले रिस्पन्ड गरिरहेको छैन"); + huEngine->registerEntry("ne_NP", TXT_KEY_ANR_CONTENT, "{title} - {class} एपले रिस्पन्ड गरिरहेको छैन।\nयससँग के गर्न चहानुहुन्छ?"); + huEngine->registerEntry("ne_NP", TXT_KEY_ANR_OPTION_TERMINATE, "टर्मिनेट गर्नुहोस्"); + huEngine->registerEntry("ne_NP", TXT_KEY_ANR_OPTION_WAIT, "पर्खनुहोस्"); + huEngine->registerEntry("ne_NP", TXT_KEY_ANR_PROP_UNKNOWN, "(अज्ञात)"); + + huEngine->registerEntry("ne_NP", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "{app} एपले अज्ञात सुविधाको अनुमति मागिरहेको छ।"); + huEngine->registerEntry("ne_NP", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "{app} एपले स्क्रिन क्याप्चर गर्न खोज्दै छ।\n\nयसलाई अनुमति दिन चहानुहुन्छ?"); + huEngine->registerEntry("ne_NP", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "{app} एपले एउटा प्लगिन लोड गर्न खोज्दै छ: {plugin}।\n\nयसलाई अनुमति दिन चहानुहुन्छ?"); + huEngine->registerEntry("ne_NP", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "एउटा नयाँ किबोर्ड डिटेक्ट गरिएको छ: {keyboard}।\n\nयसलाई चल्ने अनुमति दिन चहानुहुन्छ?"); + huEngine->registerEntry("ne_NP", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(अज्ञात)"); + huEngine->registerEntry("ne_NP", TXT_KEY_PERMISSION_TITLE, "अनुमतिको माग"); + huEngine->registerEntry("ne_NP", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "टिप: यसको लागि पर्सिस्टेन्ट नियमहरु तपाइँले हाइपरल्यान्डको कन्फीग्युरेसन फाइलमा राख्न सक्नुहुन्छ।"); + huEngine->registerEntry("ne_NP", TXT_KEY_PERMISSION_ALLOW, "अनुमति दिनुहोस्"); + huEngine->registerEntry("ne_NP", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "अनुमति दिनुहोस् र सम्झनुहोस्"); + huEngine->registerEntry("ne_NP", TXT_KEY_PERMISSION_ALLOW_ONCE, "एकपटक अनुमति दिनुहोस्"); + huEngine->registerEntry("ne_NP", TXT_KEY_PERMISSION_DENY, "अनुमति नदिनुहोस्"); + huEngine->registerEntry("ne_NP", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "अज्ञात एप (wayland क्लाइन्ट आईडी {wayland_id})"); + + huEngine->registerEntry("ne_NP", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "तपाईँको XDG_CURRENT_DESKTOP वातावरण बाहिरबाट व्यवस्थापन भइरहेको जस्तो देखिएको छ, अहिले {value} देखाइरहेको छ।\nजानीजानी नगरीएको भएमा यसले समस्याहरु निम्त्याउन सक्छ।"); + huEngine->registerEntry("ne_NP", TXT_KEY_NOTIF_NO_GUIUTILS, "तपाइँको सिस्टममा hyprland-guiutils इन्सटल गरिएको छैन। केहि डायलगहरुका लागि यो रनटाइम डिपेन्डेन्सी हो। कृपया इन्सटल गर्नुहोला।"); + huEngine->registerEntry("ne_NP", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "हाइपरल्यान्डले एउटा अत्यावश्यक एसेट लोड गर्न सकेन, तपाइँको डिस्ट्रोको प्याकेजरको प्याकेजिङ गतिलो छैन!"; + return "हाइपरल्यान्डले {count} अत्यावश्यक एसेटहरु लोड गर्न सकेन, तपाइँको डिस्ट्रोको प्याकेजरको प्याकेजिङ गतिलो छैन!"; + }); + huEngine->registerEntry("ne_NP", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "तपाइँको मनिटरको लेआउट गलत तरिकाले मिलाइएको छ। लेआउटमा {name} मनिटर अर्को मनिटर वा मनिटरहरुसङ्ग ओभरल्याप भएको छ।\nथप बुझ्नलाई कृपया विकिको मनिटर पेज हेर्नुहोस्।" + "यसले निश्चित रुपमा समस्या निम्त्याउने छ।"); + huEngine->registerEntry("ne_NP", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "{name} मनिटरले चाहेको कुनै पनि मोड सेट गर्न सकेन, {mode} मोडमा फर्कँदै।"); + huEngine->registerEntry("ne_NP", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "{name} मनिटरलाई अमान्य स्केल पठाइयो: {scale}, सजेस्ट गरिएको स्केल प्रयोग गर्दै: {fixed_scale}"); + huEngine->registerEntry("ne_NP", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "{name} प्लगिन लोेड गर्न सकिएन: {error}"); + huEngine->registerEntry("ne_NP", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM shader रिलोड गर्न सकिएन, rgba/rgbx मा फर्कँदै।"); + huEngine->registerEntry("ne_NP", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "{name} मनिटर: wide color gamut अन छ तर डिस्प्ले 10-bit मोड मा छैन।"); + + // nl_NL (Dutch) + huEngine->registerEntry("nl_NL", TXT_KEY_ANR_TITLE, "Applicatie Reageert Niet"); + huEngine->registerEntry("nl_NL", TXT_KEY_ANR_CONTENT, "Een applicatie {title} - {class} reageert niet.\nWat wilt u doen?"); + huEngine->registerEntry("nl_NL", TXT_KEY_ANR_OPTION_TERMINATE, "Beëindigen"); + huEngine->registerEntry("nl_NL", TXT_KEY_ANR_OPTION_WAIT, "Wachten"); + huEngine->registerEntry("nl_NL", TXT_KEY_ANR_PROP_UNKNOWN, "(onbekend)"); + + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Een applicatie {app} vraagt om een onbekende machtiging."); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Een applicatie {app} probeert uw scherm op te nemen.\n\nWilt u dit toestaan?"); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Een applicatie {app} probeert een plugin te laden: {plugin}.\n\nWilt u dit toestaan?"); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, + "Een nieuw toetsenbord is gedetecteerd: {keyboard}.\n\nWilt u toestemming geven dat het wordt gebruikt?"); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(onbekend)"); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_TITLE, "Toestemmingsverzoek"); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Tip: U kunt hiervoor vaste regels instellen in het Hyprland-configuratiebestand."); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_ALLOW, "Toestaan"); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Toestaan en onthouden"); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_ALLOW_ONCE, "Één keer toestaan"); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_DENY, "Weigeren"); + huEngine->registerEntry("nl_NL", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Onbekende applicatie (wayland client ID {wayland_id})"); + + huEngine->registerEntry( + "nl_NL", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "De XDG_CURRENT_DESKTOP omgevingsvariabele lijkt extern beheerd te worden en de huidige waarde is {value}.\nDit kan problemen veroorzaken, tenzij dit opzettelijk is."); + huEngine->registerEntry("nl_NL", TXT_KEY_NOTIF_NO_GUIUTILS, + "Hyprland-guiutils is niet op uw systeem geïnstalleerd. Dit is een runtime-afhankelijkheid voor sommige dialogen. Overweeg het te installeren."); + huEngine->registerEntry("nl_NL", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland kon {count} essentieel bestand niet laden, geef de pakketbeheerder van uw distro de schuld voor slecht verpakkingswerk!"; + return "Hyprland kon {count} essentiële bestanden niet laden, geef de pakketbeheerder van uw distro de schuld voor slecht verpakkingswerk!"; + }); + huEngine->registerEntry("nl_NL", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Uw monitorindeling is onjuist ingesteld. Monitor {name} overlapt met één of meerdere andere monitoren in de indeling.\n" + "Zie de wiki (Monitors pagina) voor meer informatie. Dit zal problemen veroorzaken."); + huEngine->registerEntry("nl_NL", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, + "Monitor {name} is er niet in geslaagd om een van de aangevraagde modi toe te passen en gebruikt nu de modus {mode}."); + huEngine->registerEntry("nl_NL", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, + "Ongeldige schaal opgegeven voor monitor {name}: {scale}, de voorgestelde schaal {fixed_scale} wordt gebruikt."); + huEngine->registerEntry("nl_NL", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Plugin {name} kon niet worden geladen: {error}"); + 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."); + + // 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ć?"); + huEngine->registerEntry("pl_PL", TXT_KEY_ANR_OPTION_TERMINATE, "Zakończ proces"); + huEngine->registerEntry("pl_PL", TXT_KEY_ANR_OPTION_WAIT, "Czekaj"); + huEngine->registerEntry("pl_PL", TXT_KEY_ANR_PROP_UNKNOWN, "(nieznane)"); + + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Aplikacja {app} prosi o pozwolenie na nieznany typ operacji."); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Aplikacja {app} prosi o dostęp do twojego ekranu.\n\nCzy chcesz jej na to pozwolić?"); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Aplikacja {app} próbuje załadować plugin: {plugin}.\n\nCzy chcesz jej na to pozwolić?"); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Wykryto nową klawiaturę: {keyboard}.\n\nCzy chcesz jej pozwolić operować?"); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(nieznane)"); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_TITLE, "Prośba o pozwolenie"); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Podpowiedź: możesz ustawić stałe zasady w konfiguracji Hyprland'a."); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_ALLOW, "Zezwól"); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Zezwól i zapamiętaj"); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_ALLOW_ONCE, "Zezwól raz"); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_DENY, "Odmów"); + huEngine->registerEntry("pl_PL", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Nieznana aplikacja (ID klienta wayland {wayland_id})"); + + huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Zmienna środowiska XDG_CURRENT_DESKTOP została ustawiona zewnętrznie na {value}.\nTo może sprawić problemy, chyba, że jest celowe."); + huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_NO_GUIUTILS, "Twój system nie ma hyprland-guiutils zainstalowanych, co może sprawić problemy. Zainstaluj pakiet."); + huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo == 1) + return "Nie udało się załadować {count} kluczowego zasobu, wiń swojego packager'a za robienie słabej roboty!"; + + return "Nie udało się załadować {count} kluczowych zasobów, wiń swojego packager'a za robienie słabej roboty!"; + }); + huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Pozycje twoich monitorów nie są ustawione poprawnie. Monitor {name} wchodzi na inne monitory.\nWejdź na wiki (stronę Monitory) " + "po więcej. To będzie sprawiać problemy."); + huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Monitor {name} nie zaakceptował żadnego wybranego programu. Użyto {mode}."); + huEngine->registerEntry("pl_PL", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Nieprawidłowa skala dla monitora {name}: {scale}, użyto proponowanej skali: {fixed_scale}"); + 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"); + huEngine->registerEntry("pt_PT", TXT_KEY_ANR_CONTENT, "Uma aplicação {title} - {class} não está a responder.\nO que pretendes fazer com ela?"); + huEngine->registerEntry("pt_PT", TXT_KEY_ANR_OPTION_TERMINATE, "Terminar"); + huEngine->registerEntry("pt_PT", TXT_KEY_ANR_OPTION_WAIT, "Esperar"); + huEngine->registerEntry("pt_PT", TXT_KEY_ANR_PROP_UNKNOWN, "(desconhecido)"); + + huEngine->registerEntry("pt_PT", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Uma aplicação {app} está a pedir uma permissão desconhecida."); + huEngine->registerEntry("pt_PT", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Uma aplicação {app} está a tentar fazer uma captura do ecrã.\n\nQueres permiti-lo?"); + huEngine->registerEntry("pt_PT", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "A aplicação {app} está a tentar carregar o plugin: {plugin}.\n\nQueres permiti-lo?"); + huEngine->registerEntry("pt_PT", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Um novo teclado foi detectado: {keyboard}.\n\nQueres permitir a sua operação?"); + huEngine->registerEntry("pt_PT", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(desconhecido)"); + huEngine->registerEntry("pt_PT", TXT_KEY_PERMISSION_TITLE, "Pedido de permissão"); + huEngine->registerEntry("pt_PT", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Dica: podes definir regras persistentes para estes no ficheiro de configuração do Hyprland."); + huEngine->registerEntry("pt_PT", TXT_KEY_PERMISSION_ALLOW, "Permitir"); + huEngine->registerEntry("pt_PT", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Permitir sempre"); + huEngine->registerEntry("pt_PT", TXT_KEY_PERMISSION_ALLOW_ONCE, "Permitir esta vez"); + huEngine->registerEntry("pt_PT", TXT_KEY_PERMISSION_DENY, "Recusar"); + huEngine->registerEntry("pt_PT", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Aplicação desconhecida (ID de cliente wayland {wayland_id})"); + + huEngine->registerEntry( + "pt_PT", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "O teu ambiente XDG_CURRENT_DESKTOP parece estar a ser gerido externamente, e o valor actual é {value}.\nIsto pode causar problemas a não ser que seja intencional."); + huEngine->registerEntry("pt_PT", TXT_KEY_NOTIF_NO_GUIUTILS, + "O teu sistema não tem o hyprland-guiutils instalado. Esta dependência de runtime é necessária para algumas caixas de diálogo, deverias instalá-la."); + huEngine->registerEntry("pt_PT", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland não conseguiu carregar {count} asset essencial, podes culpar o gestor de dependências da tua distro por fazer um mau trabalho!"; + return "Hyprland não conseguiu carregar {count} assets essenciais, podes culpar o gestor de dependências da tua distro por fazer um mau trabalho!"; + }); + huEngine->registerEntry( + "pt_PT", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "O layout do teu monitor não está configurado correctamente. Monitor {name} está em conflito com outro(s) monitor(es) no layout.\nProcura na wiki (página Monitores) para " + "mais informações. Isto vai causar problemas."); + huEngine->registerEntry("pt_PT", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Monitor {name} falhou ao configurar os modos requisitados, revertento para o modo {mode} de volta."); + huEngine->registerEntry("pt_PT", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Resolução inválida para o monitor {name}: {scale}, revertendo para a resolução sugerida: {fixed_scale}"); + huEngine->registerEntry("pt_PT", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Falha ao carregar o plugin {name}: {error}"); + huEngine->registerEntry("pt_PT", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM shader falhou ao recarregar, revertendo para rgba/rgbx."); + huEngine->registerEntry("pt_PT", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: gama de cores ampla está activada mas o monitor não está em modo 10-bits."); + + // zh_CN (Simplified Chinese) + huEngine->registerEntry("zh_CN", TXT_KEY_ANR_TITLE, "应用程序未响应"); + huEngine->registerEntry("zh_CN", TXT_KEY_ANR_CONTENT, "应用程序 {title} - {class} 未响应。\n你想要采取什么行动?"); + huEngine->registerEntry("zh_CN", TXT_KEY_ANR_OPTION_TERMINATE, "终止"); + huEngine->registerEntry("zh_CN", TXT_KEY_ANR_OPTION_WAIT, "等待"); + huEngine->registerEntry("zh_CN", TXT_KEY_ANR_PROP_UNKNOWN, "(未知)"); + + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "应用程序 {app} 正在请求一个未知的权限。"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "应用程序 {app} 想要捕获你的屏幕。\n\n允许它这么做吗?"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "应用程序 {app} 想要加载插件: {plugin}。\n\n允许它这么做吗?"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "检测到新的键盘 {keyboard} 接入了。\n\n允许这个键盘操作你的系统吗?"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(未知)"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_TITLE, "权限请求"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "提示:你可以在Hyprland配置中为他们创建永久性的规则。"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_ALLOW, "允许"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "总是允许"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_ALLOW_ONCE, "允许一次"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_DENY, "阻止"); + huEngine->registerEntry("zh_CN", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "未知的应用程序 (Wayland客户端ID {wayland_id})"); + + huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "你的环境变量XDG_CURRENT_DESKTOP似乎被外部管理,且当前的值为{value}。如果你不是有意这么做,这可能会导致问题。"); + huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_NO_GUIUTILS, "你的系统似乎没有安装hyprland-guiutils。这是一个用于部分对话框的运行时依赖。请考虑安装。"); + huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_FAILED_ASSETS, "Hyprland无法加载{count}个重要资产,问问你发行版的打包者在打包个什么玩意!?"); + huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "你的显示器没有被正确设置。显示器 {name} 和其他显示器的布局重叠了。请看wiki中的“显示器”一章获取更多信息。这导致问题。"); + huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "显示器 {name} 无法被设置为任何请求的模式,将使用 {mode} 兜底。"); + huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "显示器 {name} 被设置了非法的缩放:{scale},将使用建议的缩放:{fixed_scale}"); + 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, "التطبيق لا يستجيب"); + huEngine->registerEntry("ar", TXT_KEY_ANR_CONTENT, "التطبيق {title} - {class} لا يستجيب.\nما الذي تريد فعله؟"); + huEngine->registerEntry("ar", TXT_KEY_ANR_OPTION_TERMINATE, "إنهاء"); + huEngine->registerEntry("ar", TXT_KEY_ANR_OPTION_WAIT, "الانتظار"); + huEngine->registerEntry("ar", TXT_KEY_ANR_PROP_UNKNOWN, "(غير معروف)"); + + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "يطلب التطبيق {app} صلاحية غير معروفة."); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "يحاول التطبيق {app} التقاط الشاشة.\n\nهل تريد السماح له بذلك؟"); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "يحاول التطبيق {app} تحميل إضافة: {plugin}.\n\nهل تريد السماح له بذلك؟"); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "تم اكتشاف لوحة مفاتيح جديدة: {keyboard}.\n\nهل تريد السماح لها بالعمل؟"); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(غير معروف)"); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_TITLE, "طلب الإذن"); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "تلميح: يمكنك تعيين قواعد دائمة لهذه الطلبات في ملف إعدادات Hyprland."); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_ALLOW, "السماح"); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "السماح مع تذكّر الاختيار"); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_ALLOW_ONCE, "السماح لمرة واحدة"); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_DENY, "الرفض"); + huEngine->registerEntry("ar", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "تطبيق غير معروف (معرّف عميل Wayland {wayland_id})"); + + 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Что вы хотите сделать?"); + huEngine->registerEntry("ru_RU", TXT_KEY_ANR_OPTION_TERMINATE, "Завершить"); + huEngine->registerEntry("ru_RU", TXT_KEY_ANR_OPTION_WAIT, "Подождать"); + huEngine->registerEntry("ru_RU", TXT_KEY_ANR_PROP_UNKNOWN, "(неизвестно)"); + + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Приложение {app} запрашивает неизвестное разрешение."); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Приложение {app} пытается получить доступ к вашему экрану.\n\nРазрешить?"); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Приложение {app} пытается загрузить плагин: {plugin}.\n\nРазрешить?"); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Обнаружена новая клавиатура: {keyboard}.\n\nРазрешить ей работать?"); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(неизвестно)"); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_TITLE, "Запрос разрешения"); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Подсказка: вы можете настроить постоянные правила для этого в конфигурационном файле Hyprland."); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_ALLOW, "Разрешить"); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Разрешить и запомнить"); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_ALLOW_ONCE, "Разрешить в этот раз"); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_DENY, "Отклонить"); + huEngine->registerEntry("ru_RU", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Неизвестное приложение (wayland client ID {wayland_id})"); + + huEngine->registerEntry( + "ru_RU", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Переменная окружения XDG_CURRENT_DESKTOP установлена извне, текущее значение: {value}.\nЭто может вызвать проблемы, если только это не сделано намеренно."); + huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_NO_GUIUTILS, "Пакет hyprland-guiutils не установлен. Он необходим для некоторых диалогов. Рекомендуется установить его."); + huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Не удалось загрузить {count} критически важный ресурс, пожалуйтесь мейнтейнеру вашего дистрибутива за кривую сборку пакета!"; + return "Не удалось загрузить {count} критически важных ресурсов, пожалуйтесь мейнтейнеру вашего дистрибутива за кривую сборку пакета!"; + }); + huEngine->registerEntry( + "ru_RU", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Неправильно настроен макет мониторов. Монитор {name} перекрывает другие.\nПодробнее см. в документации (страница Monitors). Это обязательно вызовет проблемы."); + huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Монитор {name} не смог установить ни один из запрошенных режимов, выбран режим {mode}."); + huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Недопустимый масштаб для монитора {name}: {scale}, используется предложенный масштаб: {fixed_scale}"); + 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"); + huEngine->registerEntry("sl_SI", TXT_KEY_ANR_CONTENT, "Program {title} - {class} se ne odziva.\nKaj želite storiti?"); + huEngine->registerEntry("sl_SI", TXT_KEY_ANR_OPTION_TERMINATE, "Prekini"); + huEngine->registerEntry("sl_SI", TXT_KEY_ANR_OPTION_WAIT, "Počakaj"); + huEngine->registerEntry("sl_SI", TXT_KEY_ANR_PROP_UNKNOWN, "(neznano)"); + + huEngine->registerEntry("sl_SI", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Program {app} zahteva neznano dovoljenje."); + huEngine->registerEntry("sl_SI", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Program {app} poskuša zajeti vaš zaslon.\n\nAli mu želite to dovoliti?"); + huEngine->registerEntry("sl_SI", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Program {app} skuša naložiti vtičnik: {plugin}.\n\nAli mu želite to dovoliti?"); + huEngine->registerEntry("sl_SI", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Nova tipkovnica je bila zaznana: {keyboard}.\n\nAli ji želite dovoliti delovanje?"); + huEngine->registerEntry("sl_SI", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(neznano)"); + huEngine->registerEntry("sl_SI", TXT_KEY_PERMISSION_TITLE, "Zahteva za dovoljenje"); + huEngine->registerEntry("sl_SI", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Namig: v Hyprlandovi konfiguracijski datoteki lahko nastavite stalna pravila za dovoljenja."); + huEngine->registerEntry("sl_SI", TXT_KEY_PERMISSION_ALLOW, "Dovoli"); + huEngine->registerEntry("sl_SI", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Dovoli in si zapomni"); + huEngine->registerEntry("sl_SI", TXT_KEY_PERMISSION_ALLOW_ONCE, "Dovoli enkrat"); + huEngine->registerEntry("sl_SI", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Neznan program (ID stranke Wayland: {wayland_id})"); + + huEngine->registerEntry("sl_SI", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Zdi se, da je Vaše okolje XDG_CURRENT_DESKTOP upravljano od zunaj, trenutna vrednost je {value}.\nTo lahko povzroči težave, če ni namerno."); + huEngine->registerEntry("sl_SI", TXT_KEY_NOTIF_NO_GUIUTILS, + "hyprland-guiutils ni nameščen na vaši napravi. To je odvisnost od izvajalnega okolja za nekatera pogovorna okna. Razmislite o namestitvi."); + huEngine->registerEntry("sl_SI", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assertNo = std::stoi(vars.at("count")); + if (assertNo <= 1) + return "Hyprlandu ni uspelo naložiti {count} bistvenega sredstva, krivite upravitelja paketov vaše distribucije za slabo opravljeno pakiranje!"; + return "Hyprlandu ni uspelo naložiti {count} bistvenih sredstev, krivite upravitelja paketov vaše distribucije za slabo opravljeno pakiranje!"; + }); + huEngine->registerEntry("sl_SI", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Vaša zaslonska razporeditev je napačno nastavljena. Zaslon {monitor} se prekriva z drugimi zasloni v razporeditvi.\n" + "Prosimo poglejte wiki (stran Monitors) za več. To bo povzročilo težave."); + huEngine->registerEntry("sl_SI", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Zaslon {name} ni uspel nastaviti nobenega zahtevanega načina, vrnitev k načinu {mode}."); + huEngine->registerEntry("sl_SI", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, + "Neveljavna skala je bila posredovana zaslonu {name}: {scale}, uporabljena je predlagana skala: {fixed_scale}"); + huEngine->registerEntry("sl_SI", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Vtičnika {name} ni bilo mogoče naložiti: {error}"); + huEngine->registerEntry("sl_SI", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Ponovno nalaganje senčnika CM ni uspelo, vrnitev k rgba/rgbx."); + huEngine->registerEntry("sl_SI", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Zaslon {name}: širok barvni razpon je omogočen, vendar zaslon ni v 10-bitnem načinu."); + + // sr_RS (Serbian) + huEngine->registerEntry("sr_RS", TXT_KEY_ANR_TITLE, "Апликација не реагује"); + huEngine->registerEntry("sr_RS", TXT_KEY_ANR_CONTENT, "Апликација {title} - {class} не реагује.\nШта желите да урадите са њом?"); + huEngine->registerEntry("sr_RS", TXT_KEY_ANR_OPTION_TERMINATE, "Прекини"); + huEngine->registerEntry("sr_RS", TXT_KEY_ANR_OPTION_WAIT, "Чекај"); + huEngine->registerEntry("sr_RS", TXT_KEY_ANR_PROP_UNKNOWN, "(непознато)"); + + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Апликација {app} захтева непознату дозволу."); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Апликација {app} покушава да снима твој екран.\n\nДа ли желиш да то дозволиш?"); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Апликација {app} покушава да учита додатак: {plugin}.\n\nДа ли желиш да то дозволиш?"); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Нова тастатура је детектована: {keyboard}.\n\nДа ли желиш да дозволиш њен рад?"); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(непознато)"); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_TITLE, "Захтев за дозволу"); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Савет: можеш направити трајна правила за ово у Hyprland конфигурационој датотеци."); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_ALLOW, "Дозволи"); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Дозволи и запамти"); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_ALLOW_ONCE, "Дозволи једном"); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_DENY, "Одбиј"); + huEngine->registerEntry("sr_RS", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Непозната апликација (wayland client ID {wayland_id})"); + + huEngine->registerEntry("sr_RS", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Изгледа да се твојим XDG_CURRENT_DESKTOP окружењем управља споља, и тренутна вредност је {value}.\nОво може правити проблеме осим ако је намерно."); + huEngine->registerEntry("sr_RS", TXT_KEY_NOTIF_NO_GUIUTILS, + "Твој систем нема инсталиран hyprland-guiutils. Ово је зависност при покретању за неке дијалоге. Размотри инсталацију."); + huEngine->registerEntry("sr_RS", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland није успео да учита {count} кључни ресурс, криви пакера твоје дистрибуције за лоше одрађен посао!"; + if (assetsNo <= 4) + return "Hyprland није успео да учита {count} кључна ресурса, криви пакера твоје дистрибуције за лоше одрађен посао!"; + return "Hyprland није успео да учита {count} кључних ресурса, криви пакера твоје дистрибуције за лоше одрађен посао!"; + }); + huEngine->registerEntry( + "sr_RS", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Твој распоред монитора је неправилно постављен. Монитор {name} се преклапа са другим монитором/мониторима у распореду.\nМолим те погледај вики (Monitors страницу) за " + "више информација. Ово ће изазвати проблеме."); + huEngine->registerEntry("sr_RS", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Монитор {name} није успео да постави ниједан тражени режим, враћање на режим {mode}."); + huEngine->registerEntry("sr_RS", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Невалидна скала прослеђена монитору {name}: {scale}, користи се препоручена скала: {fixed_scale}"); + huEngine->registerEntry("sr_RS", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Неуспешно учитавање додатка {name}: {error}"); + huEngine->registerEntry("sr_RS", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Поново учитавање CM шејдера није успело, враћање на rgba/rgbx."); + huEngine->registerEntry("sr_RS", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Монитор {name}: широк спектар боја је омогућен али екран није у 10-битном режиму."); + + // sr_RS@latin (Serbian Latin) + huEngine->registerEntry("sr_RS@latin", TXT_KEY_ANR_TITLE, "Aplikacija ne reaguje"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_ANR_CONTENT, "Aplikacija {title} - {class} ne reaguje.\nŠta želite da uradite sa njom?"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_ANR_OPTION_TERMINATE, "Prekini"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_ANR_OPTION_WAIT, "Čekaj"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_ANR_PROP_UNKNOWN, "(nepoznato)"); + + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Aplikacija {app} zahteva nepoznatu dozvolu."); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Aplikacija {app} pokušava da snima tvoj ekran.\n\nDa li želiš da to dozvoliš?"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Aplikacija {app} pokušava da učita dodatak: {plugin}.\n\nDa li želiš da to dozvoliš?"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Nova tastatura je detektovana: {keyboard}.\n\nDa li želiš da dozvoliš njen rad?"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(nepoznato)"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_TITLE, "Zahtev za dozvolu"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Savet: možeš napraviti trajna pravila za ovo u Hyprland konfiguracionoj datoteci."); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_ALLOW, "Dozvoli"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Dozvoli i zapamti"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_ALLOW_ONCE, "Dozvoli jednom"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_DENY, "Odbij"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Nepoznata aplikacija (wayland client ID {wayland_id})"); + + huEngine->registerEntry("sr_RS@latin", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Izgleda da se tvojim XDG_CURRENT_DESKTOP okruženjem upravlja spolja, i trenutna vrednost je {value}.\nOvo može praviti probleme osim ako je namerno."); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_NOTIF_NO_GUIUTILS, + "Tvoj sistem nema instaliran hyprland-guiutils. Ovo je zavisnost pri pokretanju za neke dijaloge. Razmotri instalaciju."); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland nije uspeo da učita {count} ključni resurs, krivi pakera tvoje distribucije za loše odrađen posao!"; + if (assetsNo <= 4) + return "Hyprland nije uspeo da učita {count} ključna resursa, krivi pakera tvoje distribucije za loše odrađen posao!"; + return "Hyprland nije uspeo da učita {count} ključnih resursa, krivi pakera tvoje distribucije za loše odrađen posao!"; + }); + huEngine->registerEntry( + "sr_RS@latin", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Tvoj raspored monitora je nepravilno postavljen. Monitor {name} se preklapa sa drugim monitorom/monitorima u rasporedu.\nMolim te pogledaj wiki (Monitors stranicu) za " + "više informacija. Ovo će izazvati probleme."); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Monitor {name} nije uspeo da postavi nijedan traženi režim, vraćanje na režim {mode}."); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Nevalidna skala prosleđena monitoru {name}: {scale}, koristi se preporučena skala: {fixed_scale}"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Neuspešno učitavanje dodatka {name}: {error}"); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Ponovno učitavanje CM šejdera nije uspelo, vraćanje na rgba/rgbx."); + huEngine->registerEntry("sr_RS@latin", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: širok spektar boja je omogućen ali ekran nije u 10-bitnom režimu."); + + // tr_TR (Turkish) + huEngine->registerEntry("tr_TR", TXT_KEY_ANR_TITLE, "Uygulama Yanıt Vermiyor"); + huEngine->registerEntry("tr_TR", TXT_KEY_ANR_CONTENT, "Bir uygulama {title} - {class} yanıt vermiyor.\nBununla ne yapmak istiyorsun?"); + huEngine->registerEntry("tr_TR", TXT_KEY_ANR_OPTION_TERMINATE, "Sonlandır"); + huEngine->registerEntry("tr_TR", TXT_KEY_ANR_OPTION_WAIT, "Bekle"); + huEngine->registerEntry("tr_TR", TXT_KEY_ANR_PROP_UNKNOWN, "(bilinmiyor)"); + + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Bir uygulama {app} bilinmeyen bir izin istiyor."); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Bir uygulama {app} ekran kaydı yapmaya çalışıyor.\n\nİzin vermek istiyor musun?"); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Bir uygulama {app} bir eklenti kurmaya çalışıyor: {plugin}.\n\nİzin vermek istiyor musun?"); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Yeni bir klavye algılandı: {keyboard}.\n\nÇalışmasına izin vermek istiyor musun?"); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(bilinmiyor)"); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_TITLE, "İzin isteği"); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "İpucu: Hyprland config dosyasında bunlar için kalıcı kurallar atayabilirsin."); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_ALLOW, "İzin ver"); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "İzin ver ve seçimimi hatırla"); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_ALLOW_ONCE, "Yalnızca bir defa izin ver"); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_DENY, "Reddet"); + huEngine->registerEntry("tr_TR", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Bilinmeyen uygulama (wayland istemci ID {wayland_id})"); + + huEngine->registerEntry("tr_TR", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "XDG_CURRENT_DESKTOP ortamın harici olarak yönetiliyor gibi gözüküyor, ve mevcut değeri {value}.\nEğer bu bilinçli değilse sorunlara yol açabilir."); + huEngine->registerEntry("tr_TR", TXT_KEY_NOTIF_NO_GUIUTILS, + "Sisteminde hyprland-guiutils yüklü değil. Bu bazı diyaloglar için bir çalışma zamanı bağımlılığı. İndirmeyi göz önünde bulundurabilirsin."); + huEngine->registerEntry("tr_TR", TXT_KEY_NOTIF_FAILED_ASSETS, + "Hyprland {count} gerekli dosyayı yüklemekte başarısız oldu, kötü bir iş çıkardığı için kullandığın distronun paketleyicisini suçla!"); + huEngine->registerEntry( + "tr_TR", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Monitör düzenin yanlış ayarlanmış. Monitör {name} düzenindeki başka monitörlerle çakışıyor.\nLütfen daha fazla bilgi için wiki'ye (Monitörler sayfası) göz at. " + "Bu sorunlara yol açacak."); + huEngine->registerEntry("tr_TR", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Monitör {name} istenen modları ayarlamada başarısız oldu, {mode} moduna geri dönülüyor."); + huEngine->registerEntry("tr_TR", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Monitöre geçersiz ölçek iletildi {name}: {scale}, önerilen ölçek kullanılıyor: {fixed_scale}"); + huEngine->registerEntry("tr_TR", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "{name} plugini yüklenemedi: {error}"); + 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_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-бит режимында түгел."); + + // uk_UA (Ukrainian) + huEngine->registerEntry("uk_UA", TXT_KEY_ANR_TITLE, "Програма не відповідає"); + huEngine->registerEntry("uk_UA", TXT_KEY_ANR_CONTENT, "Програма {title} - {class} не відповідає.\nЩо ви хочете з нею зробити?"); + huEngine->registerEntry("uk_UA", TXT_KEY_ANR_OPTION_TERMINATE, "Завершити"); + huEngine->registerEntry("uk_UA", TXT_KEY_ANR_OPTION_WAIT, "Чекати"); + huEngine->registerEntry("uk_UA", TXT_KEY_ANR_PROP_UNKNOWN, "(невідомо)"); + + huEngine->registerEntry("uk_UA", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Програма {app} запитує невідомий дозвіл."); + huEngine->registerEntry("uk_UA", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Програма {app} намагається захопити екран.\n\nДозволити?"); + huEngine->registerEntry("uk_UA", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Програма {app} намагається завантажити плагін: {plugin}.\n\nДозволити?"); + huEngine->registerEntry("uk_UA", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Виявлено нову клавіатуру: {keyboard}.\n\nДозволити її використання?"); + huEngine->registerEntry("uk_UA", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(невідомо)"); + huEngine->registerEntry("uk_UA", TXT_KEY_PERMISSION_TITLE, "Запит дозволу"); + huEngine->registerEntry("uk_UA", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Підказка: ви можете встановити постійні правила для дозволів у файлі конфігурації Hyprland."); + huEngine->registerEntry("uk_UA", TXT_KEY_PERMISSION_ALLOW, "Дозволити"); + huEngine->registerEntry("uk_UA", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Дозволити та запам'ятати"); + huEngine->registerEntry("uk_UA", TXT_KEY_PERMISSION_ALLOW_ONCE, "Дозволити один раз"); + huEngine->registerEntry("uk_UA", TXT_KEY_PERMISSION_DENY, "Заборонити"); + huEngine->registerEntry("uk_UA", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Невідома програма (ідентифікатор клієнта wayland {wayland_id})"); + + huEngine->registerEntry( + "uk_UA", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Ваше середовище XDG_CURRENT_DESKTOP, схоже, керується ззовні, і поточне значення становить {value}.\nЦе може спричинити проблеми, якщо це не зроблено навмисно."); + huEngine->registerEntry( + "uk_UA", TXT_KEY_NOTIF_NO_GUIUTILS, + "У вашій системі не встановлено hyprland-guiutils. Це залежність, потрібна для роботи деяких діалогових вікон. Розгляньте можливість його встановлення."); + huEngine->registerEntry("uk_UA", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Не вдалося завантажити {count} необхідний ресурс, звинувачуйте пакувальника свого дистрибутива у недобросовісній роботі!"; + return "Не вдалося завантажити {count} необхідних ресурсів, звинувачуйте пакувальника свого дистрибутива у недобросовісній роботі!"; + }); + huEngine->registerEntry("uk_UA", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Макет моніторів налаштовано неправильно. Монітор {name} перекриває інші монітори у макеті.\nБудь ласка, перегляньте wiki (сторінка Monitors). " + "Це обов'язково створить проблеми."); + huEngine->registerEntry("uk_UA", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Монітор {name} не зміг встановити жодного із запитуваних режимів, повернення до режиму {mode}."); + huEngine->registerEntry("uk_UA", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, + "Неправильний масштаб переданий монітору {name}: {scale}, використання запропонованого масштабу: {fixed_scale}"); + huEngine->registerEntry("uk_UA", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Помилка завантаження плагіна {name}: {error}"); + 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?"); + huEngine->registerEntry("cs_CZ", TXT_KEY_ANR_OPTION_TERMINATE, "Ukončit"); + huEngine->registerEntry("cs_CZ", TXT_KEY_ANR_OPTION_WAIT, "Počkat"); + huEngine->registerEntry("cs_CZ", TXT_KEY_ANR_PROP_UNKNOWN, "(neznámé)"); + + huEngine->registerEntry("cs_CZ", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Aplikace {app} vyžaduje neznámé oprávnění."); + huEngine->registerEntry("cs_CZ", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Aplikace {app} se pokouší zaznamenávat vaši obrazovku.\n\nChcete jí to povolit?"); + huEngine->registerEntry("cs_CZ", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Aplikace {app} se pokouší načíst plugin: {plugin}.\n\nChcete jí to povolit?"); + huEngine->registerEntry("cs_CZ", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Byla detekována nová klávesnice: {keyboard}.\n\nChcete jí povolit?"); + huEngine->registerEntry("cs_CZ", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(neznámé)"); + huEngine->registerEntry("cs_CZ", TXT_KEY_PERMISSION_TITLE, "Žádost o oprávnění"); + huEngine->registerEntry("cs_CZ", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Tip: pro tyto případy můžete nastavit trvalá pravidla v konfiguračním souboru Hyprland."); + huEngine->registerEntry("cs_CZ", TXT_KEY_PERMISSION_ALLOW, "Povolit"); + huEngine->registerEntry("cs_CZ", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Povolit a zapamatovat"); + huEngine->registerEntry("cs_CZ", TXT_KEY_PERMISSION_ALLOW_ONCE, "Povolit jednou"); + huEngine->registerEntry("cs_CZ", TXT_KEY_PERMISSION_DENY, "Zamítnout"); + huEngine->registerEntry("cs_CZ", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Neznámá aplikace (ID klienta Wayland {wayland_id})"); + + huEngine->registerEntry( + "cs_CZ", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Proměnná prostředí XDG_CURRENT_DESKTOP se zdá být spravována externě a její aktuální hodnota je {value}.\nPokud to není záměr, může to způsobit problémy."); + huEngine->registerEntry("cs_CZ", TXT_KEY_NOTIF_NO_GUIUTILS, + "V systému není nainstalován balíček hyprland-guiutils. Jedná se o závislost pro běh některých dialogových oken. Zvažte jeho instalaci."); + huEngine->registerEntry("cs_CZ", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprlandu se nepodařilo načíst {count} nezbytnou součást. Za špatně odvedenou práci viňte tvůrce balíčku vaší distribuce!"; + if (assetsNo <= 4) + return "Hyprlandu se nepodařilo načíst {count} nezbytné součásti. Za špatně odvedenou práci viňte tvůrce balíčku vaší distribuce!"; + return "Hyprlandu se nepodařilo načíst {count} nezbytných součástí. Za špatně odvedenou práci viňte tvůrce balíčku vaší distribuce!"; + }); + huEngine->registerEntry("cs_CZ", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Rozložení vašich monitorů je nastaveno nesprávně. Monitor {name} se překrývá s ostatními monitory.\nVíce informací naleznete na wiki " + "(stránka Monitors). Toto způsobí problémy."); + huEngine->registerEntry("cs_CZ", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Monitoru {name} se nepodařilo nastavit žádný z požadovaných režimů, vrací se k režimu {mode}."); + huEngine->registerEntry("cs_CZ", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Monitoru {name} bylo předáno neplatné měřítko: {scale}, použije se navrhované měřítko: {fixed_scale}"); + huEngine->registerEntry("cs_CZ", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Nepodařilo se načíst plugin {name}: {error}"); + huEngine->registerEntry("cs_CZ", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Nepodařilo se znovu načíst CM shader, vrací se k rgba/rgbx."); + huEngine->registerEntry("cs_CZ", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: široký barevný gamut je povolen, ale displej není v 10bitovém režimu."); +} + +std::string I18n::CI18nEngine::localize(eI18nKeys key, const Hyprutils::I18n::translationVarMap& vars) { + static auto CONFIG_LOCALE = CConfigValue("general:locale"); + std::string locale = *CONFIG_LOCALE != "" ? *CONFIG_LOCALE : localeStr; + return huEngine->localizeEntry(locale, key, vars); +} diff --git a/src/i18n/Engine.hpp b/src/i18n/Engine.hpp new file mode 100644 index 00000000..79ec86f8 --- /dev/null +++ b/src/i18n/Engine.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include "../helpers/memory/Memory.hpp" +#include +#include +#include + +namespace I18n { + + enum eI18nKeys : uint8_t { + TXT_KEY_ANR_TITLE = 0, + TXT_KEY_ANR_CONTENT, + TXT_KEY_ANR_OPTION_TERMINATE, + TXT_KEY_ANR_OPTION_WAIT, + TXT_KEY_ANR_PROP_UNKNOWN, + + 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, + TXT_KEY_PERMISSION_TITLE, + TXT_KEY_PERMISSION_PERSISTENCE_HINT, + TXT_KEY_PERMISSION_ALLOW, + TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, + TXT_KEY_PERMISSION_ALLOW_ONCE, + TXT_KEY_PERMISSION_DENY, + TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, + + TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + TXT_KEY_NOTIF_NO_GUIUTILS, + TXT_KEY_NOTIF_FAILED_ASSETS, + TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + TXT_KEY_NOTIF_MONITOR_MODE_FAIL, + TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, + 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 { + public: + CI18nEngine(); + ~CI18nEngine() = default; + + std::string localize(eI18nKeys key, const std::unordered_map& vars = {}); + }; + + SP i18nEngine(); +}; diff --git a/src/init/initHelpers.cpp b/src/init/initHelpers.cpp index c27f625c..f3911c04 100644 --- a/src/init/initHelpers.cpp +++ b/src/init/initHelpers.cpp @@ -10,20 +10,20 @@ 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; } 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 e46a0963..00000000 --- a/src/layout/DwindleLayout.cpp +++ /dev/null @@ -1,1233 +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 "xwayland/XWayland.hpp" - -SWorkspaceGaps CHyprDwindleLayout::getWorkspaceGaps(const PHLWORKSPACE& pWorkspace) { - const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(pWorkspace); - 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()); - - SWorkspaceGaps gaps; - gaps.in = WORKSPACERULE.gapsIn.value_or(*PGAPSIN); - gaps.out = WORKSPACERULE.gapsOut.value_or(*PGAPSOUT); - return gaps; -} - -SNodeDisplayEdgeFlags CHyprDwindleLayout::getNodeDisplayEdgeFlags(const CBox& box, const PHLMONITOR& monitor) { - return { - .top = STICKS(box.y, monitor->m_position.y + monitor->m_reservedTopLeft.y), - .bottom = STICKS(box.y + box.h, monitor->m_position.y + monitor->m_size.y - monitor->m_reservedBottomRight.y), - .left = STICKS(box.x, monitor->m_position.x + monitor->m_reservedTopLeft.x), - .right = STICKS(box.x + box.w, monitor->m_position.x + monitor->m_size.x - monitor->m_reservedBottomRight.x), - }; -} - -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"); - - const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(workspaceID); - if (!PWORKSPACE) - return; - - const auto PMONITOR = PWORKSPACE->m_monitor.lock(); - if (!PMONITOR) - return; - - const auto edges = layout->getNodeDisplayEdgeFlags(box, PMONITOR); - auto [gapsIn, gapsOut] = layout->getWorkspaceGaps(PWORKSPACE); - - const Vector2D availableSize = box.size() - - Vector2D{(edges.left ? gapsOut.m_left : gapsIn.m_left / 2.f) + (edges.right ? gapsOut.m_right : gapsIn.m_right / 2.f), - (edges.top ? gapsOut.m_top : gapsIn.m_top / 2.f) + (edges.bottom ? gapsOut.m_bottom : gapsIn.m_bottom / 2.f)}; - - if (*PPRESERVESPLIT == 0 && *PSMARTSPLIT == 0) - splitTop = availableSize.y * *PFLMULT > availableSize.x; - - if (verticalOverride) - splitTop = true; - else if (horizontalOverride) - splitTop = false; - - const auto SPLITSIDE = !splitTop; - - if (SPLITSIDE) { - // split left/right - const float gapsAppliedToChild1 = (edges.left ? gapsOut.m_left : gapsIn.m_left / 2.f) + gapsIn.m_right / 2.f; - const float gapsAppliedToChild2 = gapsIn.m_left / 2.f + (edges.right ? gapsOut.m_right : gapsIn.m_right / 2.f); - const float totalGaps = gapsAppliedToChild1 + gapsAppliedToChild2; - const float totalAvailable = box.w - totalGaps; - - const float child1Available = totalAvailable * (splitRatio / 2.f); - const float FIRSTSIZE = child1Available + gapsAppliedToChild1; - - 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 gapsAppliedToChild1 = (edges.top ? gapsOut.m_top : gapsIn.m_top / 2.f) + gapsIn.m_bottom / 2.f; - const float gapsAppliedToChild2 = gapsIn.m_top / 2.f + (edges.bottom ? gapsOut.m_bottom : gapsIn.m_bottom / 2.f); - const float totalGaps = gapsAppliedToChild1 + gapsAppliedToChild2; - const float totalAvailable = box.h - totalGaps; - - const float child1Available = totalAvailable * (splitRatio / 2.f); - const float FIRSTSIZE = child1Available + gapsAppliedToChild1; - - 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 auto edges = getNodeDisplayEdgeFlags(pNode->box, PMONITOR); - - 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->unsetWindowData(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(edges.left ? gapsOut.m_left : gapsIn.m_left), sc(edges.top ? gapsOut.m_top : gapsIn.m_top)); - - const auto GAPOFFSETBOTTOMRIGHT = Vector2D(sc(edges.right ? gapsOut.m_right : gapsIn.m_right), sc(edges.bottom ? 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_windowData.minSize.valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}).clamp(Vector2D{0, 0}, monitorAvailable); - Vector2D maxSize = - PWINDOW->isFullscreen() ? Vector2D{INFINITY, INFINITY} : PWINDOW->m_windowData.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 (g_pCompositor->m_lastWindow.lock() && !g_pCompositor->m_lastWindow->m_isFloating && g_pCompositor->m_lastWindow.lock() != pWindow && - g_pCompositor->m_lastWindow->m_workspace == pWindow->m_workspace && g_pCompositor->m_lastWindow->m_isMapped) { - OPENINGON = getNodeFromWindow(g_pCompositor->m_lastWindow.lock()); - } 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"); - - const auto edges = getNodeDisplayEdgeFlags(NEWPARENT->box, PMONITOR); - - const auto WORKSPACE = g_pCompositor->getWorkspaceByID(PNODE->workspaceID); - auto [gapsIn, gapsOut] = getWorkspaceGaps(WORKSPACE); - - // if cursor over first child, make it first, etc - const Vector2D availableSize = NEWPARENT->box.size() - - Vector2D{(edges.left ? gapsOut.m_left : gapsIn.m_left / 2.f) + (edges.right ? gapsOut.m_right : gapsIn.m_right / 2.f), - (edges.top ? gapsOut.m_top : gapsIn.m_top / 2.f) + (edges.bottom ? gapsOut.m_bottom : gapsIn.m_bottom / 2.f)}; - - const auto SIDEBYSIDE = availableSize.x > availableSize.y * *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()); -} - -void CHyprDwindleLayout::onWindowRemovedTiling(PHLWINDOW pWindow) { - - const auto PNODE = getNodeFromWindow(pWindow); - - if (!PNODE) { - Debug::log(ERR, "onWindowRemovedTiling node null?"); - return; - } - - pWindow->unsetWindowData(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); -} - -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(); - 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 : g_pCompositor->m_lastWindow.lock(); - - if (!validMapped(PWINDOW)) - return; - - const auto PNODE = getNodeFromWindow(PWINDOW); - - if (!PNODE) { - *PWINDOW->m_realSize = - (PWINDOW->m_realSize->goal() + pixResize) - .clamp(PWINDOW->m_windowData.minSize.valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}), PWINDOW->m_windowData.maxSize.valueOr(Vector2D{INFINITY, INFINITY})); - PWINDOW->updateWindowDecos(); - return; - } - - static auto PANIMATE = CConfigValue("misc:animate_manual_resizes"); - static auto PSMARTRESIZING = CConfigValue("dwindle:smart_resizing"); - - // get some data about our window - const auto PMONITOR = PWINDOW->m_monitor.lock(); - const auto edges = getNodeDisplayEdgeFlags(CBox{PWINDOW->m_position, PWINDOW->m_size}, PMONITOR); - - 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_windowData.minSize.valueOr(Vector2D{30.0, 30.0}); - Vector2D maxSize = PWINDOW->m_windowData.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 (edges.left && edges.right) - allowedMovement.x = 0; - - if (edges.bottom && edges.top) - 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 || edges.right; - const auto TOP = corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT || edges.bottom; - const auto RIGHT = corner == CORNER_TOPRIGHT || corner == CORNER_BOTTOMRIGHT || edges.left; - const auto BOTTOM = corner == CORNER_BOTTOMLEFT || corner == CORNER_BOTTOMRIGHT || edges.top; - 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->unsetWindowData(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()) - g_pCompositor->focusWindow(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 (!g_pCompositor->m_lastMonitor) - return {}; - - // get window candidate - PHLWINDOW candidate = g_pCompositor->m_lastWindow.lock(); - - if (!candidate) - candidate = g_pCompositor->m_lastMonitor->m_activeWorkspace->getFirstWindow(); - - // create a fake node - SDwindleNodeData node; - - if (!candidate) - return g_pCompositor->m_lastMonitor->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 de80beed..00000000 --- a/src/layout/DwindleLayout.hpp +++ /dev/null @@ -1,121 +0,0 @@ -#pragma once - -#include "IHyprLayout.hpp" -#include "../config/ConfigDataValues.hpp" -#include "../desktop/DesktopTypes.hpp" - -#include -#include -#include -#include -#include - -class CHyprDwindleLayout; -enum eFullscreenMode : int8_t; - -struct SNodeDisplayEdgeFlags { - bool top = false, bottom = false, left = false, right = false; -}; - -struct SWorkspaceGaps { - CCssGapData in; - CCssGapData out; -}; - -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: - SWorkspaceGaps getWorkspaceGaps(const PHLWORKSPACE& pWorkspace); - SNodeDisplayEdgeFlags getNodeDisplayEdgeFlags(const CBox& box, const PHLMONITOR& monitor); - - 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 38868424..00000000 --- a/src/layout/IHyprLayout.cpp +++ /dev/null @@ -1,1061 +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 "../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" - -void IHyprLayout::onWindowCreated(PHLWINDOW pWindow, eDirection direction) { - CBox desiredGeometry = g_pXWaylandManager->getGeometryForWindow(pWindow); - - const bool HASPERSISTENTSIZE = std::ranges::any_of(pWindow->m_matchedRules, [](const auto& rule) { return rule->m_ruleType == CWindowRule::RULE_PERSISTENTSIZE; }); - - 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(); - g_pCompositor->updateWindowAnimatedDecorationValues(pWindow); - - 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; - } - - 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 = g_pCompositor->m_lastWindow.lock() && g_pCompositor->m_lastWindow->m_workspace == pWindow->m_workspace ? - g_pCompositor->m_lastWindow.lock() : - (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(); - - g_pCompositor->focusWindow(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); - g_pCompositor->focusWindow(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_windowData.minSize.valueOr(Vector2D(MIN_WINDOW_SIZE, MIN_WINDOW_SIZE))); - Vector2D MAXSIZE; - if (DRAGGINGWINDOW->m_windowData.maxSize.hasValue()) - MAXSIZE = DRAGGINGWINDOW->requestedMaxSize().clamp({}, DRAGGINGWINDOW->m_windowData.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_windowData.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 == g_pCompositor->m_lastWindow) - 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->unsetWindowData(PRIORITY_LAYOUT); - pWindow->updateWindowData(); - - if (pWindow == m_lastTiledWindow) - m_lastTiledWindow.reset(); - } - - g_pCompositor->updateWindowAnimatedDecorationValues(pWindow); - 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); - - if (targetBoxMonLocal.w < PMONITOR->m_size.x) { - if (targetBoxMonLocal.x < 0) - targetBoxMonLocal.x = 0; - else if (targetBoxMonLocal.x + targetBoxMonLocal.w > PMONITOR->m_size.x) - targetBoxMonLocal.x = PMONITOR->m_size.x - targetBoxMonLocal.w; - } - - if (targetBoxMonLocal.h < PMONITOR->m_size.y) { - if (targetBoxMonLocal.y < 0) - targetBoxMonLocal.y = 0; - else if (targetBoxMonLocal.y + targetBoxMonLocal.h > PMONITOR->m_size.y) - targetBoxMonLocal.y = PMONITOR->m_size.y - 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 : g_pCompositor->m_lastWindow.lock(); - - 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_windowData.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_windowData.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 != g_pCompositor->m_lastMonitor) - 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); - g_pCompositor->focusWindow(pWindow); - g_pCompositor->warpCursorTo(pWindow->middle()); -} - -Vector2D IHyprLayout::predictSizeForNewWindowFloating(PHLWINDOW pWindow) { // get all rules, see if we have any size overrides. - Vector2D sizeOverride = {}; - if (g_pCompositor->m_lastMonitor) { - - // If `persistentsize` is set, use the stored size if available. - const bool HASPERSISTENTSIZE = std::ranges::any_of(pWindow->m_matchedRules, [](const auto& rule) { return rule->m_ruleType == CWindowRule::RULE_PERSISTENTSIZE; }); - - 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(); - } - - for (auto const& r : g_pConfigManager->getMatchingRules(pWindow, true, true)) { - if (r->m_ruleType != CWindowRule::RULE_SIZE) - continue; - - try { - const auto VALUE = r->m_rule.substr(r->m_rule.find(' ') + 1); - const auto SIZEXSTR = VALUE.substr(0, VALUE.find(' ')); - const auto SIZEYSTR = VALUE.substr(VALUE.find(' ') + 1); - - const auto MAXSIZE = pWindow->requestedMaxSize(); - - const float SIZEX = SIZEXSTR == "max" ? std::clamp(MAXSIZE.x, MIN_WINDOW_SIZE, g_pCompositor->m_lastMonitor->m_size.x) : - stringToPercentage(SIZEXSTR, g_pCompositor->m_lastMonitor->m_size.x); - - const float SIZEY = SIZEYSTR == "max" ? std::clamp(MAXSIZE.y, MIN_WINDOW_SIZE, g_pCompositor->m_lastMonitor->m_size.y) : - stringToPercentage(SIZEYSTR, g_pCompositor->m_lastMonitor->m_size.y); - - sizeOverride = {SIZEX, SIZEY}; - - } catch (...) { Debug::log(LOG, "Rule size failed, rule: {} -> {}", r->m_rule, r->m_value); } - break; - } - } - - return sizeOverride; -} - -Vector2D IHyprLayout::predictSizeForNewWindow(PHLWINDOW pWindow) { - bool shouldBeFloated = g_pXWaylandManager->shouldBeFloated(pWindow, true); - - if (!shouldBeFloated) { - for (auto const& r : g_pConfigManager->getMatchingRules(pWindow, true, true)) { - if (r->m_ruleType != CWindowRule::RULE_FLOAT) - continue; - - shouldBeFloated = true; - break; - } - } - - 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_windowData.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 d97a2ba8..00000000 --- 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 00000000..4a93809c --- /dev/null +++ b/src/layout/LayoutManager.cpp @@ -0,0 +1,345 @@ +#include "LayoutManager.hpp" + +#include "space/Space.hpp" +#include "target/Target.hpp" + +#include "../config/ConfigManager.hpp" +#include "../Compositor.hpp" +#include "../desktop/state/FocusState.hpp" +#include "../desktop/view/Group.hpp" +#include "../event/EventBus.hpp" + +using namespace Layout; + +CLayoutManager::CLayoutManager() = default; + +void CLayoutManager::newTarget(SP target, SP space) { + // on a new target: remember desired pos for float, if available + if (const auto DESIRED_GEOM = target->desiredGeometry(); DESIRED_GEOM) + target->rememberFloatingSize(DESIRED_GEOM->size); + + target->assignToSpace(space); +} + +void CLayoutManager::removeTarget(SP target) { + target->assignToSpace(nullptr); +} + +void CLayoutManager::changeFloatingMode(SP target) { + if (!target->space()) + return; + + target->space()->toggleTargetFloating(target); +} + +void CLayoutManager::beginDragTarget(SP target, eMouseBindMode mode) { + m_dragStateController->dragBegin(target, mode); +} + +void CLayoutManager::moveMouse(const Vector2D& mousePos) { + m_dragStateController->mouseMove(mousePos); +} + +void CLayoutManager::resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner) { + if (target->isPseudo()) { + auto fixedΔ = Δ; + if (corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT) + fixedΔ.x = -fixedΔ.x; + if (corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT) + fixedΔ.y = -fixedΔ.y; + + auto newPseudoSize = target->pseudoSize() + fixedΔ; + const auto TARGET_TILE_SIZE = target->position().size(); + newPseudoSize.x = std::clamp(newPseudoSize.x, MIN_WINDOW_SIZE, TARGET_TILE_SIZE.x); + newPseudoSize.y = std::clamp(newPseudoSize.y, MIN_WINDOW_SIZE, TARGET_TILE_SIZE.y); + + target->setPseudoSize(newPseudoSize); + + return; + } + + target->space()->resizeTarget(Δ, target, corner); +} + +void CLayoutManager::setTargetGeom(const CBox& box, SP target) { + if (!target->floating()) + return; + + target->space()->setTargetGeom(box, target); +} + +std::expected CLayoutManager::layoutMsg(const std::string_view& sv) { + + const auto MONITOR = Desktop::focusState()->monitor(); + // forward to the active workspace + if (!MONITOR) + return std::unexpected("No monitor, can't find ws to target"); + + auto ws = MONITOR->m_activeSpecialWorkspace ? MONITOR->m_activeSpecialWorkspace : MONITOR->m_activeWorkspace; + + if (!ws) + return std::unexpected("No workspace, can't target"); + + return ws->m_space->layoutMsg(sv); +} + +void CLayoutManager::moveTarget(const Vector2D& Δ, SP target) { + if (!target->floating()) + return; + + target->space()->moveTarget(Δ, target); +} + +void CLayoutManager::endDragTarget() { + m_dragStateController->dragEnd(); +} + +void CLayoutManager::fullscreenRequestForTarget(SP target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode) { + target->space()->setFullscreen(target, effectiveMode); +} + +void CLayoutManager::switchTargets(SP a, SP b, bool preserveFocus) { + + if (preserveFocus) { + a->swap(b); + return; + } + + const auto IS_A_ACTIVE = Desktop::focusState()->window() == a->window(); + const auto IS_B_ACTIVE = Desktop::focusState()->window() == b->window(); + + a->swap(b); + + if (IS_A_ACTIVE && b->window()) + Desktop::focusState()->fullWindowFocus(b->window(), Desktop::FOCUS_REASON_KEYBIND); + + if (IS_B_ACTIVE && a->window()) + Desktop::focusState()->fullWindowFocus(a->window(), Desktop::FOCUS_REASON_KEYBIND); +} + +void CLayoutManager::moveInDirection(SP target, const std::string& direction, bool silent) { + Math::eDirection dir = Math::fromChar(direction.at(0)); + if (dir == Math::DIRECTION_DEFAULT) { + Log::logger->log(Log::ERR, "invalid direction for moveInDirection: {}", direction); + return; + } + + target->space()->moveTargetInDirection(target, dir, silent); +} + +SP CLayoutManager::getNextCandidate(SP space, SP from) { + return space->getNextCandidate(from); +} + +bool CLayoutManager::isReachable(SP target) { + return true; +} + +void CLayoutManager::bringTargetToTop(SP target) { + if (!target) + return; + + if (target->window()->m_group) { + // grouped, change the current to this window + target->window()->m_group->setCurrent(target->window()); + } +} + +std::optional CLayoutManager::predictSizeForNewTiledTarget() { + const auto FOCUSED_MON = Desktop::focusState()->monitor(); + + if (!FOCUSED_MON || !FOCUSED_MON->m_activeWorkspace) + return std::nullopt; + + if (FOCUSED_MON->m_activeSpecialWorkspace) + return FOCUSED_MON->m_activeSpecialWorkspace->m_space->predictSizeForNewTiledTarget(); + + return FOCUSED_MON->m_activeWorkspace->m_space->predictSizeForNewTiledTarget(); +} + +const UP& CLayoutManager::dragController() { + return m_dragStateController; +} + +static inline bool canSnap(const double SIDEA, const double SIDEB, const double GAP) { + return std::abs(SIDEA - SIDEB) < GAP; +} + +static void snapMove(double& start, double& end, const double P) { + end = P + (end - start); + start = P; +} + +static void snapResize(double& start, double& end, const double P) { + start = P; +} + +using SnapFn = std::function; + +void CLayoutManager::performSnap(Vector2D& sourcePos, Vector2D& sourceSize, SP DRAGGINGTARGET, const eMouseBindMode MODE, const int CORNER, const Vector2D& BEGINSIZE) { + + const auto DRAGGINGWINDOW = DRAGGINGTARGET->window(); + + if (!Desktop::View::validMapped(DRAGGINGWINDOW)) + return; + + static auto SNAPWINDOWGAP = CConfigValue("general:snap:window_gap"); + static auto SNAPMONITORGAP = CConfigValue("general:snap:monitor_gap"); + static auto SNAPBORDEROVERLAP = CConfigValue("general:snap:border_overlap"); + static auto SNAPRESPECTGAPS = CConfigValue("general:snap:respect_gaps"); + + static auto PGAPSIN = CConfigValue("general:gaps_in"); + static auto PGAPSOUT = CConfigValue("general:gaps_out"); + const auto GAPSNONE = CCssGapData{0, 0, 0, 0}; + + const SnapFn SNAP = (MODE == MBIND_MOVE) ? snapMove : snapResize; + int snaps = 0; + + struct SRange { + double start = 0; + double end = 0; + }; + const auto EXTENTS = DRAGGINGWINDOW->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS); + SRange sourceX = {sourcePos.x - EXTENTS.topLeft.x, sourcePos.x + sourceSize.x + EXTENTS.bottomRight.x}; + SRange sourceY = {sourcePos.y - EXTENTS.topLeft.y, sourcePos.y + sourceSize.y + EXTENTS.bottomRight.y}; + + if (*SNAPWINDOWGAP) { + const double GAPSIZE = *SNAPWINDOWGAP; + const auto WSID = DRAGGINGWINDOW->workspaceID(); + const bool HASFULLSCREEN = DRAGGINGWINDOW->m_workspace && DRAGGINGWINDOW->m_workspace->m_hasFullscreenWindow; + + const auto* GAPSIN = *SNAPRESPECTGAPS ? sc(PGAPSIN.ptr()->getData()) : &GAPSNONE; + const double GAPSX = GAPSIN->m_left + GAPSIN->m_right; + const double GAPSY = GAPSIN->m_top + GAPSIN->m_bottom; + + for (auto& other : g_pCompositor->m_windows) { + if ((HASFULLSCREEN && !other->m_createdOverFullscreen) || other == DRAGGINGWINDOW || other->workspaceID() != WSID || !other->m_isMapped || other->m_fadingOut || + other->isX11OverrideRedirect()) + continue; + + const CBox SURF = other->getWindowBoxUnified(Desktop::View::RESERVED_EXTENTS); + const SRange SURFBX = {SURF.x - GAPSX, SURF.x + SURF.w + GAPSX}; + const SRange SURFBY = {SURF.y - GAPSY, SURF.y + SURF.h + GAPSY}; + + // only snap windows if their ranges overlap in the opposite axis + if (sourceY.start <= SURFBY.end && SURFBY.start <= sourceY.end) { + if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && canSnap(sourceX.start, SURFBX.end, GAPSIZE)) { + SNAP(sourceX.start, sourceX.end, SURFBX.end); + snaps |= SNAP_LEFT; + } else if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && canSnap(sourceX.end, SURFBX.start, GAPSIZE)) { + SNAP(sourceX.end, sourceX.start, SURFBX.start); + snaps |= SNAP_RIGHT; + } + } + if (sourceX.start <= SURFBX.end && SURFBX.start <= sourceX.end) { + if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && canSnap(sourceY.start, SURFBY.end, GAPSIZE)) { + SNAP(sourceY.start, sourceY.end, SURFBY.end); + snaps |= SNAP_UP; + } else if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && canSnap(sourceY.end, SURFBY.start, GAPSIZE)) { + SNAP(sourceY.end, sourceY.start, SURFBY.start); + snaps |= SNAP_DOWN; + } + } + + // corner snapping + if (sourceX.start == SURFBX.end || SURFBX.start == sourceX.end) { + const SRange SURFY = {SURFBY.start + GAPSY, SURFBY.end - GAPSY}; + if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && !(snaps & SNAP_UP) && canSnap(sourceY.start, SURFY.start, GAPSIZE)) { + SNAP(sourceY.start, sourceY.end, SURFY.start); + snaps |= SNAP_UP; + } else if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && !(snaps & SNAP_DOWN) && canSnap(sourceY.end, SURFY.end, GAPSIZE)) { + SNAP(sourceY.end, sourceY.start, SURFY.end); + snaps |= SNAP_DOWN; + } + } + if (sourceY.start == SURFBY.end || SURFBY.start == sourceY.end) { + const SRange SURFX = {SURFBX.start + GAPSX, SURFBX.end - GAPSX}; + if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && !(snaps & SNAP_LEFT) && canSnap(sourceX.start, SURFX.start, GAPSIZE)) { + SNAP(sourceX.start, sourceX.end, SURFX.start); + snaps |= SNAP_LEFT; + } else if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && !(snaps & SNAP_RIGHT) && canSnap(sourceX.end, SURFX.end, GAPSIZE)) { + SNAP(sourceX.end, sourceX.start, SURFX.end); + snaps |= SNAP_RIGHT; + } + } + } + } + + if (*SNAPMONITORGAP) { + const double GAPSIZE = *SNAPMONITORGAP; + const auto EXTENTNONE = SBoxExtents{{0, 0}, {0, 0}}; + const auto* EXTENTDIFF = *SNAPBORDEROVERLAP ? &EXTENTS : &EXTENTNONE; + const auto MON = DRAGGINGWINDOW->m_monitor.lock(); + + const auto* GAPSOUT = *SNAPRESPECTGAPS ? sc(PGAPSOUT.ptr()->getData()) : &GAPSNONE; + const auto WORK_AREA = Desktop::CReservedArea{GAPSOUT->m_top, GAPSOUT->m_right, GAPSOUT->m_bottom, GAPSOUT->m_left}.apply(MON->logicalBoxMinusReserved()); + + SRange monX = {WORK_AREA.x, WORK_AREA.x + WORK_AREA.w}; + SRange monY = {WORK_AREA.y, WORK_AREA.y + WORK_AREA.h}; + + const bool HAS_LEFT = MON->m_reservedArea.left() > 0; + const bool HAS_TOP = MON->m_reservedArea.top() > 0; + const bool HAS_BOTTOM = MON->m_reservedArea.bottom() > 0; + const bool HAS_RIGHT = MON->m_reservedArea.right() > 0; + + if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && + ((HAS_LEFT && canSnap(sourceX.start, monX.start, GAPSIZE)) || canSnap(sourceX.start, (monX.start -= MON->m_reservedArea.left() + EXTENTDIFF->topLeft.x), GAPSIZE))) { + SNAP(sourceX.start, sourceX.end, monX.start); + snaps |= SNAP_LEFT; + } + if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && + ((HAS_RIGHT && canSnap(sourceX.end, monX.end, GAPSIZE)) || canSnap(sourceX.end, (monX.end += MON->m_reservedArea.right() + EXTENTDIFF->bottomRight.x), GAPSIZE))) { + SNAP(sourceX.end, sourceX.start, monX.end); + snaps |= SNAP_RIGHT; + } + if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && + ((HAS_TOP && canSnap(sourceY.start, monY.start, GAPSIZE)) || canSnap(sourceY.start, (monY.start -= MON->m_reservedArea.top() + EXTENTDIFF->topLeft.y), GAPSIZE))) { + SNAP(sourceY.start, sourceY.end, monY.start); + snaps |= SNAP_UP; + } + if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && + ((HAS_BOTTOM && canSnap(sourceY.end, monY.end, GAPSIZE)) || canSnap(sourceY.end, (monY.end += MON->m_reservedArea.bottom() + EXTENTDIFF->bottomRight.y), GAPSIZE))) { + SNAP(sourceY.end, sourceY.start, monY.end); + snaps |= SNAP_DOWN; + } + } + + // remove extents from main surface + sourceX = {sourceX.start + EXTENTS.topLeft.x, sourceX.end - EXTENTS.bottomRight.x}; + sourceY = {sourceY.start + EXTENTS.topLeft.y, sourceY.end - EXTENTS.bottomRight.y}; + + if (MODE == MBIND_RESIZE_FORCE_RATIO) { + if ((CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && snaps & SNAP_LEFT) || (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && snaps & SNAP_RIGHT)) { + const double SIZEY = (sourceX.end - sourceX.start) * (BEGINSIZE.y / BEGINSIZE.x); + if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT)) + sourceY.start = sourceY.end - SIZEY; + else + sourceY.end = sourceY.start + SIZEY; + } else if ((CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && snaps & SNAP_UP) || (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && snaps & SNAP_DOWN)) { + const double SIZEX = (sourceY.end - sourceY.start) * (BEGINSIZE.x / BEGINSIZE.y); + if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT)) + sourceX.start = sourceX.end - SIZEX; + else + sourceX.end = sourceX.start + SIZEX; + } + } + + sourcePos = {sourceX.start, sourceY.start}; + sourceSize = {sourceX.end - sourceX.start, sourceY.end - sourceY.start}; +} + +void CLayoutManager::recalculateMonitor(PHLMONITOR m) { + if (m->m_activeSpecialWorkspace) + m->m_activeSpecialWorkspace->m_space->recalculate(); + if (m->m_activeWorkspace) + m->m_activeWorkspace->m_space->recalculate(); +} + +void CLayoutManager::invalidateMonitorGeometries(PHLMONITOR m) { + for (const auto& ws : g_pCompositor->getWorkspaces()) { + if (ws && ws->m_monitor == m) { + ws->m_space->recheckWorkArea(); + ws->m_space->recalculate(); + } + } +} diff --git a/src/layout/LayoutManager.hpp b/src/layout/LayoutManager.hpp new file mode 100644 index 00000000..e99911d5 --- /dev/null +++ b/src/layout/LayoutManager.hpp @@ -0,0 +1,87 @@ +#pragma once + +#include "../helpers/memory/Memory.hpp" +#include "../helpers/math/Math.hpp" +#include "../managers/input/InputManager.hpp" + +#include "supplementary/DragController.hpp" + +#include +#include + +enum eFullscreenMode : int8_t; + +namespace Layout { + class ITarget; + class CSpace; + + enum eRectCorner : uint8_t { + CORNER_NONE = 0, + CORNER_TOPLEFT = (1 << 0), + CORNER_TOPRIGHT = (1 << 1), + CORNER_BOTTOMRIGHT = (1 << 2), + CORNER_BOTTOMLEFT = (1 << 3), + }; + + inline eRectCorner cornerFromBox(const CBox& box, const Vector2D& pos) { + const auto CENTER = box.middle(); + + if (pos.x < CENTER.x) + return pos.y < CENTER.y ? CORNER_TOPLEFT : CORNER_BOTTOMLEFT; + return pos.y < CENTER.y ? CORNER_TOPRIGHT : CORNER_BOTTOMRIGHT; + } + + enum eSnapEdge : uint8_t { + SNAP_INVALID = 0, + SNAP_UP = (1 << 0), + SNAP_DOWN = (1 << 1), + SNAP_LEFT = (1 << 2), + SNAP_RIGHT = (1 << 3), + }; + + class CLayoutManager { + public: + CLayoutManager(); + ~CLayoutManager() = default; + + void newTarget(SP target, SP space); + void removeTarget(SP target); + + void changeFloatingMode(SP target); + + void beginDragTarget(SP target, eMouseBindMode mode); + void moveMouse(const Vector2D& mousePos); + void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + void moveTarget(const Vector2D& Δ, SP target); + void setTargetGeom(const CBox& box, SP target); // floats only + void endDragTarget(); + + std::expected layoutMsg(const std::string_view& sv); + + void fullscreenRequestForTarget(SP target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode); + + void switchTargets(SP a, SP b, bool preserveFocus = true); + + void moveInDirection(SP target, const std::string& direction, bool silent = false); + + SP getNextCandidate(SP space, SP from); + + bool isReachable(SP target); + + void bringTargetToTop(SP target); + + std::optional predictSizeForNewTiledTarget(); + + void performSnap(Vector2D& sourcePos, Vector2D& sourceSize, SP target, eMouseBindMode mode, int corner, const Vector2D& beginSize); + + void invalidateMonitorGeometries(PHLMONITOR); + void recalculateMonitor(PHLMONITOR); + + const UP& dragController(); + + private: + UP m_dragStateController = makeUnique(); + }; +} + +inline UP g_layoutManager; \ No newline at end of file diff --git a/src/layout/MasterLayout.cpp b/src/layout/MasterLayout.cpp deleted file mode 100644 index b40c339a..00000000 --- a/src/layout/MasterLayout.cpp +++ /dev/null @@ -1,1532 +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 "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(g_pCompositor->m_lastWindow.lock()); - 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(g_pCompositor->m_lastWindow.lock()) && g_pCompositor->m_lastWindow->m_workspace == pWindow->m_workspace ? - getNodeFromWindow(g_pCompositor->m_lastWindow.lock()) : - 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()); -} - -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->unsetWindowData(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()); -} - -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(); - 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->unsetWindowData(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_windowData.minSize.valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}).clamp(Vector2D{0, 0}, monitorAvailable); - Vector2D maxSize = - PWINDOW->isFullscreen() ? Vector2D{INFINITY, INFINITY} : PWINDOW->m_windowData.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 : g_pCompositor->m_lastWindow.lock(); - - if (!validMapped(PWINDOW)) - return; - - const auto PNODE = getNodeFromWindow(PWINDOW); - - if (!PNODE) { - *PWINDOW->m_realSize = - (PWINDOW->m_realSize->goal() + pixResize) - .clamp(PWINDOW->m_windowData.minSize.valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}), PWINDOW->m_windowData.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->unsetWindowData(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(); - g_pCompositor->setActiveMonitor(pMonitor); - } - onWindowCreatedTiling(pWindow); - } else { - // if same monitor, switch windows - switchWindows(pWindow, PWINDOW2); - if (silent) - g_pCompositor->focusWindow(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; - - if (header.pWindow->isFullscreen()) { - const auto PWORKSPACE = header.pWindow->m_workspace; - const auto FSMODE = header.pWindow->m_fullscreenState.internal; - static auto INHERITFULLSCREEN = CConfigValue("master:inherit_fullscreen"); - g_pCompositor->setWindowFullscreenInternal(header.pWindow, FSMODE_NONE); - g_pCompositor->focusWindow(PWINDOWTOCHANGETO); - if (*INHERITFULLSCREEN) - g_pCompositor->setWindowFullscreenInternal(PWINDOWTOCHANGETO, FSMODE); - } else { - g_pCompositor->focusWindow(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 (!g_pCompositor->m_lastMonitor) - return {}; - - const int NODES = getNodesOnWorkspace(g_pCompositor->m_lastMonitor->m_activeWorkspace->m_id); - - if (NODES <= 0) - return g_pCompositor->m_lastMonitor->m_size; - - const auto MASTER = getMasterNodeOnWorkspace(g_pCompositor->m_lastMonitor->m_activeWorkspace->m_id); - if (!MASTER) // wtf - return {}; - - if (*PNEWSTATUS == "master") { - return MASTER->size; - } else { - const auto SLAVES = NODES - getMastersOnWorkspace(g_pCompositor->m_lastMonitor->m_activeWorkspace->m_id); - - // TODO: make this better - return {g_pCompositor->m_lastMonitor->m_size.x - MASTER->size.x, g_pCompositor->m_lastMonitor->m_size.y / (SLAVES + 1)}; - } - - return {}; -} - -void CHyprMasterLayout::onEnable() { - for (auto const& w : g_pCompositor->m_windows) { - if (w->m_isFloating || !w->m_isMapped || w->isHidden()) - continue; - - onWindowCreatedTiling(w); - } -} - -void CHyprMasterLayout::onDisable() { - m_masterNodesData.clear(); -} diff --git a/src/layout/MasterLayout.hpp b/src/layout/MasterLayout.hpp deleted file mode 100644 index a5968916..00000000 --- a/src/layout/MasterLayout.hpp +++ /dev/null @@ -1,112 +0,0 @@ -#pragma once - -#include "IHyprLayout.hpp" -#include "../desktop/DesktopTypes.hpp" -#include "../helpers/varlist/VarList.hpp" -#include -#include -#include - -enum eFullscreenMode : int8_t; - -//orientation determines which side of the screen the master area resides -enum eOrientation : uint8_t { - ORIENTATION_LEFT = 0, - ORIENTATION_TOP, - ORIENTATION_RIGHT, - ORIENTATION_BOTTOM, - ORIENTATION_CENTER -}; - -struct SMasterNodeData { - bool isMaster = false; - float percMaster = 0.5f; - - PHLWINDOWREF pWindow; - - Vector2D position; - Vector2D size; - - float percSize = 1.f; // size multiplier for resizing children - - WORKSPACEID workspaceID = WORKSPACE_INVALID; - - bool ignoreFullscreenChecks = false; - - // - bool operator==(const SMasterNodeData& rhs) const { - return pWindow.lock() == rhs.pWindow.lock(); - } -}; - -struct SMasterWorkspaceData { - WORKSPACEID workspaceID = WORKSPACE_INVALID; - eOrientation orientation = ORIENTATION_LEFT; - // Previously focused non-master window when `focusmaster previous` command was issued - PHLWINDOWREF focusMasterPrev; - - // - bool operator==(const SMasterWorkspaceData& rhs) const { - return workspaceID == rhs.workspaceID; - } -}; - -class CHyprMasterLayout : public IHyprLayout { - public: - virtual void onWindowCreatedTiling(PHLWINDOW, eDirection direction = DIRECTION_DEFAULT); - virtual void onWindowRemovedTiling(PHLWINDOW); - virtual bool isWindowTiled(PHLWINDOW); - virtual void recalculateMonitor(const MONITORID&); - virtual void recalculateWindow(PHLWINDOW); - virtual void resizeActiveWindow(const Vector2D&, eRectCorner corner = CORNER_NONE, PHLWINDOW pWindow = nullptr); - virtual void fullscreenRequestForWindow(PHLWINDOW pWindow, const eFullscreenMode CURRENT_EFFECTIVE_MODE, const eFullscreenMode EFFECTIVE_MODE); - virtual std::any layoutMessage(SLayoutMessageHeader, std::string); - virtual SWindowRenderLayoutHints requestRenderHints(PHLWINDOW); - virtual void switchWindows(PHLWINDOW, PHLWINDOW); - virtual void moveWindowTo(PHLWINDOW, const std::string& dir, bool silent); - virtual void alterSplitRatio(PHLWINDOW, float, bool); - virtual std::string getLayoutName(); - virtual void replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to); - virtual Vector2D predictSizeForNewWindowTiled(); - - virtual void onEnable(); - virtual void onDisable(); - - private: - std::list m_masterNodesData; - std::vector m_masterWorkspacesData; - - bool m_forceWarps = false; - - void buildOrientationCycleVectorFromVars(std::vector& cycle, CVarList& vars); - void buildOrientationCycleVectorFromEOperation(std::vector& cycle); - void runOrientationCycle(SLayoutMessageHeader& header, CVarList* vars, int next); - eOrientation getDynamicOrientation(PHLWORKSPACE); - int getNodesOnWorkspace(const WORKSPACEID&); - void applyNodeDataToWindow(SMasterNodeData*); - SMasterNodeData* getNodeFromWindow(PHLWINDOW); - SMasterNodeData* getMasterNodeOnWorkspace(const WORKSPACEID&); - SMasterWorkspaceData* getMasterWorkspaceData(const WORKSPACEID&); - void calculateWorkspace(PHLWORKSPACE); - PHLWINDOW getNextWindow(PHLWINDOW, bool, bool); - int getMastersOnWorkspace(const WORKSPACEID&); - - friend struct SMasterNodeData; - friend struct SMasterWorkspaceData; -}; - -template -struct std::formatter : std::formatter { - template - auto format(const SMasterNodeData* const& node, FormatContext& ctx) const { - auto out = ctx.out(); - if (!node) - return std::format_to(out, "[Node nullptr]"); - std::format_to(out, "[Node {:x}: workspace: {}, pos: {:j2}, size: {:j2}", rc(node), node->workspaceID, node->position, node->size); - if (node->isMaster) - std::format_to(out, ", master"); - if (!node->pWindow.expired()) - std::format_to(out, ", window: {:x}", node->pWindow.lock()); - return std::format_to(out, "]"); - } -}; diff --git a/src/layout/algorithm/Algorithm.cpp b/src/layout/algorithm/Algorithm.cpp new file mode 100644 index 00000000..cfb5b7e3 --- /dev/null +++ b/src/layout/algorithm/Algorithm.cpp @@ -0,0 +1,271 @@ +#include "Algorithm.hpp" + +#include "FloatingAlgorithm.hpp" +#include "TiledAlgorithm.hpp" +#include "../target/WindowTarget.hpp" +#include "../space/Space.hpp" +#include "../../desktop/view/Window.hpp" +#include "../../desktop/history/WindowHistoryTracker.hpp" +#include "../../helpers/Monitor.hpp" +#include "../../render/Renderer.hpp" + +#include "../../debug/log/Logger.hpp" + +using namespace Layout; + +SP CAlgorithm::create(UP&& tiled, UP&& floating, SP space) { + auto algo = SP(new CAlgorithm(std::move(tiled), std::move(floating), space)); + algo->m_self = algo; + algo->m_tiled->m_parent = algo; + algo->m_floating->m_parent = algo; + return algo; +} + +CAlgorithm::CAlgorithm(UP&& tiled, UP&& floating, SP space) : + m_tiled(std::move(tiled)), m_floating(std::move(floating)), m_space(space) { + ; +} + +void CAlgorithm::addTarget(SP target) { + const bool SHOULD_FLOAT = target->floating(); + + if (SHOULD_FLOAT) { + m_floatingTargets.emplace_back(target); + m_floating->newTarget(target); + } else { + m_tiledTargets.emplace_back(target); + m_tiled->newTarget(target); + } +} + +void CAlgorithm::removeTarget(SP target) { + const bool IS_FLOATING = std::ranges::contains(m_floatingTargets, target); + + if (IS_FLOATING) { + std::erase(m_floatingTargets, target); + m_floating->removeTarget(target); + return; + } + + const bool IS_TILED = std::ranges::contains(m_tiledTargets, target); + + if (IS_TILED) { + std::erase(m_tiledTargets, target); + m_tiled->removeTarget(target); + return; + } + + Log::logger->log(Log::ERR, "BUG THIS: CAlgorithm::removeTarget, but not found"); +} + +void CAlgorithm::moveTarget(SP target, std::optional focalPoint, bool reposition) { + const bool SHOULD_FLOAT = target->floating(); + + if (SHOULD_FLOAT) { + m_floatingTargets.emplace_back(target); + if (reposition) + m_floating->newTarget(target); + else + m_floating->movedTarget(target, focalPoint); + } else { + m_tiledTargets.emplace_back(target); + if (reposition) + m_tiled->newTarget(target); + else + m_tiled->movedTarget(target, focalPoint); + } +} + +SP CAlgorithm::space() const { + return m_space.lock(); +} + +void CAlgorithm::setFloating(SP target, bool floating, bool reposition) { + removeTarget(target); + + g_pHyprRenderer->damageWindow(target->window()); + + target->setFloating(floating); + + moveTarget(target, std::nullopt, reposition); + + g_pHyprRenderer->damageWindow(target->window()); +} + +size_t CAlgorithm::tiledTargets() const { + return m_tiledTargets.size(); +} + +size_t CAlgorithm::floatingTargets() const { + return m_floatingTargets.size(); +} + +void CAlgorithm::recalculate() { + m_tiled->recalculate(); + m_floating->recalculate(); + + const auto PWORKSPACE = m_space->workspace(); + const auto PMONITOR = PWORKSPACE->m_monitor; + + if (PWORKSPACE->m_hasFullscreenWindow && PMONITOR) { + // massive hack from the fullscreen func + const auto PFULLWINDOW = PWORKSPACE->getFullscreenWindow(); + + if (PFULLWINDOW) { + if (PWORKSPACE->m_fullscreenMode == FSMODE_FULLSCREEN) { + *PFULLWINDOW->m_realPosition = PMONITOR->m_position; + *PFULLWINDOW->m_realSize = PMONITOR->m_size; + } else if (PWORKSPACE->m_fullscreenMode == FSMODE_MAXIMIZED) + PFULLWINDOW->layoutTarget()->setPositionGlobal(m_space->workArea()); + } + + return; + } +} + +void CAlgorithm::recenter(SP t) { + if (t->floating()) + m_floating->recenter(t); +} + +std::expected CAlgorithm::layoutMsg(const std::string_view& sv) { + if (const auto ret = m_floating->layoutMsg(sv); !ret) + return ret; + return m_tiled->layoutMsg(sv); +} + +std::optional CAlgorithm::predictSizeForNewTiledTarget() { + return m_tiled->predictSizeForNewTarget(); +} + +void CAlgorithm::resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner) { + if (target->floating()) + m_floating->resizeTarget(Δ, target, corner); + else + m_tiled->resizeTarget(Δ, target, corner); +} + +void CAlgorithm::moveTarget(const Vector2D& Δ, SP target) { + if (target->floating()) + m_floating->moveTarget(Δ, target); +} + +void CAlgorithm::swapTargets(SP a, SP b) { + auto swapFirst = [&a, &b](std::vector>& targets) -> bool { + auto ia = std::ranges::find(targets, a); + auto ib = std::ranges::find(targets, b); + + if (ia != std::ranges::end(targets) && ib != std::ranges::end(targets)) { + std::iter_swap(ia, ib); + return true; + } else if (ia != std::ranges::end(targets)) + *ia = b; + else if (ib != std::ranges::end(targets)) + *ib = a; + + return false; + }; + + if (!swapFirst(m_tiledTargets)) + swapFirst(m_floatingTargets); + + const WP algA = a->floating() ? WP(m_floating) : WP(m_tiled); + const WP algB = b->floating() ? WP(m_floating) : WP(m_tiled); + + algA->swapTargets(a, b); + if (algA != algB) + algB->swapTargets(b, a); +} + +void CAlgorithm::moveTargetInDirection(SP t, Math::eDirection dir, bool silent) { + if (t->floating()) + m_floating->moveTargetInDirection(t, dir, silent); + else + m_tiled->moveTargetInDirection(t, dir, silent); +} + +void CAlgorithm::updateFloatingAlgo(UP&& algo) { + algo->m_parent = m_self; + + for (const auto& t : m_floatingTargets) { + const auto TARGET = t.lock(); + if (!TARGET) + continue; + + // Unhide windows when switching layouts to prevent them from being permanently lost + const auto WINDOW = TARGET->window(); + if (WINDOW) + WINDOW->setHidden(false); + + m_floating->removeTarget(TARGET); + algo->newTarget(TARGET); + } + + m_floating = std::move(algo); +} + +void CAlgorithm::updateTiledAlgo(UP&& algo) { + algo->m_parent = m_self; + + for (const auto& t : m_tiledTargets) { + const auto TARGET = t.lock(); + if (!TARGET) + continue; + + // Unhide windows when switching layouts to prevent them from being permanently lost + // This is a safeguard for layouts (including third-party plugins) that use setHidden + const auto WINDOW = TARGET->window(); + if (WINDOW) + WINDOW->setHidden(false); + + m_tiled->removeTarget(TARGET); + algo->newTarget(TARGET); + } + + m_tiled = std::move(algo); +} + +const UP& CAlgorithm::tiledAlgo() const { + return m_tiled; +} + +const UP& CAlgorithm::floatingAlgo() const { + return m_floating; +} + +SP CAlgorithm::getNextCandidate(SP old) { + if (old->floating()) { + // use window history to determine best target + for (const auto& w : Desktop::History::windowTracker()->fullHistory() | std::views::reverse) { + if (!w->m_workspace || w->m_workspace->m_space != m_space || !w->layoutTarget() || !w->layoutTarget()->space()) + continue; + + return w->layoutTarget(); + } + + // no history, fall back + } else { + // ask the layout + const auto CANDIDATE = m_tiled->getNextCandidate(old); + if (CANDIDATE) + return CANDIDATE; + + // no candidate, fall back + } + + // fallback: try to focus anything + if (!m_tiledTargets.empty()) + return m_tiledTargets.back().lock(); + if (!m_floatingTargets.empty()) + return m_floatingTargets.back().lock(); + + // god damn it, maybe empty? + return nullptr; +} + +void CAlgorithm::setTargetGeom(const CBox& box, SP target) { + if (!target->floating() || !std::ranges::contains(m_floatingTargets, target)) + return; + + m_floating->setTargetGeom(box, target); +} diff --git a/src/layout/algorithm/Algorithm.hpp b/src/layout/algorithm/Algorithm.hpp new file mode 100644 index 00000000..7df6c5c1 --- /dev/null +++ b/src/layout/algorithm/Algorithm.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include "../../helpers/math/Math.hpp" +#include "../../helpers/math/Direction.hpp" +#include "../../helpers/memory/Memory.hpp" + +#include "../LayoutManager.hpp" + +#include +#include + +namespace Layout { + class ITarget; + class IFloatingAlgorithm; + class ITiledAlgorithm; + class CSpace; + + class CAlgorithm { + public: + static SP create(UP&& tiled, UP&& floating, SP space); + ~CAlgorithm() = default; + + void addTarget(SP target); + void moveTarget(SP target, std::optional focalPoint = std::nullopt, bool reposition = false); + void removeTarget(SP target); + + void swapTargets(SP a, SP b); + void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + SP getNextCandidate(SP old); + + void setFloating(SP target, bool floating, bool reposition = false); + + std::expected layoutMsg(const std::string_view& sv); + std::optional predictSizeForNewTiledTarget(); + + void recalculate(); + void recenter(SP t); + + void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + void moveTarget(const Vector2D& Δ, SP target); + + void setTargetGeom(const CBox& box, SP target); // only for float + + void updateFloatingAlgo(UP&& algo); + void updateTiledAlgo(UP&& algo); + + const UP& tiledAlgo() const; + const UP& floatingAlgo() const; + + SP space() const; + + size_t tiledTargets() const; + size_t floatingTargets() const; + + private: + CAlgorithm(UP&& tiled, UP&& floating, SP space); + + UP m_tiled; + UP m_floating; + WP m_space; + WP m_self; + + std::vector> m_tiledTargets, m_floatingTargets; + }; +} \ No newline at end of file diff --git a/src/layout/algorithm/FloatingAlgorithm.cpp b/src/layout/algorithm/FloatingAlgorithm.cpp new file mode 100644 index 00000000..058887bf --- /dev/null +++ b/src/layout/algorithm/FloatingAlgorithm.cpp @@ -0,0 +1,18 @@ +#include "FloatingAlgorithm.hpp" +#include "Algorithm.hpp" +#include "../space/Space.hpp" + +using namespace Layout; + +void IFloatingAlgorithm::recalculate() { + ; +} + +void IFloatingAlgorithm::recenter(SP t) { + const auto LAST = t->lastFloatingSize(); + + if (LAST.x <= 5 || LAST.y <= 5) + return; + + t->setPositionGlobal({m_parent->space()->workArea().middle() - LAST / 2.F, LAST}); +} diff --git a/src/layout/algorithm/FloatingAlgorithm.hpp b/src/layout/algorithm/FloatingAlgorithm.hpp new file mode 100644 index 00000000..40e53034 --- /dev/null +++ b/src/layout/algorithm/FloatingAlgorithm.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "../../helpers/math/Math.hpp" +#include "../../helpers/memory/Memory.hpp" + +#include "ModeAlgorithm.hpp" + +namespace Layout { + + class ITarget; + class CAlgorithm; + + class IFloatingAlgorithm : public IModeAlgorithm { + public: + virtual ~IFloatingAlgorithm() = default; + + // a target is being moved by a delta + virtual void moveTarget(const Vector2D& Δ, SP target) = 0; + + // a target is moved to a pos x size + virtual void setTargetGeom(const CBox& geom, SP target) = 0; + + virtual void recenter(SP t); + + virtual void recalculate(); + + protected: + IFloatingAlgorithm() = default; + + WP m_parent; + + friend class Layout::CAlgorithm; + }; +} \ No newline at end of file diff --git a/src/layout/algorithm/ModeAlgorithm.cpp b/src/layout/algorithm/ModeAlgorithm.cpp new file mode 100644 index 00000000..dea5bb17 --- /dev/null +++ b/src/layout/algorithm/ModeAlgorithm.cpp @@ -0,0 +1,33 @@ +#include "ModeAlgorithm.hpp" + +#include "../space/Space.hpp" +#include "Algorithm.hpp" +#include "../../helpers/Monitor.hpp" +#include "../../desktop/view/Window.hpp" + +using namespace Layout; + +std::expected IModeAlgorithm::layoutMsg(const std::string_view& sv) { + return {}; +} + +std::optional IModeAlgorithm::predictSizeForNewTarget() { + return std::nullopt; +} + +std::optional IModeAlgorithm::focalPointForDir(SP t, Math::eDirection dir) { + Vector2D focalPoint; + + const auto WINDOWIDEALBB = + t->fullscreenMode() != FSMODE_NONE ? m_parent->space()->workspace()->m_monitor->logicalBox() : t->window()->getWindowIdealBoundingBoxIgnoreReserved(); + + switch (dir) { + case Math::DIRECTION_UP: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x / 2.0, -1.0}; break; + case Math::DIRECTION_DOWN: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x / 2.0, WINDOWIDEALBB.size().y + 1.0}; break; + case Math::DIRECTION_LEFT: focalPoint = WINDOWIDEALBB.pos() + Vector2D{-1.0, WINDOWIDEALBB.size().y / 2.0}; break; + case Math::DIRECTION_RIGHT: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x + 1.0, WINDOWIDEALBB.size().y / 2.0}; break; + default: return std::nullopt; + } + + return focalPoint; +} diff --git a/src/layout/algorithm/ModeAlgorithm.hpp b/src/layout/algorithm/ModeAlgorithm.hpp new file mode 100644 index 00000000..0fedc3da --- /dev/null +++ b/src/layout/algorithm/ModeAlgorithm.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include "../../helpers/math/Math.hpp" +#include "../../helpers/math/Direction.hpp" +#include "../../helpers/memory/Memory.hpp" + +#include "../LayoutManager.hpp" + +#include + +namespace Layout { + + class ITarget; + class CAlgorithm; + + class IModeAlgorithm { + public: + virtual ~IModeAlgorithm() = default; + + // a completely new target + virtual void newTarget(SP target) = 0; + + // a target moved into the algorithm (from another) + virtual void movedTarget(SP target, std::optional focalPoint = std::nullopt) = 0; + + // a target removed + virtual void removeTarget(SP target) = 0; + + // a target is being resized by a delta. Corner none likely means not interactive + virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE) = 0; + + // recalculate layout + virtual void recalculate() = 0; + + // swap targets + virtual void swapTargets(SP a, SP b) = 0; + + // move a target in a given direction + virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent) = 0; + + // optional: handle layout messages + virtual std::expected layoutMsg(const std::string_view& sv); + + // optional: predict new window's size + virtual std::optional predictSizeForNewTarget(); + + // Impl'd here: focal point for dir + virtual std::optional focalPointForDir(SP t, Math::eDirection dir); + + protected: + IModeAlgorithm() = default; + + WP m_parent; + + friend class Layout::CAlgorithm; + }; +} \ No newline at end of file diff --git a/src/layout/algorithm/TiledAlgorithm.hpp b/src/layout/algorithm/TiledAlgorithm.hpp new file mode 100644 index 00000000..99d1bd99 --- /dev/null +++ b/src/layout/algorithm/TiledAlgorithm.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "../../helpers/math/Math.hpp" +#include "../../helpers/memory/Memory.hpp" + +#include "ModeAlgorithm.hpp" + +namespace Layout { + + class ITarget; + class CAlgorithm; + + class ITiledAlgorithm : public IModeAlgorithm { + public: + virtual ~ITiledAlgorithm() = default; + + virtual SP getNextCandidate(SP old) = 0; + + protected: + ITiledAlgorithm() = default; + + WP m_parent; + + friend class Layout::CAlgorithm; + }; +} \ No newline at end of file diff --git a/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp b/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp new file mode 100644 index 00000000..0d069e4f --- /dev/null +++ b/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp @@ -0,0 +1,252 @@ +#include "DefaultFloatingAlgorithm.hpp" + +#include "../../Algorithm.hpp" + +#include "../../../target/WindowTarget.hpp" +#include "../../../space/Space.hpp" + +#include "../../../../Compositor.hpp" +#include "../../../../helpers/Monitor.hpp" + +using namespace Layout; +using namespace Layout::Floating; + +constexpr const Vector2D DEFAULT_SIZE = {640, 400}; + +// +void CDefaultFloatingAlgorithm::newTarget(SP target) { + const auto WORK_AREA = m_parent->space()->workArea(true); + const auto DESIRED_GEOM = target->desiredGeometry(); + const auto MONITOR_POS = m_parent->space()->workspace()->m_monitor->logicalBox().pos(); + + CBox windowGeometry; + + if (!DESIRED_GEOM) { + switch (DESIRED_GEOM.error()) { + case GEOMETRY_INVALID_DESIRED: { + // if the desired is invalid, we hide the window. + if (target->type() == TARGET_TYPE_WINDOW) + dynamicPointerCast(target)->window()->setHidden(true); + return; + } + case GEOMETRY_NO_DESIRED: { + // add a default geom + windowGeometry = CBox{WORK_AREA.middle() - DEFAULT_SIZE / 2.F, DEFAULT_SIZE}; + break; + } + } + } else { + if (DESIRED_GEOM->pos) + windowGeometry = CBox{DESIRED_GEOM->pos.value(), DESIRED_GEOM->size}; + else + windowGeometry = CBox{WORK_AREA.middle() - DESIRED_GEOM->size / 2.F, DESIRED_GEOM->size}; + } + + bool posOverridden = false; + + if (target->window() && target->window()->m_firstMap) { + const auto WINDOW = target->window(); + + // set this here so that expressions can use it. This could be wrong of course. + WINDOW->m_realSize->setValueAndWarp(DESIRED_GEOM ? DESIRED_GEOM->size : DEFAULT_SIZE); + + if (!WINDOW->m_ruleApplicator->static_.size.empty()) { + const auto COMPUTED = WINDOW->calculateExpression(WINDOW->m_ruleApplicator->static_.size); + if (!COMPUTED) + Log::logger->log(Log::ERR, "failed to parse {} as an expression", WINDOW->m_ruleApplicator->static_.size); + else { + windowGeometry.w = COMPUTED->x; + windowGeometry.h = COMPUTED->y; + + // update for pos to work with size. + WINDOW->m_realPosition->setValueAndWarp(*COMPUTED); + } + } + + if (!WINDOW->m_ruleApplicator->static_.position.empty()) { + const auto COMPUTED = WINDOW->calculateExpression(WINDOW->m_ruleApplicator->static_.position); + if (!COMPUTED) + Log::logger->log(Log::ERR, "failed to parse {} as an expression", WINDOW->m_ruleApplicator->static_.position); + else { + windowGeometry.x = COMPUTED->x + MONITOR_POS.x; + windowGeometry.y = COMPUTED->y + MONITOR_POS.y; + posOverridden = true; + } + } + + if (WINDOW->m_ruleApplicator->static_.center.value_or(false)) { + const auto POS = WORK_AREA.middle() - windowGeometry.size() / 2.f; + windowGeometry.x = POS.x; + windowGeometry.y = POS.y; + posOverridden = true; + } + } else if (target->lastFloatingSize().x > 5 && target->lastFloatingSize().y > 5) { + windowGeometry.w = target->lastFloatingSize().x; + windowGeometry.h = target->lastFloatingSize().y; + } + + if (!posOverridden && (!DESIRED_GEOM || !DESIRED_GEOM->pos)) + windowGeometry = CBox{WORK_AREA.middle() - windowGeometry.size() / 2.F, windowGeometry.size()}; + + if (posOverridden // pos is overridden by a rule + || (DESIRED_GEOM && DESIRED_GEOM->pos && target->window() && target->window()->m_isX11) // X11 window with a geom + || WORK_AREA.containsPoint(windowGeometry.middle())) // geometry within work area + target->setPositionGlobal(windowGeometry); + else { + const auto POS = WORK_AREA.middle() - windowGeometry.size() / 2.f; + windowGeometry.x = POS.x; + windowGeometry.y = POS.y; + + target->setPositionGlobal(windowGeometry); + } + + // TODO: not very OOP, is it? + if (const auto WTARGET = dynamicPointerCast(target); WTARGET) { + const auto PWINDOW = WTARGET->window(); + + if (PWINDOW->m_X11DoesntWantBorders || (PWINDOW->m_isX11 && PWINDOW->isX11OverrideRedirect())) { + PWINDOW->m_realPosition->warp(); + PWINDOW->m_realSize->warp(); + } + + if (!PWINDOW->isX11OverrideRedirect()) + g_pCompositor->changeWindowZOrder(PWINDOW, true); + else { + PWINDOW->m_pendingReportedSize = PWINDOW->m_realSize->goal(); + PWINDOW->m_reportedSize = PWINDOW->m_pendingReportedSize; + } + } + + updateTarget(target); +} + +void CDefaultFloatingAlgorithm::movedTarget(SP target, std::optional focalPoint) { + auto LAST_SIZE = target->lastFloatingSize(); + const auto CURRENT_SIZE = target->position().size(); + + // ignore positioning a dragged target + if (g_layoutManager->dragController()->target() == target) + return; + + if (LAST_SIZE.x < 5 || LAST_SIZE.y < 5) { + const auto DESIRED = target->desiredGeometry(); + LAST_SIZE = DESIRED ? DESIRED->size : DEFAULT_SIZE; + } + + if (target->wasTiling()) { + // Avoid floating toggles that don't change size, they aren't easily visible to the user + if (std::abs(LAST_SIZE.x - CURRENT_SIZE.x) < 5 && std::abs(LAST_SIZE.y - CURRENT_SIZE.y) < 5) + LAST_SIZE += Vector2D{10, 10}; + + // calculate new position + const auto OLD_CENTER = target->position().middle(); + + // put around the current center, fit in workArea + target->setPositionGlobal(fitBoxInWorkArea(CBox{OLD_CENTER - LAST_SIZE / 2.F, LAST_SIZE}, target)); + + } else { + // calculate new position + const auto THIS_MON_POS = m_parent->space()->workspace()->m_monitor->m_position; + const auto OLD_POS = target->position().pos(); + const auto MON_FROM_OLD = g_pCompositor->getMonitorFromVector(OLD_POS); + const auto NEW_POS = MON_FROM_OLD ? OLD_POS - MON_FROM_OLD->m_position + THIS_MON_POS : OLD_POS; + + // put around the current center, fit in workArea + target->setPositionGlobal(fitBoxInWorkArea(CBox{NEW_POS, LAST_SIZE}, target)); + } + + updateTarget(target); +} + +CBox CDefaultFloatingAlgorithm::fitBoxInWorkArea(const CBox& box, SP t) { + const auto WORK_AREA = m_parent->space()->workArea(true); + const auto EXTENTS = t->window() ? t->window()->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS) : SBoxExtents{}; + CBox targetBox = box.copy().addExtents(EXTENTS); + + targetBox.x = std::max(targetBox.x, WORK_AREA.x); + targetBox.y = std::max(targetBox.y, WORK_AREA.y); + + if (targetBox.x + targetBox.w > WORK_AREA.x + WORK_AREA.w) + targetBox.x = WORK_AREA.x + WORK_AREA.w - targetBox.w; + + if (targetBox.y + targetBox.h > WORK_AREA.y + WORK_AREA.h) + targetBox.y = WORK_AREA.y + WORK_AREA.h - targetBox.h; + + return targetBox.addExtents(SBoxExtents{.topLeft = -EXTENTS.topLeft, .bottomRight = -EXTENTS.bottomRight}); +} + +void CDefaultFloatingAlgorithm::removeTarget(SP target) { + target->rememberFloatingSize(target->position().size()); + m_datas.erase(target); +} + +void CDefaultFloatingAlgorithm::resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner) { + auto pos = target->position(); + pos.w += Δ.x; + pos.h += Δ.y; + pos.translate(-Δ / 2.F); + target->setPositionGlobal(pos); + + if (g_layoutManager->dragController()->target() == target) + target->warpPositionSize(); + + updateTarget(target); +} + +void CDefaultFloatingAlgorithm::moveTarget(const Vector2D& Δ, SP target) { + auto pos = target->position(); + pos.translate(Δ); + target->setPositionGlobal(pos); + + if (g_layoutManager->dragController()->target() == target) + target->warpPositionSize(); + + updateTarget(target); +} + +void CDefaultFloatingAlgorithm::swapTargets(SP a, SP b) { + auto posABackup = a->position(); + a->setPositionGlobal(b->position()); + b->setPositionGlobal(posABackup); + + updateTarget(a); + updateTarget(b); +} + +void CDefaultFloatingAlgorithm::moveTargetInDirection(SP t, Math::eDirection dir, bool silent) { + auto pos = t->position(); + auto work = m_parent->space()->workArea(true); + + const auto EXTENTS = t->window() ? t->window()->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS) : SBoxExtents{}; + + switch (dir) { + case Math::DIRECTION_LEFT: pos.x = work.x + EXTENTS.topLeft.x; break; + case Math::DIRECTION_RIGHT: pos.x = work.x + work.w - pos.w - EXTENTS.bottomRight.x; break; + case Math::DIRECTION_UP: pos.y = work.y + EXTENTS.topLeft.y; break; + case Math::DIRECTION_DOWN: pos.y = work.y + work.h - pos.h - EXTENTS.bottomRight.y; break; + default: Log::logger->log(Log::ERR, "Invalid direction in CDefaultFloatingAlgorithm::moveTargetInDirection"); break; + } + + t->setPositionGlobal(pos); + + updateTarget(t); +} + +void CDefaultFloatingAlgorithm::recenter(SP t) { + if (!m_datas.contains(t)) { + IFloatingAlgorithm::recenter(t); + return; + } + + t->setPositionGlobal(m_datas.at(t).lastBox); +} + +void CDefaultFloatingAlgorithm::setTargetGeom(const CBox& geom, SP target) { + target->setPositionGlobal(geom); + + updateTarget(target); +} + +void CDefaultFloatingAlgorithm::updateTarget(SP t) { + m_datas[t] = {.lastBox = t->position()}; +} diff --git a/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.hpp b/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.hpp new file mode 100644 index 00000000..1e87fac1 --- /dev/null +++ b/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.hpp @@ -0,0 +1,40 @@ +#include "../../FloatingAlgorithm.hpp" + +#include + +namespace Layout { + class CAlgorithm; +} + +namespace Layout::Floating { + class CDefaultFloatingAlgorithm : public IFloatingAlgorithm { + public: + CDefaultFloatingAlgorithm() = default; + virtual ~CDefaultFloatingAlgorithm() = default; + + virtual void newTarget(SP target); + virtual void movedTarget(SP target, std::optional focalPoint = std::nullopt); + virtual void removeTarget(SP target); + + virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + virtual void moveTarget(const Vector2D& Δ, SP target); + + virtual void setTargetGeom(const CBox& geom, SP target); + + virtual void swapTargets(SP a, SP b); + virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + virtual void recenter(SP t); + + private: + CBox fitBoxInWorkArea(const CBox& box, SP t); + + void updateTarget(SP); + + struct SWindowData { + CBox lastBox; + }; + + std::map, SWindowData> m_datas; + }; +}; \ No newline at end of file diff --git a/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp new file mode 100644 index 00000000..7ef36753 --- /dev/null +++ b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp @@ -0,0 +1,845 @@ +#include "DwindleAlgorithm.hpp" + +#include "../../Algorithm.hpp" +#include "../../../space/Space.hpp" +#include "../../../target/WindowTarget.hpp" +#include "../../../LayoutManager.hpp" + +#include "../../../../config/ConfigValue.hpp" +#include "../../../../desktop/state/FocusState.hpp" +#include "../../../../helpers/Monitor.hpp" +#include "../../../../Compositor.hpp" + +#include + +using namespace Layout; +using namespace Layout::Tiled; + +struct Layout::Tiled::SDwindleNodeData { + WP pParent; + bool isNode = false; + WP pTarget; + std::array, 2> children = {}; + WP self; + bool splitTop = false; // for preserve_split + CBox box = {0}; + float splitRatio = 1.f; + bool valid = true; + bool ignoreFullscreenChecks = false; + + // For list lookup + bool operator==(const SDwindleNodeData& rhs) const { + return pTarget.lock() == rhs.pTarget.lock() && box == rhs.box && pParent == rhs.pParent && children[0] == rhs.children[0] && children[1] == rhs.children[1]; + } + + void recalcSizePosRecursive(bool force = false, bool horizontalOverride = false, bool verticalOverride = false) { + if (children[0]) { + static auto PSMARTSPLIT = CConfigValue("dwindle:smart_split"); + static auto PPRESERVESPLIT = CConfigValue("dwindle:preserve_split"); + static auto PFLMULT = CConfigValue("dwindle:split_width_multiplier"); + + if (*PPRESERVESPLIT == 0 && *PSMARTSPLIT == 0) + splitTop = box.h * *PFLMULT > box.w; + + if (verticalOverride) + splitTop = true; + else if (horizontalOverride) + splitTop = false; + + const auto SPLITSIDE = !splitTop; + + if (SPLITSIDE) { + // split left/right + const float FIRSTSIZE = box.w / 2.0 * splitRatio; + children[0]->box = CBox{box.x, box.y, FIRSTSIZE, box.h}.noNegativeSize(); + children[1]->box = CBox{box.x + FIRSTSIZE, box.y, box.w - FIRSTSIZE, box.h}.noNegativeSize(); + } else { + // split top/bottom + const float FIRSTSIZE = box.h / 2.0 * splitRatio; + children[0]->box = CBox{box.x, box.y, box.w, FIRSTSIZE}.noNegativeSize(); + children[1]->box = CBox{box.x, box.y + FIRSTSIZE, box.w, box.h - FIRSTSIZE}.noNegativeSize(); + } + + children[0]->recalcSizePosRecursive(force); + children[1]->recalcSizePosRecursive(force); + } else + pTarget->setPositionGlobal(box); + } +}; + +void CDwindleAlgorithm::newTarget(SP target) { + addTarget(target); +} + +void CDwindleAlgorithm::addTarget(SP target, bool newTarget) { + const auto WORK_AREA = m_parent->space()->workArea(); + + const auto PNODE = m_dwindleNodesData.emplace_back(makeShared()); + PNODE->self = PNODE; + + const auto PMONITOR = m_parent->space()->workspace()->m_monitor; + const auto PWORKSPACE = m_parent->space()->workspace(); + + static auto PUSEACTIVE = CConfigValue("dwindle:use_active_for_splits"); + static auto PDEFAULTSPLIT = CConfigValue("dwindle:default_split_ratio"); + + // Populate the node with our window's data + PNODE->pTarget = target; + PNODE->isNode = false; + + SP OPENINGON; + + const auto MOUSECOORDS = m_overrideFocalPoint.value_or(g_pInputManager->getMouseCoordsInternal()); + const auto ACTIVE_MON = Desktop::focusState()->monitor(); + + if ((PWORKSPACE == ACTIVE_MON->m_activeWorkspace || (PWORKSPACE->m_isSpecialWorkspace && PMONITOR->m_activeSpecialWorkspace)) && !*PUSEACTIVE) { + OPENINGON = getNodeFromWindow( + g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::SKIP_FULLSCREEN_PRIORITY)); + + if (!OPENINGON && g_pCompositor->isPointOnReservedArea(MOUSECOORDS, ACTIVE_MON)) + OPENINGON = getClosestNode(MOUSECOORDS); + + } else if (*PUSEACTIVE || m_overrideFocalPoint) { + const auto ACTIVE_WINDOW = Desktop::focusState()->window(); + + if (m_overrideFocalPoint) + OPENINGON = getClosestNode(*m_overrideFocalPoint); + else if (!m_overrideFocalPoint && ACTIVE_WINDOW && !ACTIVE_WINDOW->m_isFloating && ACTIVE_WINDOW != target->window() && ACTIVE_WINDOW->m_workspace == PWORKSPACE && + ACTIVE_WINDOW->m_isMapped) + OPENINGON = getNodeFromWindow(ACTIVE_WINDOW); + + if (!OPENINGON) + OPENINGON = getClosestNode(MOUSECOORDS, target); + } else + OPENINGON = getFirstNode(); + + // first, check if OPENINGON isn't too big. + const auto PREDSIZEMAX = OPENINGON ? Vector2D(OPENINGON->box.w, OPENINGON->box.h) : PMONITOR->m_size; + if (const auto MAXSIZE = target->maxSize().value_or(Math::VECTOR2D_MAX); MAXSIZE.x < PREDSIZEMAX.x || MAXSIZE.y < PREDSIZEMAX.y) { + // we can't continue. make it floating. + std::erase(m_dwindleNodesData, PNODE); + m_parent->setFloating(target, true, true); + return; + } + + // last fail-safe to avoid duplicate fullscreens + if ((!OPENINGON || OPENINGON->pTarget.lock() == target) && getNodes() > 1) { + for (auto& node : m_dwindleNodesData) { + if (node->pTarget.lock() && node->pTarget.lock() != target) { + OPENINGON = node; + break; + } + } + } + + // if it's the first, it's easy. Make it fullscreen. + if (!OPENINGON || OPENINGON->pTarget.lock() == target) { + PNODE->box = WORK_AREA; + PNODE->pTarget->setPositionGlobal(PNODE->box); + return; + } + + // get the node under our cursor + + const auto NEWPARENT = m_dwindleNodesData.emplace_back(makeShared()); + + // make the parent have the OPENINGON's stats + NEWPARENT->box = OPENINGON->box; + NEWPARENT->pParent = OPENINGON->pParent; + NEWPARENT->isNode = true; // it is a node + NEWPARENT->splitRatio = std::clamp(*PDEFAULTSPLIT, 0.1F, 1.9F); + + static auto PWIDTHMULTIPLIER = CConfigValue("dwindle:split_width_multiplier"); + + // if cursor over first child, make it first, etc + const auto SIDEBYSIDE = NEWPARENT->box.w > NEWPARENT->box.h * *PWIDTHMULTIPLIER; + NEWPARENT->splitTop = !SIDEBYSIDE; + + static auto PFORCESPLIT = CConfigValue("dwindle:force_split"); + static auto PERMANENTDIRECTIONOVERRIDE = CConfigValue("dwindle:permanent_direction_override"); + static auto PSMARTSPLIT = CConfigValue("dwindle:smart_split"); + static auto PSPLITBIAS = CConfigValue("dwindle:split_bias"); + + bool horizontalOverride = false; + bool verticalOverride = false; + + // let user select position -> top, right, bottom, left + if (m_overrideDirection != Math::DIRECTION_DEFAULT) { + + // this is horizontal + if (m_overrideDirection % 2 == 0) + verticalOverride = true; + else + horizontalOverride = true; + + // 0 -> top and left | 1,2 -> right and bottom + if (m_overrideDirection % 3 == 0) { + NEWPARENT->children[1] = OPENINGON; + NEWPARENT->children[0] = PNODE; + } else { + NEWPARENT->children[0] = OPENINGON; + NEWPARENT->children[1] = PNODE; + } + + // whether or not the override persists after opening one window + if (*PERMANENTDIRECTIONOVERRIDE == 0) + m_overrideDirection = Math::DIRECTION_DEFAULT; + } else if (*PSMARTSPLIT == 1 || m_overrideFocalPoint) { + const auto PARENT_CENTER = NEWPARENT->box.pos() + NEWPARENT->box.size() / 2; + const auto PARENT_PROPORTIONS = NEWPARENT->box.h / NEWPARENT->box.w; + const auto DELTA = MOUSECOORDS - PARENT_CENTER; + const auto DELTA_SLOPE = DELTA.y / DELTA.x; + + if (abs(DELTA_SLOPE) < PARENT_PROPORTIONS) { + if (DELTA.x > 0) { + // right + NEWPARENT->splitTop = false; + NEWPARENT->children[0] = OPENINGON; + NEWPARENT->children[1] = PNODE; + } else { + // left + NEWPARENT->splitTop = false; + NEWPARENT->children[0] = PNODE; + NEWPARENT->children[1] = OPENINGON; + } + } else { + if (DELTA.y > 0) { + // bottom + NEWPARENT->splitTop = true; + NEWPARENT->children[0] = OPENINGON; + NEWPARENT->children[1] = PNODE; + } else { + // top + NEWPARENT->splitTop = true; + NEWPARENT->children[0] = PNODE; + NEWPARENT->children[1] = OPENINGON; + } + } + } else if (*PFORCESPLIT == 0 || !newTarget) { + if ((SIDEBYSIDE && MOUSECOORDS.x < NEWPARENT->box.x + (NEWPARENT->box.w / 2.F)) || (!SIDEBYSIDE && MOUSECOORDS.y < NEWPARENT->box.y + (NEWPARENT->box.h / 2.F))) { + // we are hovering over the first node, make PNODE first. + NEWPARENT->children[1] = OPENINGON; + NEWPARENT->children[0] = PNODE; + } else { + // we are hovering over the second node, make PNODE second. + NEWPARENT->children[0] = OPENINGON; + NEWPARENT->children[1] = PNODE; + } + } else { + if (*PFORCESPLIT == 1) { + NEWPARENT->children[1] = OPENINGON; + NEWPARENT->children[0] = PNODE; + } else { + NEWPARENT->children[0] = OPENINGON; + NEWPARENT->children[1] = PNODE; + } + } + + // split in favor of a specific window + if (*PSPLITBIAS && NEWPARENT->children[0] == PNODE) + NEWPARENT->splitRatio = 2.f - NEWPARENT->splitRatio; + + // and update the previous parent if it exists + if (OPENINGON->pParent) { + if (OPENINGON->pParent->children[0] == OPENINGON) + OPENINGON->pParent->children[0] = NEWPARENT; + else + OPENINGON->pParent->children[1] = NEWPARENT; + } + + // Update the children + if (!verticalOverride && (NEWPARENT->box.w * *PWIDTHMULTIPLIER > NEWPARENT->box.h || horizontalOverride)) { + // split left/right -> forced + OPENINGON->box = {NEWPARENT->box.pos(), Vector2D(NEWPARENT->box.w / 2.f, NEWPARENT->box.h)}; + PNODE->box = {Vector2D(NEWPARENT->box.x + NEWPARENT->box.w / 2.f, NEWPARENT->box.y), Vector2D(NEWPARENT->box.w / 2.f, NEWPARENT->box.h)}; + } else { + // split top/bottom + OPENINGON->box = {NEWPARENT->box.pos(), Vector2D(NEWPARENT->box.w, NEWPARENT->box.h / 2.f)}; + PNODE->box = {Vector2D(NEWPARENT->box.x, NEWPARENT->box.y + NEWPARENT->box.h / 2.f), Vector2D(NEWPARENT->box.w, NEWPARENT->box.h / 2.f)}; + } + + OPENINGON->pParent = NEWPARENT; + PNODE->pParent = NEWPARENT; + + NEWPARENT->recalcSizePosRecursive(false, horizontalOverride, verticalOverride); + + calculateWorkspace(); +} + +void CDwindleAlgorithm::movedTarget(SP target, std::optional focalPoint) { + m_overrideFocalPoint = focalPoint; + addTarget(target, false); + m_overrideFocalPoint.reset(); +} + +void CDwindleAlgorithm::removeTarget(SP target) { + const auto PNODE = getNodeFromTarget(target); + + if (!PNODE) { + Log::logger->log(Log::ERR, "onWindowRemovedTiling node null?"); + return; + } + + if (target->fullscreenMode() != FSMODE_NONE) + g_pCompositor->setWindowFullscreenInternal(target->window(), FSMODE_NONE); + + const auto PPARENT = PNODE->pParent; + + if (!PPARENT) { + Log::logger->log(Log::DEBUG, "Removing last node (dwindle)"); + std::erase(m_dwindleNodesData, PNODE); + return; + } + + const auto PSIBLING = PPARENT->children[0] == PNODE ? PPARENT->children[1] : PPARENT->children[0]; + + PSIBLING->pParent = PPARENT->pParent; + + if (PPARENT->pParent != nullptr) { + if (PPARENT->pParent->children[0] == PPARENT) + PPARENT->pParent->children[0] = PSIBLING; + else + PPARENT->pParent->children[1] = PSIBLING; + } + + PPARENT->valid = false; + PNODE->valid = false; + + std::erase(m_dwindleNodesData, PPARENT); + std::erase(m_dwindleNodesData, PNODE); + + recalculate(); +} + +void CDwindleAlgorithm::resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner) { + if (!validMapped(target->window())) + return; + + const auto PNODE = getNodeFromTarget(target); + + if (!PNODE) + return; + + static auto PANIMATE = CConfigValue("misc:animate_manual_resizes"); + static auto PSMARTRESIZING = CConfigValue("dwindle:smart_resizing"); + + // get some data about our window + const auto PMONITOR = m_parent->space()->workspace()->m_monitor; + const auto MONITOR_WORKAREA = PMONITOR->logicalBoxMinusReserved(); + const auto BOX = target->position(); + const bool DISPLAYLEFT = STICKS(BOX.x, MONITOR_WORKAREA.x); + const bool DISPLAYRIGHT = STICKS(BOX.x + BOX.w, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w); + const bool DISPLAYTOP = STICKS(BOX.y, MONITOR_WORKAREA.y); + const bool DISPLAYBOTTOM = STICKS(BOX.y + BOX.h, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h); + + // construct allowed movement + Vector2D allowedMovement = Δ; + if (DISPLAYLEFT && DISPLAYRIGHT) + allowedMovement.x = 0; + + if (DISPLAYBOTTOM && DISPLAYTOP) + allowedMovement.y = 0; + + if (*PSMARTRESIZING == 1) { + // Identify inner and outer nodes for both directions + SP PVOUTER = nullptr; + SP PVINNER = nullptr; + SP PHOUTER = nullptr; + SP PHINNER = nullptr; + + const auto LEFT = corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT || DISPLAYRIGHT; + const auto TOP = corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT || DISPLAYBOTTOM; + const auto RIGHT = corner == CORNER_TOPRIGHT || corner == CORNER_BOTTOMRIGHT || DISPLAYLEFT; + const auto BOTTOM = corner == CORNER_BOTTOMLEFT || corner == CORNER_BOTTOMRIGHT || DISPLAYTOP; + const auto NONE = corner == CORNER_NONE; + + for (auto PCURRENT = PNODE; PCURRENT && PCURRENT->pParent; PCURRENT = PCURRENT->pParent.lock()) { + const auto PPARENT = PCURRENT->pParent; + + if (!PVOUTER && PPARENT->splitTop && (NONE || (TOP && PPARENT->children[1] == PCURRENT) || (BOTTOM && PPARENT->children[0] == PCURRENT))) + PVOUTER = PCURRENT; + else if (!PVOUTER && !PVINNER && PPARENT->splitTop) + PVINNER = PCURRENT; + else if (!PHOUTER && !PPARENT->splitTop && (NONE || (LEFT && PPARENT->children[1] == PCURRENT) || (RIGHT && PPARENT->children[0] == PCURRENT))) + PHOUTER = PCURRENT; + else if (!PHOUTER && !PHINNER && !PPARENT->splitTop) + PHINNER = PCURRENT; + + if (PVOUTER && PHOUTER) + break; + } + + if (PHOUTER) { + PHOUTER->pParent->splitRatio = std::clamp(PHOUTER->pParent->splitRatio + allowedMovement.x * 2.f / PHOUTER->pParent->box.w, 0.1, 1.9); + + if (PHINNER) { + const auto ORIGINAL = PHINNER->box.w; + PHOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); + if (PHINNER->pParent->children[0] == PHINNER) + PHINNER->pParent->splitRatio = std::clamp((ORIGINAL - allowedMovement.x) / PHINNER->pParent->box.w * 2.f, 0.1, 1.9); + else + PHINNER->pParent->splitRatio = std::clamp(2 - (ORIGINAL + allowedMovement.x) / PHINNER->pParent->box.w * 2.f, 0.1, 1.9); + PHINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0); + } else + PHOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); + } + + if (PVOUTER) { + PVOUTER->pParent->splitRatio = std::clamp(PVOUTER->pParent->splitRatio + allowedMovement.y * 2.f / PVOUTER->pParent->box.h, 0.1, 1.9); + + if (PVINNER) { + const auto ORIGINAL = PVINNER->box.h; + PVOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); + if (PVINNER->pParent->children[0] == PVINNER) + PVINNER->pParent->splitRatio = std::clamp((ORIGINAL - allowedMovement.y) / PVINNER->pParent->box.h * 2.f, 0.1, 1.9); + else + PVINNER->pParent->splitRatio = std::clamp(2 - (ORIGINAL + allowedMovement.y) / PVINNER->pParent->box.h * 2.f, 0.1, 1.9); + PVINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0); + } else + PVOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); + } + } else { + // get the correct containers to apply splitratio to + const auto PPARENT = PNODE->pParent; + + if (!PPARENT) + return; // the only window on a workspace, ignore + + const bool PARENTSIDEBYSIDE = !PPARENT->splitTop; + + // Get the parent's parent + auto PPARENT2 = PPARENT->pParent; + + Hyprutils::Utils::CScopeGuard x([target, this] { + // snap all windows, don't animate resizes if they are manual + if (target == g_layoutManager->dragController()->target()) { + for (const auto& w : m_dwindleNodesData) { + if (w->isNode) + continue; + + w->pTarget->warpPositionSize(); + } + } + }); + + // No parent means we have only 2 windows, and thus one axis of freedom + if (!PPARENT2) { + if (PARENTSIDEBYSIDE) { + allowedMovement.x *= 2.f / PPARENT->box.w; + PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.x, 0.1, 1.9); + PPARENT->recalcSizePosRecursive(*PANIMATE == 0); + } else { + allowedMovement.y *= 2.f / PPARENT->box.h; + PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.y, 0.1, 1.9); + PPARENT->recalcSizePosRecursive(*PANIMATE == 0); + } + + return; + } + + // Get first parent with other split + while (PPARENT2 && PPARENT2->splitTop == !PARENTSIDEBYSIDE) + PPARENT2 = PPARENT2->pParent; + + // no parent, one axis of freedom + if (!PPARENT2) { + if (PARENTSIDEBYSIDE) { + allowedMovement.x *= 2.f / PPARENT->box.w; + PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.x, 0.1, 1.9); + PPARENT->recalcSizePosRecursive(*PANIMATE == 0); + } else { + allowedMovement.y *= 2.f / PPARENT->box.h; + PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.y, 0.1, 1.9); + PPARENT->recalcSizePosRecursive(*PANIMATE == 0); + } + + return; + } + + // 2 axes of freedom + const auto SIDECONTAINER = PARENTSIDEBYSIDE ? PPARENT : PPARENT2; + const auto TOPCONTAINER = PARENTSIDEBYSIDE ? PPARENT2 : PPARENT; + + allowedMovement.x *= 2.f / SIDECONTAINER->box.w; + allowedMovement.y *= 2.f / TOPCONTAINER->box.h; + + SIDECONTAINER->splitRatio = std::clamp(SIDECONTAINER->splitRatio + allowedMovement.x, 0.1, 1.9); + TOPCONTAINER->splitRatio = std::clamp(TOPCONTAINER->splitRatio + allowedMovement.y, 0.1, 1.9); + SIDECONTAINER->recalcSizePosRecursive(*PANIMATE == 0); + TOPCONTAINER->recalcSizePosRecursive(*PANIMATE == 0); + } + + // snap all windows, don't animate resizes if they are manual + if (target == g_layoutManager->dragController()->target()) { + for (const auto& w : m_dwindleNodesData) { + if (w->isNode) + continue; + + w->pTarget->warpPositionSize(); + } + } +} + +SP CDwindleAlgorithm::getNextCandidate(SP old) { + const auto MIDDLE = old->position().middle(); + + if (const auto NODE = getClosestNode(MIDDLE); NODE) + return NODE->pTarget.lock(); + + if (const auto NODE = getFirstNode(); NODE) + return NODE->pTarget.lock(); + + return nullptr; +} + +void CDwindleAlgorithm::swapTargets(SP a, SP b) { + auto nodeA = getNodeFromTarget(a); + auto nodeB = getNodeFromTarget(b); + + if (nodeA) + nodeA->pTarget = b; + if (nodeB) + nodeB->pTarget = a; +} + +void CDwindleAlgorithm::recalculate() { + calculateWorkspace(); +} + +std::optional CDwindleAlgorithm::predictSizeForNewTarget() { + // get window candidate + PHLWINDOW candidate = Desktop::focusState()->window(); + + if (!candidate || candidate->m_workspace != m_parent->space()->workspace()) + candidate = m_parent->space()->workspace()->getFirstWindow(); + + // create a fake node + SDwindleNodeData node; + + if (!candidate) + return Desktop::focusState()->monitor()->m_size; + else { + const auto PNODE = getNodeFromWindow(candidate); + + if (!PNODE) + return {}; + + node = *PNODE; + node.pTarget.reset(); + + CBox box = PNODE->box; + + static auto PFLMULT = CConfigValue("dwindle:split_width_multiplier"); + + bool splitTop = box.h * *PFLMULT > box.w; + + const auto SPLITSIDE = !splitTop; + + if (SPLITSIDE) + node.box = {{}, {box.w / 2.0, box.h}}; + else + node.box = {{}, {box.w, box.h / 2.0}}; + + // TODO: make this better and more accurate + + return node.box.size(); + } + + return {}; +} + +void CDwindleAlgorithm::moveTargetInDirection(SP t, Math::eDirection dir, bool silent) { + static auto PMONITORFALLBACK = CConfigValue("binds:window_direction_monitor_fallback"); + + const auto PNODE = getNodeFromTarget(t); + const Vector2D originalPos = t->position().middle(); + + if (!PNODE || !t->window()) + return; + + const auto FOCAL_POINT = focalPointForDir(t, dir); + + const auto PMONITORFOCAL = g_pCompositor->getMonitorFromVector(FOCAL_POINT.value_or(t->position().middle())); + + if (PMONITORFOCAL != m_parent->space()->workspace()->m_monitor && !*PMONITORFALLBACK) + return; // noop + + t->window()->setAnimationsToMove(); + + removeTarget(t); + + if (PMONITORFOCAL != m_parent->space()->workspace()->m_monitor) { + // move with a focal point + + if (PMONITORFOCAL->m_activeWorkspace) + t->assignToSpace(PMONITORFOCAL->m_activeWorkspace->m_space, FOCAL_POINT); + + return; + } + + movedTarget(t, FOCAL_POINT); + + // restore focus to the previous position + if (silent) { + const auto PNODETOFOCUS = getClosestNode(originalPos); + if (PNODETOFOCUS && PNODETOFOCUS->pTarget) + Desktop::focusState()->fullWindowFocus(PNODETOFOCUS->pTarget->window(), Desktop::FOCUS_REASON_KEYBIND); + } +} + +// --------- internal --------- // + +void CDwindleAlgorithm::calculateWorkspace() { + const auto PWORKSPACE = m_parent->space()->workspace(); + const auto PMONITOR = PWORKSPACE->m_monitor; + + if (!PMONITOR || PWORKSPACE->m_hasFullscreenWindow) + return; + + const auto TOPNODE = getMasterNode(); + + if (TOPNODE) { + TOPNODE->box = m_parent->space()->workArea(); + TOPNODE->recalcSizePosRecursive(); + } +} + +SP CDwindleAlgorithm::getNodeFromTarget(SP t) { + for (const auto& n : m_dwindleNodesData) { + if (n->pTarget == t) + return n; + } + + return nullptr; +} + +SP CDwindleAlgorithm::getNodeFromWindow(PHLWINDOW w) { + return w ? getNodeFromTarget(w->layoutTarget()) : nullptr; +} + +int CDwindleAlgorithm::getNodes() { + return m_dwindleNodesData.size(); +} + +SP CDwindleAlgorithm::getFirstNode() { + return m_dwindleNodesData.empty() ? nullptr : m_dwindleNodesData.at(0); +} + +SP CDwindleAlgorithm::getClosestNode(const Vector2D& point, SP skip) { + SP res = nullptr; + double distClosest = -1; + for (auto& n : m_dwindleNodesData) { + if (skip && n->pTarget == skip) + continue; + + if (n->pTarget && Desktop::View::validMapped(n->pTarget->window())) { + auto distAnother = vecToRectDistanceSquared(point, n->box.pos(), n->box.pos() + n->box.size()); + if (!res || distAnother < distClosest) { + res = n; + distClosest = distAnother; + } + } + } + return res; +} + +SP CDwindleAlgorithm::getMasterNode() { + for (auto& n : m_dwindleNodesData) { + if (!n->pParent) + return n; + } + return nullptr; +} + +std::expected CDwindleAlgorithm::layoutMsg(const std::string_view& sv) { + const auto ARGS = CVarList2(std::string{sv}, 0, ' '); + + const auto CURRENT_NODE = getNodeFromWindow(Desktop::focusState()->window()); + + if (ARGS[0] == "togglesplit") { + if (CURRENT_NODE) { + if (!toggleSplit(CURRENT_NODE)) + return std::unexpected("can't togglesplit in the current workspace"); + } + } else if (ARGS[0] == "swapsplit") { + if (CURRENT_NODE) { + if (!swapSplit(CURRENT_NODE)) + return std::unexpected("can't swapsplit in the current workspace"); + } + } else if (ARGS[0] == "rotatesplit") { + if (CURRENT_NODE) { + int angle = 90; + if (!ARGS[1].empty()) { + try { + angle = std::stoi(std::string{ARGS[1]}); + } catch (const std::exception& e) { + Log::logger->log(Log::WARN, "Invalid angle argument for rotatesplit: {}", ARGS[1]); + return std::unexpected("Invalid angle argument"); + } + } + rotateSplit(CURRENT_NODE, angle); + } + } else if (ARGS[0] == "movetoroot") { + auto node = CURRENT_NODE; + if (!ARGS[1].empty()) { + auto w = g_pCompositor->getWindowByRegex(std::string{ARGS[1]}); + if (w) + node = getNodeFromWindow(w); + } + + const auto STABLE = ARGS[2].empty() || ARGS[2] != "unstable"; + if (!moveToRoot(node, STABLE)) + return std::unexpected("can't movetoroot in the current workspace"); + } else if (ARGS[0] == "preselect") { + auto direction = ARGS[1]; + + if (direction.empty()) { + Log::logger->log(Log::ERR, "Expected direction for preselect"); + return std::unexpected("No direction for preselect"); + } + + switch (direction.front()) { + case 'u': + case 't': { + m_overrideDirection = Math::DIRECTION_UP; + break; + } + case 'd': + case 'b': { + m_overrideDirection = Math::DIRECTION_DOWN; + break; + } + case 'r': { + m_overrideDirection = Math::DIRECTION_RIGHT; + break; + } + case 'l': { + m_overrideDirection = Math::DIRECTION_LEFT; + break; + } + default: { + // any other character resets the focus direction + // needed for the persistent mode + m_overrideDirection = Math::DIRECTION_DEFAULT; + break; + } + } + } else if (ARGS[0] == "splitratio") { + auto ratio = ARGS[1]; + bool exact = ARGS[2].starts_with("exact"); + + if (ratio.empty()) + return std::unexpected("splitratio requires an arg"); + + auto delta = getPlusMinusKeywordResult(std::string{ratio}, 0.F); + + if (!CURRENT_NODE || !CURRENT_NODE->pParent) + return std::unexpected("cannot alter split ratio on no / single node"); + + if (!delta) + return std::unexpected(std::format("failed to parse \"{}\" as a delta", ratio)); + + const float newRatio = exact ? *delta : CURRENT_NODE->pParent->splitRatio + *delta; + CURRENT_NODE->pParent->splitRatio = std::clamp(newRatio, 0.1F, 1.9F); + + CURRENT_NODE->pParent->recalcSizePosRecursive(); + } + + return {}; +} + +bool CDwindleAlgorithm::toggleSplit(SP x) { + if (!x || !x->pParent) + return false; + + if (x->pTarget->fullscreenMode() != FSMODE_NONE) + return false; + + x->pParent->splitTop = !x->pParent->splitTop; + + x->pParent->recalcSizePosRecursive(); + + return true; +} + +bool CDwindleAlgorithm::swapSplit(SP x) { + if (x->pTarget->fullscreenMode() != FSMODE_NONE || !x->pParent) + return false; + + std::swap(x->pParent->children[0], x->pParent->children[1]); + + x->pParent->recalcSizePosRecursive(); + + return true; +} + +void CDwindleAlgorithm::rotateSplit(SP x, int angle) { + if (!x || !x->pParent) + return; + + if (x->pTarget->fullscreenMode() != FSMODE_NONE) + return; + + // normalize the angle to multiples of 90 degrees + int normalizedAngle = ((sc(angle / 90) % 4) + 4) % 4; // ensures positive modulo + + auto pParent = x->pParent; + + bool shouldSwap = false; + + switch (normalizedAngle) { + case 0: // 0 degrees - no change + break; + case 1: + if (pParent->splitTop) + shouldSwap = true; + pParent->splitTop = !pParent->splitTop; + break; + case 2: shouldSwap = true; break; + case 3: + if (!pParent->splitTop) + shouldSwap = true; + pParent->splitTop = !pParent->splitTop; + break; + default: break; // should never happen + } + + if (shouldSwap) + std::swap(pParent->children[0], pParent->children[1]); + + pParent->recalcSizePosRecursive(); +} + +bool CDwindleAlgorithm::moveToRoot(SP x, bool stable) { + if (!x || !x->pParent) + return false; + + if (x->pTarget->fullscreenMode() != FSMODE_NONE) + return false; + + // already at root + if (!x->pParent->pParent) + return false; + + auto& pNode = x->pParent->children[0] == x ? x->pParent->children[0] : x->pParent->children[1]; + + // instead of [getMasterNodeOnWorkspace], we walk back to root since we need + // to know which children of root is our ancestor + auto pAncestor = x, pRoot = x->pParent.lock(); + while (pRoot->pParent) { + pAncestor = pRoot; + pRoot = pRoot->pParent.lock(); + } + + auto& pSwap = pRoot->children[0] == pAncestor ? pRoot->children[1] : pRoot->children[0]; + std::swap(pNode, pSwap); + std::swap(pNode->pParent, pSwap->pParent); + + // [stable] in that the focused window occupies same side of screen + if (stable) + std::swap(pRoot->children[0], pRoot->children[1]); + + pRoot->recalcSizePosRecursive(); + + return true; +} diff --git a/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp new file mode 100644 index 00000000..41cbf8bb --- /dev/null +++ b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp @@ -0,0 +1,58 @@ +#include "../../TiledAlgorithm.hpp" + +namespace Layout { + class CAlgorithm; +} + +namespace Layout::Tiled { + struct SDwindleNodeData; + + class CDwindleAlgorithm : public ITiledAlgorithm { + public: + CDwindleAlgorithm() = default; + virtual ~CDwindleAlgorithm() = default; + + virtual void newTarget(SP target); + virtual void movedTarget(SP target, std::optional focalPoint = std::nullopt); + virtual void removeTarget(SP target); + + virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + virtual void recalculate(); + + virtual SP getNextCandidate(SP old); + + virtual std::expected layoutMsg(const std::string_view& sv); + virtual std::optional predictSizeForNewTarget(); + + virtual void swapTargets(SP a, SP b); + virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + private: + std::vector> m_dwindleNodesData; + + struct { + bool started = false; + bool pseudo = false; + bool xExtent = false; + bool yExtent = false; + } m_pseudoDragFlags; + + std::optional m_overrideFocalPoint; // for onWindowCreatedTiling. + + void addTarget(SP target, bool newTarget = true); + void calculateWorkspace(); + SP getNodeFromTarget(SP); + SP getNodeFromWindow(PHLWINDOW w); + int getNodes(); + SP getFirstNode(); + SP getClosestNode(const Vector2D&, SP skip = nullptr); + SP getMasterNode(); + + bool toggleSplit(SP); + bool swapSplit(SP); + void rotateSplit(SP, int angle = 90); + bool moveToRoot(SP, bool stable = true); + + Math::eDirection m_overrideDirection = Math::DIRECTION_DEFAULT; + }; +}; \ No newline at end of file diff --git a/src/layout/algorithm/tiled/master/MasterAlgorithm.cpp b/src/layout/algorithm/tiled/master/MasterAlgorithm.cpp new file mode 100644 index 00000000..7c436b31 --- /dev/null +++ b/src/layout/algorithm/tiled/master/MasterAlgorithm.cpp @@ -0,0 +1,1307 @@ +#include "MasterAlgorithm.hpp" + +#include "../../Algorithm.hpp" +#include "../../../space/Space.hpp" +#include "../../../target/WindowTarget.hpp" + +#include "../../../../config/ConfigValue.hpp" +#include "../../../../config/ConfigManager.hpp" +#include "../../../../desktop/state/FocusState.hpp" +#include "../../../../helpers/Monitor.hpp" +#include "../../../../Compositor.hpp" +#include "../../../../render/Renderer.hpp" + +#include + +using namespace Layout; +using namespace Layout::Tiled; + +struct Layout::Tiled::SMasterNodeData { + bool isMaster = false; + float percMaster = 0.5f; + + WP pTarget; + + Vector2D position; + Vector2D size; + + float percSize = 1.f; // size multiplier for resizing children + + bool ignoreFullscreenChecks = false; + + // + bool operator==(const SMasterNodeData& rhs) const { + return pTarget.lock() == rhs.pTarget.lock(); + } +}; + +void CMasterAlgorithm::newTarget(SP target) { + addTarget(target, true); +} + +void CMasterAlgorithm::movedTarget(SP target, std::optional focalPoint) { + addTarget(target, false); +} + +void CMasterAlgorithm::addTarget(SP target, bool firstMap) { + static auto PNEWONACTIVE = CConfigValue("master:new_on_active"); + static auto PNEWONTOP = CConfigValue("master:new_on_top"); + static auto PNEWSTATUS = CConfigValue("master:new_status"); + + const auto PWORKSPACE = m_parent->space()->workspace(); + const auto PMONITOR = PWORKSPACE->m_monitor; + + bool dragOntoMaster = false; + + if (g_layoutManager->dragController()->wasDraggingWindow()) { + if (const auto n = getClosestNode(g_pInputManager->getMouseCoordsInternal()); n && n->isMaster) + dragOntoMaster = true; + } + + const bool BNEWBEFOREACTIVE = *PNEWONACTIVE == "before"; + const bool BNEWISMASTER = dragOntoMaster || *PNEWSTATUS == "master"; + + const auto PNODE = [&]() -> SP { + if (*PNEWONACTIVE != "none" && !BNEWISMASTER) { + const auto pLastNode = getNodeFromWindow(Desktop::focusState()->window()); + if (pLastNode && !(pLastNode->isMaster && (getMastersNo() == 1 || *PNEWSTATUS == "slave"))) { + auto it = std::ranges::find(m_masterNodesData, pLastNode); + if (!BNEWBEFOREACTIVE) + ++it; + return *m_masterNodesData.emplace(it, makeShared()); + } + } + return *PNEWONTOP ? *m_masterNodesData.emplace(m_masterNodesData.begin(), makeShared()) : m_masterNodesData.emplace_back(makeShared()); + }(); + + PNODE->pTarget = target; + + const auto WINDOWSONWORKSPACE = getNodesNo(); + static auto PMFACT = CConfigValue("master:mfact"); + float lastSplitPercent = *PMFACT; + + auto OPENINGON = isWindowTiled(Desktop::focusState()->window()) && Desktop::focusState()->window()->m_workspace == PWORKSPACE ? + getNodeFromWindow(Desktop::focusState()->window()) : + getMasterNode(); + + const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); + static auto PDROPATCURSOR = CConfigValue("master:drop_at_cursor"); + eOrientation orientation = getDynamicOrientation(); + const auto NODEIT = std::ranges::find(m_masterNodesData, PNODE); + + bool forceDropAsMaster = false; + // if dragging window to move, drop it at the cursor position instead of bottom/top of stack + if (*PDROPATCURSOR && g_layoutManager->dragController()->mode() == MBIND_MOVE) { + if (WINDOWSONWORKSPACE > 2) { + auto& v = m_masterNodesData; + + const std::size_t srcIndex = static_cast(std::distance(v.begin(), NODEIT)); + + for (std::size_t i = 0; i < v.size(); ++i) { + const CBox box = v[i]->pTarget->position(); + if (!box.containsPoint(MOUSECOORDS)) + continue; + + std::size_t insertIndex = i; + + switch (orientation) { + case ORIENTATION_LEFT: + case ORIENTATION_RIGHT: + if (MOUSECOORDS.y > box.middle().y) + ++insertIndex; // insert after + break; + + case ORIENTATION_TOP: + case ORIENTATION_BOTTOM: + if (MOUSECOORDS.x > box.middle().x) + ++insertIndex; // insert after + break; + + case ORIENTATION_CENTER: break; + + default: UNREACHABLE(); + } + + if (insertIndex > srcIndex) + --insertIndex; + + if (insertIndex == srcIndex) + break; + + auto node = std::move(v[srcIndex]); + v.erase(v.begin() + static_cast(srcIndex)); + v.insert(v.begin() + static_cast(insertIndex), std::move(node)); + + break; + } + } else if (WINDOWSONWORKSPACE == 2) { + // when dropping as the second tiled window in the workspace, + // make it the master only if the cursor is on the master side of the screen + for (auto const& nd : m_masterNodesData) { + if (nd->isMaster) { + const auto MIDDLE = nd->pTarget->position().middle(); + switch (orientation) { + case ORIENTATION_LEFT: + case ORIENTATION_CENTER: + if (MOUSECOORDS.x < MIDDLE.x) + forceDropAsMaster = true; + break; + case ORIENTATION_RIGHT: + if (MOUSECOORDS.x > MIDDLE.x) + forceDropAsMaster = true; + break; + case ORIENTATION_TOP: + if (MOUSECOORDS.y < MIDDLE.y) + forceDropAsMaster = true; + break; + case ORIENTATION_BOTTOM: + if (MOUSECOORDS.y > MIDDLE.y) + forceDropAsMaster = true; + break; + default: UNREACHABLE(); + } + break; + } + } + } + } + + if (BNEWISMASTER // + || WINDOWSONWORKSPACE == 1 // + || (WINDOWSONWORKSPACE > 2 && !firstMap && OPENINGON && OPENINGON->isMaster) // + || forceDropAsMaster // + || (*PNEWSTATUS == "inherit" && OPENINGON && OPENINGON->isMaster && g_layoutManager->dragController()->mode() != MBIND_MOVE)) { + + if (BNEWBEFOREACTIVE) { + for (auto& nd : m_masterNodesData | std::views::reverse) { + if (nd->isMaster) { + nd->isMaster = false; + lastSplitPercent = nd->percMaster; + break; + } + } + } else { + for (auto& nd : m_masterNodesData) { + if (nd->isMaster) { + nd->isMaster = false; + lastSplitPercent = nd->percMaster; + break; + } + } + } + + PNODE->isMaster = true; + PNODE->percMaster = lastSplitPercent; + + // first, check if it isn't too big. + if (const auto MAXSIZE = target->maxSize().value_or(Math::VECTOR2D_MAX); MAXSIZE.x < PMONITOR->m_size.x * lastSplitPercent || MAXSIZE.y < PMONITOR->m_size.y) { + // we can't continue. make it floating. + m_parent->setFloating(target, true, true); + std::erase(m_masterNodesData, PNODE); + return; + } + } else { + PNODE->isMaster = false; + PNODE->percMaster = lastSplitPercent; + + // first, check if it isn't too big. + if (const auto MAXSIZE = target->maxSize().value_or(Math::VECTOR2D_MAX); + MAXSIZE.x < PMONITOR->m_size.x * (1 - lastSplitPercent) || MAXSIZE.y < PMONITOR->m_size.y * (1.f / (WINDOWSONWORKSPACE - 1))) { + // we can't continue. make it floating. + m_parent->setFloating(target, true); + std::erase(m_masterNodesData, PNODE); + return; + } + } + + // recalc + calculateWorkspace(); +} + +void CMasterAlgorithm::removeTarget(SP target) { + const auto MASTERSLEFT = getMastersNo(); + static auto SMALLSPLIT = CConfigValue("master:allow_small_split"); + + const auto PNODE = getNodeFromTarget(target); + + if (target->fullscreenMode() != FSMODE_NONE) + g_pCompositor->setWindowFullscreenInternal(target->window(), FSMODE_NONE); + + if (PNODE->isMaster && (MASTERSLEFT <= 1 || *SMALLSPLIT == 1)) { + // find a new master from top of the list + for (auto& nd : m_masterNodesData) { + if (!nd->isMaster) { + nd->isMaster = true; + nd->percMaster = PNODE->percMaster; + break; + } + } + } + + std::erase(m_masterNodesData, PNODE); + + if (getMastersNo() == getNodesNo() && MASTERSLEFT > 1) { + for (auto& nd : m_masterNodesData | std::views::reverse) { + nd->isMaster = false; + break; + } + } + // BUGFIX: correct bug where closing one master in a stack of 2 would leave + // the screen half bare, and make it difficult to select remaining window + if (getNodesNo() == 1) { + for (auto& nd : m_masterNodesData) { + if (!nd->isMaster) { + nd->isMaster = true; + break; + } + } + } + + calculateWorkspace(); +} + +void CMasterAlgorithm::resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner) { + const auto PNODE = getNodeFromTarget(target); + + if (!PNODE) + return; + + const auto PMONITOR = m_parent->space()->workspace()->m_monitor; + static auto SLAVECOUNTFORCENTER = CConfigValue("master:slave_count_for_center_master"); + static auto PSMARTRESIZING = CConfigValue("master:smart_resizing"); + + const auto WORKAREA = PMONITOR->logicalBoxMinusReserved(); + const bool DISPLAYBOTTOM = STICKS(PNODE->position.y + PNODE->size.y, WORKAREA.y + WORKAREA.h); + const bool DISPLAYRIGHT = STICKS(PNODE->position.x + PNODE->size.x, WORKAREA.x + WORKAREA.w); + const bool DISPLAYTOP = STICKS(PNODE->position.y, WORKAREA.y); + const bool DISPLAYLEFT = STICKS(PNODE->position.x, WORKAREA.x); + + const bool LEFT = corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT; + const bool TOP = corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT; + const bool NONE = corner == CORNER_NONE; + + const auto MASTERS = getMastersNo(); + const auto WINDOWS = getNodesNo(); + const auto STACKWINDOWS = WINDOWS - MASTERS; + + eOrientation orientation = getDynamicOrientation(); + bool centered = orientation == ORIENTATION_CENTER && (STACKWINDOWS >= *SLAVECOUNTFORCENTER); + double delta = 0; + + if (getNodesNo() == 1 && !centered) + return; + + m_forceWarps = true; + + switch (orientation) { + case ORIENTATION_LEFT: delta = Δ.x / PMONITOR->m_size.x; break; + case ORIENTATION_RIGHT: delta = -Δ.x / PMONITOR->m_size.x; break; + case ORIENTATION_BOTTOM: delta = -Δ.y / PMONITOR->m_size.y; break; + case ORIENTATION_TOP: delta = Δ.y / PMONITOR->m_size.y; break; + case ORIENTATION_CENTER: + delta = Δ.x / PMONITOR->m_size.x; + if (STACKWINDOWS >= *SLAVECOUNTFORCENTER) { + if (!NONE || !PNODE->isMaster) + delta *= 2; + if ((!PNODE->isMaster && DISPLAYLEFT) || (PNODE->isMaster && LEFT && *PSMARTRESIZING)) + delta = -delta; + } + break; + default: UNREACHABLE(); + } + + for (auto& n : m_masterNodesData) { + if (n->isMaster) + n->percMaster = std::clamp(n->percMaster + delta, 0.05, 0.95); + } + + // check the up/down resize + const bool isStackVertical = orientation == ORIENTATION_LEFT || orientation == ORIENTATION_RIGHT || orientation == ORIENTATION_CENTER; + + const auto RESIZEDELTA = isStackVertical ? Δ.y : Δ.x; + + auto nodesInSameColumn = PNODE->isMaster ? MASTERS : STACKWINDOWS; + if (orientation == ORIENTATION_CENTER && !PNODE->isMaster) + nodesInSameColumn = DISPLAYRIGHT ? (nodesInSameColumn + 1) / 2 : nodesInSameColumn / 2; + + const auto SIZE = isStackVertical ? WORKAREA.h / nodesInSameColumn : WORKAREA.w / nodesInSameColumn; + + if (RESIZEDELTA != 0 && nodesInSameColumn > 1) { + if (!*PSMARTRESIZING) { + PNODE->percSize = std::clamp(PNODE->percSize + RESIZEDELTA / SIZE, 0.05, 1.95); + } else { + const auto NODEIT = std::ranges::find(m_masterNodesData, PNODE); + const auto REVNODEIT = std::ranges::find(m_masterNodesData | std::views::reverse, PNODE); + + const float totalSize = isStackVertical ? WORKAREA.h : WORKAREA.w; + const float minSize = totalSize / nodesInSameColumn * 0.2; + const bool resizePrevNodes = isStackVertical ? (TOP || DISPLAYBOTTOM) && !DISPLAYTOP : (LEFT || DISPLAYRIGHT) && !DISPLAYLEFT; + + int nodesLeft = 0; + float sizeLeft = 0; + int nodeCount = 0; + // check the sizes of all the nodes to be resized for later calculation + auto checkNodesLeft = [&sizeLeft, &nodesLeft, orientation, isStackVertical, &nodeCount, PNODE](auto it) { + if (it->isMaster != PNODE->isMaster) + return; + nodeCount++; + if (!it->isMaster && orientation == ORIENTATION_CENTER && nodeCount % 2 == 1) + return; + sizeLeft += isStackVertical ? it->size.y : it->size.x; + nodesLeft++; + }; + float resizeDiff; + if (resizePrevNodes) { + std::for_each(std::next(REVNODEIT), m_masterNodesData.rend(), checkNodesLeft); + resizeDiff = -RESIZEDELTA; + } else { + std::for_each(std::next(NODEIT), m_masterNodesData.end(), checkNodesLeft); + resizeDiff = RESIZEDELTA; + } + + const float nodeSize = isStackVertical ? PNODE->size.y : PNODE->size.x; + const float maxSizeIncrease = sizeLeft - nodesLeft * minSize; + const float maxSizeDecrease = minSize - nodeSize; + + // leaves enough room for the other nodes + resizeDiff = std::clamp(resizeDiff, maxSizeDecrease, maxSizeIncrease); + PNODE->percSize += resizeDiff / SIZE; + + // resize the other nodes + nodeCount = 0; + auto resizeNodesLeft = [maxSizeIncrease, resizeDiff, minSize, orientation, isStackVertical, SIZE, &nodeCount, nodesLeft, PNODE](auto& it) { + if (it->isMaster != PNODE->isMaster) + return; + nodeCount++; + // if center orientation, only resize when on the same side + if (!it->isMaster && orientation == ORIENTATION_CENTER && nodeCount % 2 == 1) + return; + const float size = isStackVertical ? it->size.y : it->size.x; + const float resizeDeltaForEach = maxSizeIncrease != 0 ? resizeDiff * (size - minSize) / maxSizeIncrease : resizeDiff / nodesLeft; + it->percSize -= resizeDeltaForEach / SIZE; + }; + if (resizePrevNodes) + std::for_each(std::next(REVNODEIT), m_masterNodesData.rend(), resizeNodesLeft); + else + std::for_each(std::next(NODEIT), m_masterNodesData.end(), resizeNodesLeft); + } + } + + recalculate(); + + m_forceWarps = false; +} + +void CMasterAlgorithm::swapTargets(SP a, SP b) { + auto nodeA = getNodeFromTarget(a); + auto nodeB = getNodeFromTarget(b); + + if (nodeA) + nodeA->pTarget = b; + if (nodeB) + nodeB->pTarget = a; +} + +void CMasterAlgorithm::moveTargetInDirection(SP t, Math::eDirection dir, bool silent) { + static auto PMONITORFALLBACK = CConfigValue("binds:window_direction_monitor_fallback"); + + const auto PWINDOW2 = g_pCompositor->getWindowInDirection(t->window(), dir); + + if (!t->window()) + return; + + PHLWORKSPACE targetWs; + + if (!PWINDOW2 && t->space() && t->space()->workspace()) { + // try to find a monitor in dir + const auto PMONINDIR = g_pCompositor->getMonitorInDirection(t->space()->workspace()->m_monitor.lock(), dir); + if (PMONINDIR) + targetWs = PMONINDIR->m_activeWorkspace; + } else + targetWs = PWINDOW2->m_workspace; + + if (!targetWs) + return; + + t->window()->setAnimationsToMove(); + + if (t->window()->m_workspace != targetWs) { + if (!*PMONITORFALLBACK) + return; // noop + + t->assignToSpace(targetWs->m_space, focalPointForDir(t, dir)); + } else if (PWINDOW2) { + // if same monitor, switch windows + g_layoutManager->switchTargets(t, PWINDOW2->layoutTarget()); + if (silent) + Desktop::focusState()->fullWindowFocus(PWINDOW2, Desktop::FOCUS_REASON_KEYBIND); + + recalculate(); + } +} + +void CMasterAlgorithm::recalculate() { + calculateWorkspace(); +} + +std::expected CMasterAlgorithm::layoutMsg(const std::string_view& sv) { + auto switchToWindow = [&](SP target) { + if (!target || !validMapped(target->window())) + return; + + Desktop::focusState()->fullWindowFocus(target->window(), Desktop::FOCUS_REASON_KEYBIND); + g_pCompositor->warpCursorTo(target->position().middle()); + + g_pInputManager->m_forcedFocus = target->window(); + g_pInputManager->simulateMouseMovement(); + g_pInputManager->m_forcedFocus.reset(); + }; + + CVarList2 vars(std::string{sv}, 0, 's'); + + if (vars.size() < 1 || vars[0].empty()) { + Log::logger->log(Log::ERR, "layoutmsg called without params"); + return std::unexpected("layoutmsg without params"); + } + + auto command = vars[0]; + + // swapwithmaster + // first message argument can have the following values: + // * master - keep the focus at the new master + // * child - keep the focus at the new child + // * auto (default) - swap the focus (keep the focus of the previously selected window) + // * ignoremaster - ignore if master is focused + + const auto PWINDOW = Desktop::focusState()->window(); + + if (command == "swapwithmaster") { + if (!PWINDOW) + return std::unexpected("No focused window"); + + if (!isWindowTiled(PWINDOW)) + return std::unexpected("focused window isn't tiled"); + + const auto PMASTER = getMasterNode(); + + if (!PMASTER) + return std::unexpected("no master node"); + + const auto NEWCHILD = PMASTER->pTarget.lock(); + + const bool IGNORE_IF_MASTER = vars.size() >= 2 && std::ranges::any_of(vars, [](const auto& e) { return e == "ignoremaster"; }); + + if (PMASTER->pTarget.lock() != PWINDOW->layoutTarget()) { + const auto& NEWMASTER = PWINDOW->layoutTarget(); + const bool newFocusToChild = vars.size() >= 2 && vars[1] == "child"; + g_layoutManager->switchTargets(NEWMASTER, NEWCHILD); + const auto NEWFOCUS = newFocusToChild ? NEWCHILD : NEWMASTER; + switchToWindow(NEWFOCUS); + } else if (!IGNORE_IF_MASTER) { + for (auto const& n : m_masterNodesData) { + if (!n->isMaster) { + const auto NEWMASTER = n->pTarget.lock(); + g_layoutManager->switchTargets(NEWMASTER, NEWCHILD); + const bool newFocusToMaster = vars.size() >= 2 && vars[1] == "master"; + const auto NEWFOCUS = newFocusToMaster ? NEWMASTER : NEWCHILD; + switchToWindow(NEWFOCUS); + break; + } + } + } + + return {}; + } + // focusmaster + // first message argument can have the following values: + // * master - keep the focus at the new master, even if it was focused before + // * previous - focus window which was previously switched from using `focusmaster previous` command, otherwise fallback to `auto` + // * auto (default) - swap the focus with the first child, if the current focus was master, otherwise focus master + else if (command == "focusmaster") { + if (!PWINDOW) + return std::unexpected("no focused window"); + + const auto PMASTER = getMasterNode(); + + if (!PMASTER) + return std::unexpected("no master"); + + const auto& ARG = vars[1]; // returns empty string if out of bounds + + if (PMASTER->pTarget.lock() != PWINDOW->layoutTarget()) { + switchToWindow(PMASTER->pTarget.lock()); + // save previously focused window (only for `previous` mode) + if (ARG == "previous") + m_workspaceData.focusMasterPrev = PWINDOW->layoutTarget(); + return {}; + } + + const auto focusAuto = [&]() { + // focus first non-master window + for (auto const& n : m_masterNodesData) { + if (!n->isMaster) { + switchToWindow(n->pTarget.lock()); + break; + } + } + }; + + if (ARG == "master") + return {}; + // switch to previously saved window + else if (ARG == "previous") { + const auto PREVWINDOW = m_workspaceData.focusMasterPrev.lock(); + const bool VALID = PREVWINDOW && getNodeFromWindow(PREVWINDOW->window()) && (PWINDOW != PREVWINDOW->window()); + VALID ? switchToWindow(PREVWINDOW) : focusAuto(); + } else + focusAuto(); + } else if (command == "cyclenext") { + if (!PWINDOW) + return std::unexpected("no window"); + + const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop"; + const auto PNEXTWINDOW = getNextTarget(PWINDOW->layoutTarget(), true, !NOLOOP); + switchToWindow(PNEXTWINDOW); + } else if (command == "cycleprev") { + if (!PWINDOW) + return std::unexpected("no window"); + + const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop"; + const auto PPREVWINDOW = getNextTarget(PWINDOW->layoutTarget(), false, !NOLOOP); + switchToWindow(PPREVWINDOW); + } else if (command == "swapnext") { + if (!validMapped(PWINDOW)) + return std::unexpected("no window"); + + if (PWINDOW->layoutTarget()->floating()) { + g_pKeybindManager->m_dispatchers["swapnext"](""); + return {}; + } + + const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop"; + const auto PWINDOWTOSWAPWITH = getNextTarget(PWINDOW->layoutTarget(), true, !NOLOOP); + + if (PWINDOWTOSWAPWITH) { + g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); + g_layoutManager->switchTargets(PWINDOW->layoutTarget(), PWINDOWTOSWAPWITH); + switchToWindow(PWINDOW->layoutTarget()); + } + } else if (command == "swapprev") { + if (!validMapped(PWINDOW)) + return std::unexpected("no window"); + + if (PWINDOW->layoutTarget()->floating()) { + g_pKeybindManager->m_dispatchers["swapnext"]("prev"); + return {}; + } + + const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop"; + const auto PWINDOWTOSWAPWITH = getNextTarget(PWINDOW->layoutTarget(), false, !NOLOOP); + + if (PWINDOWTOSWAPWITH) { + g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); + g_layoutManager->switchTargets(PWINDOW->layoutTarget(), PWINDOWTOSWAPWITH); + switchToWindow(PWINDOW->layoutTarget()); + } + } else if (command == "addmaster") { + if (!validMapped(PWINDOW)) + return std::unexpected("no window"); + + if (PWINDOW->layoutTarget()->floating()) + return std::unexpected("window is floating"); + + const auto PNODE = getNodeFromTarget(PWINDOW->layoutTarget()); + + const auto WINDOWS = getNodesNo(); + const auto MASTERS = getMastersNo(); + static auto SMALLSPLIT = CConfigValue("master:allow_small_split"); + + if (MASTERS + 2 > WINDOWS && *SMALLSPLIT == 0) + return std::unexpected("nothing to do"); + + g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); + + if (!PNODE || PNODE->isMaster) { + // first non-master node + for (auto& n : m_masterNodesData) { + if (!n->isMaster) { + n->isMaster = true; + break; + } + } + } else { + PNODE->isMaster = true; + } + + calculateWorkspace(); + + } else if (command == "removemaster") { + + if (!validMapped(PWINDOW)) + return std::unexpected("no window"); + + if (PWINDOW->layoutTarget()->floating()) + return std::unexpected("window isnt tiled"); + + const auto PNODE = getNodeFromTarget(PWINDOW->layoutTarget()); + + const auto WINDOWS = getNodesNo(); + const auto MASTERS = getMastersNo(); + + if (WINDOWS < 2 || MASTERS < 2) + return std::unexpected("nothing to do"); + + g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); + + if (!PNODE || !PNODE->isMaster) { + // first non-master node + for (auto& nd : m_masterNodesData | std::views::reverse) { + if (nd->isMaster) { + nd->isMaster = false; + break; + } + } + } else { + PNODE->isMaster = false; + } + + calculateWorkspace(); + } else if (command == "orientationleft" || command == "orientationright" || command == "orientationtop" || command == "orientationbottom" || command == "orientationcenter") { + if (!PWINDOW) + return std::unexpected("no window"); + + g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); + + if (command == "orientationleft") + m_workspaceData.explicitOrientation = ORIENTATION_LEFT; + else if (command == "orientationright") + m_workspaceData.explicitOrientation = ORIENTATION_RIGHT; + else if (command == "orientationtop") + m_workspaceData.explicitOrientation = ORIENTATION_TOP; + else if (command == "orientationbottom") + m_workspaceData.explicitOrientation = ORIENTATION_BOTTOM; + else if (command == "orientationcenter") + m_workspaceData.explicitOrientation = ORIENTATION_CENTER; + + calculateWorkspace(); + } else if (command == "orientationnext") { + runOrientationCycle(nullptr, 1); + } else if (command == "orientationprev") { + runOrientationCycle(nullptr, -1); + } else if (command == "orientationcycle") { + runOrientationCycle(&vars, 1); + } else if (command == "mfact") { + + if (!PWINDOW) + return std::unexpected("no window"); + + const bool exact = vars[1] == "exact"; + + float ratio = 0.F; + + try { + ratio = std::stof(std::string{exact ? vars[2] : vars[1]}); + } catch (...) { return std::unexpected("bad ratio"); } + + const auto PNODE = getNodeFromWindow(PWINDOW); + + const auto PMASTER = getMasterNode(); + + float newRatio = exact ? ratio : PMASTER->percMaster + ratio; + PMASTER->percMaster = std::clamp(newRatio, 0.05f, 0.95f); + + recalculate(); + } else if (command == "rollnext") { + const auto PNODE = getNodeFromWindow(PWINDOW); + + if (!PNODE) + return std::unexpected("window couldnt be found"); + + const auto OLDMASTER = PNODE->isMaster ? PNODE : getMasterNode(); + if (!OLDMASTER) + return std::unexpected("no old master"); + + auto oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER); + + for (auto& nd : m_masterNodesData) { + if (!nd->isMaster) { + const auto& newMaster = nd; + newMaster->isMaster = true; + + auto newMasterIt = std::ranges::find(m_masterNodesData, newMaster); + + if (newMasterIt < oldMasterIt) + std::ranges::rotate(newMasterIt, std::next(newMasterIt), oldMasterIt); + else if (newMasterIt > oldMasterIt) + std::ranges::rotate(oldMasterIt, newMasterIt, std::next(newMasterIt)); + + switchToWindow(newMaster->pTarget.lock()); + OLDMASTER->isMaster = false; + + oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER); + if (oldMasterIt != m_masterNodesData.end()) + std::ranges::rotate(oldMasterIt, std::next(oldMasterIt), m_masterNodesData.end()); + + break; + } + } + + calculateWorkspace(); + } else if (command == "rollprev") { + const auto PNODE = getNodeFromWindow(PWINDOW); + + if (!PNODE) + return std::unexpected("window couldnt be found"); + + const auto OLDMASTER = PNODE->isMaster ? PNODE : getMasterNode(); + if (!OLDMASTER) + return std::unexpected("no old master"); + + auto oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER); + + for (auto& nd : m_masterNodesData | std::views::reverse) { + if (!nd->isMaster) { + const auto& newMaster = nd; + newMaster->isMaster = true; + + auto newMasterIt = std::ranges::find(m_masterNodesData, newMaster); + + if (newMasterIt < oldMasterIt) + std::ranges::rotate(newMasterIt, std::next(newMasterIt), oldMasterIt); + else if (newMasterIt > oldMasterIt) + std::ranges::rotate(oldMasterIt, newMasterIt, std::next(newMasterIt)); + + switchToWindow(newMaster->pTarget.lock()); + OLDMASTER->isMaster = false; + + oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER); + if (oldMasterIt != m_masterNodesData.begin()) + std::ranges::rotate(m_masterNodesData.begin(), oldMasterIt, std::next(oldMasterIt)); + + break; + } + } + + calculateWorkspace(); + } + + return {}; +} + +std::optional CMasterAlgorithm::predictSizeForNewTarget() { + static auto PNEWSTATUS = CConfigValue("master:new_status"); + + const auto MONITOR = m_parent->space()->workspace()->m_monitor; + + if (!MONITOR) + return std::nullopt; + + const int NODES = getNodesNo(); + + if (NODES <= 0) + return Desktop::focusState()->monitor()->m_size; + + const auto MASTER = getMasterNode(); + if (!MASTER) // wtf + return std::nullopt; + + if (*PNEWSTATUS == "master") { + return MASTER->size; + } else { + const auto SLAVES = NODES - getMastersNo(); + + // TODO: make this better + if (SLAVES == 0) + return Vector2D{MONITOR->m_size.x / 2.F, MONITOR->m_size.y}; + else + return Vector2D{MONITOR->m_size.x - MASTER->size.x, MONITOR->m_size.y / (SLAVES + 1)}; + } + + return std::nullopt; +} + +void CMasterAlgorithm::buildOrientationCycleVectorFromVars(std::vector& cycle, Hyprutils::String::CVarList2* vars) { + for (size_t i = 1; i < vars->size(); ++i) { + if ((*vars)[i] == "top") { + cycle.emplace_back(ORIENTATION_TOP); + } else if ((*vars)[i] == "right") { + cycle.emplace_back(ORIENTATION_RIGHT); + } else if ((*vars)[i] == "bottom") { + cycle.emplace_back(ORIENTATION_BOTTOM); + } else if ((*vars)[i] == "left") { + cycle.emplace_back(ORIENTATION_LEFT); + } else if ((*vars)[i] == "center") { + cycle.emplace_back(ORIENTATION_CENTER); + } + } +} + +void CMasterAlgorithm::buildOrientationCycleVectorFromEOperation(std::vector& cycle) { + for (int i = 0; i <= ORIENTATION_CENTER; ++i) { + cycle.push_back(sc(i)); + } +} + +eOrientation CMasterAlgorithm::defaultOrientation() { + static auto PORIENT = CConfigValue("master:orientation"); + + const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(m_parent->space()->workspace()); + std::string orientationString; + if (WORKSPACERULE.layoutopts.contains("orientation")) + orientationString = WORKSPACERULE.layoutopts.at("orientation"); + else + orientationString = *PORIENT; + + eOrientation orientation = ORIENTATION_LEFT; + // override if workspace rule is set + if (!orientationString.empty()) { + if (orientationString == "top") + orientation = ORIENTATION_TOP; + else if (orientationString == "right") + orientation = ORIENTATION_RIGHT; + else if (orientationString == "bottom") + orientation = ORIENTATION_BOTTOM; + else if (orientationString == "center") + orientation = ORIENTATION_CENTER; + else + orientation = ORIENTATION_LEFT; + } + + return orientation; +} + +void CMasterAlgorithm::runOrientationCycle(Hyprutils::String::CVarList2* vars, int next) { + std::vector cycle; + if (vars != nullptr) + buildOrientationCycleVectorFromVars(cycle, vars); + + if (cycle.empty()) + buildOrientationCycleVectorFromEOperation(cycle); + + const auto PWINDOW = Desktop::focusState()->window(); + + if (!PWINDOW) + return; + + g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); + + int nextOrPrev = 0; + for (size_t i = 0; i < cycle.size(); ++i) { + if (m_workspaceData.explicitOrientation.value_or(defaultOrientation()) == cycle[i]) { + nextOrPrev = i + next; + break; + } + } + + if (nextOrPrev >= sc(cycle.size())) + nextOrPrev = nextOrPrev % sc(cycle.size()); + else if (nextOrPrev < 0) + nextOrPrev = cycle.size() + (nextOrPrev % sc(cycle.size())); + + m_workspaceData.explicitOrientation = cycle.at(nextOrPrev); + calculateWorkspace(); +} + +eOrientation CMasterAlgorithm::getDynamicOrientation() { + return m_workspaceData.explicitOrientation.value_or(defaultOrientation()); +} + +int CMasterAlgorithm::getNodesNo() { + return m_masterNodesData.size(); +} + +SP CMasterAlgorithm::getNodeFromWindow(PHLWINDOW x) { + return x ? getNodeFromTarget(x->layoutTarget()) : nullptr; +} + +SP CMasterAlgorithm::getNodeFromTarget(SP x) { + for (const auto& n : m_masterNodesData) { + if (n->pTarget == x) + return n; + } + + return nullptr; +} + +SP CMasterAlgorithm::getMasterNode() { + for (const auto& n : m_masterNodesData) { + if (n->isMaster) + return n; + } + + return nullptr; +} + +void CMasterAlgorithm::calculateWorkspace() { + const auto PMASTERNODE = getMasterNode(); + + if (!PMASTERNODE) + return; + + Hyprutils::Utils::CScopeGuard x([this] { + g_pHyprRenderer->damageMonitor(m_parent->space()->workspace()->m_monitor.lock()); + + if (!m_forceWarps) + return; + + for (const auto& n : m_masterNodesData) { + n->pTarget->warpPositionSize(); + } + }); + + eOrientation orientation = getDynamicOrientation(); + bool centerMasterWindow = false; + static auto SLAVECOUNTFORCENTER = CConfigValue("master:slave_count_for_center_master"); + static auto CMFALLBACK = CConfigValue("master:center_master_fallback"); + static auto PIGNORERESERVED = CConfigValue("master:center_ignores_reserved"); + static auto PSMARTRESIZING = CConfigValue("master:smart_resizing"); + + const auto MASTERS = getMastersNo(); + const auto WINDOWS = getNodesNo(); + const auto STACKWINDOWS = WINDOWS - MASTERS; + const auto WORKAREA = m_parent->space()->workArea(); + const auto PMONITOR = m_parent->space()->workspace()->m_monitor; + const auto reservedLeft = PMONITOR ? PMONITOR->m_reservedArea.left() : 0; + const auto reservedRight = PMONITOR ? PMONITOR->m_reservedArea.right() : 0; + const auto UNRESERVED_WIDTH = WORKAREA.width + reservedLeft + reservedRight; + + if (orientation == ORIENTATION_CENTER) { + if (STACKWINDOWS >= *SLAVECOUNTFORCENTER) + centerMasterWindow = true; + else { + if (*CMFALLBACK == "left") + orientation = ORIENTATION_LEFT; + else if (*CMFALLBACK == "right") + orientation = ORIENTATION_RIGHT; + else if (*CMFALLBACK == "top") + orientation = ORIENTATION_TOP; + else if (*CMFALLBACK == "bottom") + orientation = ORIENTATION_BOTTOM; + else + orientation = ORIENTATION_LEFT; + } + } + + const float totalSize = (orientation == ORIENTATION_TOP || orientation == ORIENTATION_BOTTOM) ? WORKAREA.w : WORKAREA.h; + const float masterAverageSize = totalSize / MASTERS; + const float slaveAverageSize = totalSize / STACKWINDOWS; + float masterAccumulatedSize = 0; + float slaveAccumulatedSize = 0; + + if (*PSMARTRESIZING) { + // check the total width and height so that later + // if larger/smaller than screen size them down/up + for (auto const& nd : m_masterNodesData) { + if (nd->isMaster) + masterAccumulatedSize += totalSize / MASTERS * nd->percSize; + else + slaveAccumulatedSize += totalSize / STACKWINDOWS * nd->percSize; + } + } + + // compute placement of master window(s) + if (WINDOWS == 1 && !centerMasterWindow) { + static auto PALWAYSKEEPPOSITION = CConfigValue("master:always_keep_position"); + if (*PALWAYSKEEPPOSITION) { + const float WIDTH = WORKAREA.w * PMASTERNODE->percMaster; + float nextX = 0; + + if (orientation == ORIENTATION_RIGHT) + nextX = WORKAREA.w - WIDTH; + else if (orientation == ORIENTATION_CENTER) + nextX = (WORKAREA.w - WIDTH) / 2; + + PMASTERNODE->size = Vector2D(WIDTH, WORKAREA.h); + PMASTERNODE->position = WORKAREA.pos() + Vector2D(nextX, 0.0); + } else { + PMASTERNODE->size = WORKAREA.size(); + PMASTERNODE->position = WORKAREA.pos(); + } + + PMASTERNODE->pTarget->setPositionGlobal({PMASTERNODE->position, PMASTERNODE->size}); + return; + } else if (orientation == ORIENTATION_TOP || orientation == ORIENTATION_BOTTOM) { + const float HEIGHT = STACKWINDOWS != 0 ? WORKAREA.h * PMASTERNODE->percMaster : WORKAREA.h; + float widthLeft = WORKAREA.w; + int mastersLeft = MASTERS; + float nextX = 0; + float nextY = 0; + + if (orientation == ORIENTATION_BOTTOM) + nextY = WORKAREA.h - HEIGHT; + + for (auto& nd : m_masterNodesData) { + if (!nd->isMaster) + continue; + + float WIDTH = mastersLeft > 1 ? widthLeft / mastersLeft * nd->percSize : widthLeft; + if (WIDTH > widthLeft * 0.9f && mastersLeft > 1) + WIDTH = widthLeft * 0.9f; + + if (*PSMARTRESIZING) { + nd->percSize *= WORKAREA.w / masterAccumulatedSize; + WIDTH = masterAverageSize * nd->percSize; + } + + nd->size = Vector2D(WIDTH, HEIGHT); + nd->position = WORKAREA.pos() + Vector2D(nextX, nextY); + nd->pTarget->setPositionGlobal({nd->position, nd->size}); + + mastersLeft--; + widthLeft -= WIDTH; + nextX += WIDTH; + } + } else { // orientation left, right or center + const float TOTAL_WIDTH = *PIGNORERESERVED && centerMasterWindow ? UNRESERVED_WIDTH : WORKAREA.w; + float WIDTH = TOTAL_WIDTH; + float heightLeft = WORKAREA.h; + int mastersLeft = MASTERS; + float nextX = 0; + float nextY = 0; + + if (STACKWINDOWS > 0 || centerMasterWindow) + WIDTH *= PMASTERNODE->percMaster; + + if (orientation == ORIENTATION_RIGHT) + nextX = WORKAREA.w - WIDTH; + else if (centerMasterWindow) + nextX += (TOTAL_WIDTH - WIDTH) / 2; + + for (auto& nd : m_masterNodesData) { + if (!nd->isMaster) + continue; + + float HEIGHT = mastersLeft > 1 ? heightLeft / mastersLeft * nd->percSize : heightLeft; + if (HEIGHT > heightLeft * 0.9f && mastersLeft > 1) + HEIGHT = heightLeft * 0.9f; + + if (*PSMARTRESIZING) { + nd->percSize *= WORKAREA.h / masterAccumulatedSize; + HEIGHT = masterAverageSize * nd->percSize; + } + + nd->size = Vector2D(WIDTH, HEIGHT); + nd->position = (*PIGNORERESERVED && centerMasterWindow ? WORKAREA.pos() - Vector2D(reservedLeft, 0.0) : WORKAREA.pos()) + Vector2D(nextX, nextY); + nd->pTarget->setPositionGlobal({nd->position, nd->size}); + + mastersLeft--; + heightLeft -= HEIGHT; + nextY += HEIGHT; + } + } + + if (STACKWINDOWS == 0) + return; + + // compute placement of slave window(s) + int slavesLeft = STACKWINDOWS; + if (orientation == ORIENTATION_TOP || orientation == ORIENTATION_BOTTOM) { + const float HEIGHT = WORKAREA.h - PMASTERNODE->size.y; + float widthLeft = WORKAREA.w; + float nextX = 0; + float nextY = 0; + + if (orientation == ORIENTATION_TOP) + nextY = PMASTERNODE->size.y; + + for (auto& nd : m_masterNodesData) { + if (nd->isMaster) + continue; + + float WIDTH = slavesLeft > 1 ? widthLeft / slavesLeft * nd->percSize : widthLeft; + if (WIDTH > widthLeft * 0.9f && slavesLeft > 1) + WIDTH = widthLeft * 0.9f; + + if (*PSMARTRESIZING) { + nd->percSize *= WORKAREA.w / slaveAccumulatedSize; + WIDTH = slaveAverageSize * nd->percSize; + } + + nd->size = Vector2D(WIDTH, HEIGHT); + nd->position = WORKAREA.pos() + Vector2D(nextX, nextY); + nd->pTarget->setPositionGlobal({nd->position, nd->size}); + + slavesLeft--; + widthLeft -= WIDTH; + nextX += WIDTH; + } + } else if (orientation == ORIENTATION_LEFT || orientation == ORIENTATION_RIGHT) { + const float WIDTH = WORKAREA.w - PMASTERNODE->size.x; + float heightLeft = WORKAREA.h; + float nextY = 0; + float nextX = 0; + + if (orientation == ORIENTATION_LEFT) + nextX = PMASTERNODE->size.x; + + for (auto& nd : m_masterNodesData) { + if (nd->isMaster) + continue; + + float HEIGHT = slavesLeft > 1 ? heightLeft / slavesLeft * nd->percSize : heightLeft; + if (HEIGHT > heightLeft * 0.9f && slavesLeft > 1) + HEIGHT = heightLeft * 0.9f; + + if (*PSMARTRESIZING) { + nd->percSize *= WORKAREA.h / slaveAccumulatedSize; + HEIGHT = slaveAverageSize * nd->percSize; + } + + nd->size = Vector2D(WIDTH, HEIGHT); + nd->position = WORKAREA.pos() + Vector2D(nextX, nextY); + nd->pTarget->setPositionGlobal({nd->position, nd->size}); + + slavesLeft--; + heightLeft -= HEIGHT; + nextY += HEIGHT; + } + } else { // slaves for centered master window(s) + const float WIDTH = ((*PIGNORERESERVED ? UNRESERVED_WIDTH : WORKAREA.w) - PMASTERNODE->size.x) / 2.0; + float heightLeft = 0; + float heightLeftL = WORKAREA.h; + float heightLeftR = WORKAREA.h; + float nextX = 0; + float nextY = 0; + float nextYL = 0; + float nextYR = 0; + bool onRight = *CMFALLBACK == "right"; + int slavesLeftL = 1 + (slavesLeft - 1) / 2; + int slavesLeftR = slavesLeft - slavesLeftL; + + if (onRight) { + slavesLeftR = 1 + (slavesLeft - 1) / 2; + slavesLeftL = slavesLeft - slavesLeftR; + } + + const float slaveAverageHeightL = WORKAREA.h / slavesLeftL; + const float slaveAverageHeightR = WORKAREA.h / slavesLeftR; + float slaveAccumulatedHeightL = 0; + float slaveAccumulatedHeightR = 0; + + if (*PSMARTRESIZING) { + for (auto const& nd : m_masterNodesData) { + if (nd->isMaster) + continue; + + if (onRight) + slaveAccumulatedHeightR += slaveAverageHeightR * nd->percSize; + else + slaveAccumulatedHeightL += slaveAverageHeightL * nd->percSize; + + onRight = !onRight; + } + + onRight = *CMFALLBACK == "right"; + } + + for (auto& nd : m_masterNodesData) { + if (nd->isMaster) + continue; + + if (onRight) { + nextX = WIDTH + PMASTERNODE->size.x - (*PIGNORERESERVED ? reservedLeft : 0); + nextY = nextYR; + heightLeft = heightLeftR; + slavesLeft = slavesLeftR; + } else { + nextX = 0; + nextY = nextYL; + heightLeft = heightLeftL; + slavesLeft = slavesLeftL; + } + + float HEIGHT = slavesLeft > 1 ? heightLeft / slavesLeft * nd->percSize : heightLeft; + if (HEIGHT > heightLeft * 0.9f && slavesLeft > 1) + HEIGHT = heightLeft * 0.9f; + + if (*PSMARTRESIZING) { + if (onRight) { + nd->percSize *= WORKAREA.h / slaveAccumulatedHeightR; + HEIGHT = slaveAverageHeightR * nd->percSize; + } else { + nd->percSize *= WORKAREA.h / slaveAccumulatedHeightL; + HEIGHT = slaveAverageHeightL * nd->percSize; + } + } + + nd->size = Vector2D(*PIGNORERESERVED ? (WIDTH - (onRight ? reservedRight : reservedLeft)) : WIDTH, HEIGHT); + nd->position = WORKAREA.pos() + Vector2D(nextX, nextY); + nd->pTarget->setPositionGlobal({nd->position, nd->size}); + + if (onRight) { + heightLeftR -= HEIGHT; + nextYR += HEIGHT; + slavesLeftR--; + } else { + heightLeftL -= HEIGHT; + nextYL += HEIGHT; + slavesLeftL--; + } + + onRight = !onRight; + } + } +} + +SP CMasterAlgorithm::getNextCandidate(SP old) { + const auto MIDDLE = old->position().middle(); + + if (const auto NODE = getClosestNode(MIDDLE); NODE) + return NODE->pTarget.lock(); + + if (const auto NODE = getMasterNode(); NODE) + return NODE->pTarget.lock(); + + return nullptr; +} + +SP CMasterAlgorithm::getNextTarget(SP t, bool next, bool loop) { + if (t->floating()) + return nullptr; + + const auto PNODE = getNodeFromTarget(t); + + auto nodes = m_masterNodesData; + if (!next) + std::ranges::reverse(nodes); + + const auto NODEIT = std::ranges::find(nodes, PNODE); + + const bool ISMASTER = PNODE->isMaster; + + auto CANDIDATE = std::find_if(NODEIT, nodes.end(), [&](const auto& other) { return other != PNODE && ISMASTER == other->isMaster; }); + if (CANDIDATE == nodes.end()) + CANDIDATE = std::ranges::find_if(nodes, [&](const auto& other) { return other != PNODE && ISMASTER != other->isMaster; }); + + if (CANDIDATE != nodes.end() && !loop) { + if ((*CANDIDATE)->isMaster && next) + return nullptr; + if (!(*CANDIDATE)->isMaster && ISMASTER && !next) + return nullptr; + } + + return CANDIDATE == nodes.end() ? nullptr : (*CANDIDATE)->pTarget.lock(); +} + +int CMasterAlgorithm::getMastersNo() { + return std::ranges::count_if(m_masterNodesData, [](const auto& n) { return n->isMaster; }); +} + +bool CMasterAlgorithm::isWindowTiled(PHLWINDOW x) { + return x && !x->layoutTarget()->floating(); +} + +SP CMasterAlgorithm::getClosestNode(const Vector2D& point) { + SP res = nullptr; + double distClosest = -1; + for (auto& n : m_masterNodesData) { + if (n->pTarget && Desktop::View::validMapped(n->pTarget->window())) { + auto distAnother = vecToRectDistanceSquared(point, n->position, n->position + n->size); + if (!res || distAnother < distClosest) { + res = n; + distClosest = distAnother; + } + } + } + return res; +} diff --git a/src/layout/algorithm/tiled/master/MasterAlgorithm.hpp b/src/layout/algorithm/tiled/master/MasterAlgorithm.hpp new file mode 100644 index 00000000..5cfa6b36 --- /dev/null +++ b/src/layout/algorithm/tiled/master/MasterAlgorithm.hpp @@ -0,0 +1,76 @@ +#include "../../TiledAlgorithm.hpp" + +#include + +namespace Layout { + class CAlgorithm; +} + +namespace Layout::Tiled { + struct SMasterNodeData; + + //orientation determines which side of the screen the master area resides + enum eOrientation : uint8_t { + ORIENTATION_LEFT = 0, + ORIENTATION_TOP, + ORIENTATION_RIGHT, + ORIENTATION_BOTTOM, + ORIENTATION_CENTER + }; + + struct SMasterWorkspaceData { + WORKSPACEID workspaceID = WORKSPACE_INVALID; + std::optional explicitOrientation; + // Previously focused non-master window when `focusmaster previous` command was issued + WP focusMasterPrev; + + // + bool operator==(const SMasterWorkspaceData& rhs) const { + return workspaceID == rhs.workspaceID; + } + }; + + class CMasterAlgorithm : public ITiledAlgorithm { + public: + CMasterAlgorithm() = default; + virtual ~CMasterAlgorithm() = default; + + virtual void newTarget(SP target); + virtual void movedTarget(SP target, std::optional focalPoint = std::nullopt); + virtual void removeTarget(SP target); + + virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + virtual void recalculate(); + + virtual SP getNextCandidate(SP old); + + virtual std::expected layoutMsg(const std::string_view& sv); + virtual std::optional predictSizeForNewTarget(); + + virtual void swapTargets(SP a, SP b); + virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + private: + std::vector> m_masterNodesData; + SMasterWorkspaceData m_workspaceData; + + void addTarget(SP target, bool firstMap); + + bool m_forceWarps = false; + + void buildOrientationCycleVectorFromVars(std::vector& cycle, Hyprutils::String::CVarList2* vars); + void buildOrientationCycleVectorFromEOperation(std::vector& cycle); + void runOrientationCycle(Hyprutils::String::CVarList2* vars, int next); + eOrientation getDynamicOrientation(); + int getNodesNo(); + SP getNodeFromWindow(PHLWINDOW); + SP getNodeFromTarget(SP); + SP getMasterNode(); + SP getClosestNode(const Vector2D&); + void calculateWorkspace(); + SP getNextTarget(SP, bool, bool); + int getMastersNo(); + bool isWindowTiled(PHLWINDOW); + eOrientation defaultOrientation(); + }; +}; \ No newline at end of file diff --git a/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp b/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp new file mode 100644 index 00000000..fe92f27c --- /dev/null +++ b/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp @@ -0,0 +1,278 @@ +#include "MonocleAlgorithm.hpp" + +#include "../../Algorithm.hpp" +#include "../../../space/Space.hpp" +#include "../../../target/WindowTarget.hpp" +#include "../../../LayoutManager.hpp" + +#include "../../../../config/ConfigValue.hpp" +#include "../../../../desktop/state/FocusState.hpp" +#include "../../../../desktop/history/WindowHistoryTracker.hpp" +#include "../../../../helpers/Monitor.hpp" +#include "../../../../Compositor.hpp" +#include "../../../../event/EventBus.hpp" + +#include +#include +#include + +using namespace Hyprutils::String; +using namespace Hyprutils::Utils; +using namespace Layout; +using namespace Layout::Tiled; + +CMonocleAlgorithm::CMonocleAlgorithm() { + // hook into focus changes to bring focused window to front + m_focusCallback = Event::bus()->m_events.window.active.listen([this](PHLWINDOW pWindow, Desktop::eFocusReason reason) { + if (!pWindow) + return; + + if (!pWindow->m_workspace->isVisible()) + return; + + const auto TARGET = pWindow->layoutTarget(); + if (!TARGET) + return; + + focusTargetUpdate(TARGET); + }); +} + +CMonocleAlgorithm::~CMonocleAlgorithm() { + // unhide all windows before destruction + for (const auto& data : m_targetDatas) { + const auto TARGET = data->target.lock(); + if (!TARGET) + continue; + + const auto WINDOW = TARGET->window(); + if (WINDOW) + WINDOW->setHidden(false); + } + + m_focusCallback.reset(); +} + +SP CMonocleAlgorithm::dataFor(SP t) { + for (auto& data : m_targetDatas) { + if (data->target.lock() == t) + return data; + } + return nullptr; +} + +void CMonocleAlgorithm::newTarget(SP target) { + const auto DATA = m_targetDatas.emplace_back(makeShared(target)); + + m_currentVisibleIndex = m_targetDatas.size() - 1; + + recalculate(); +} + +void CMonocleAlgorithm::movedTarget(SP target, std::optional focalPoint) { + newTarget(target); +} + +void CMonocleAlgorithm::removeTarget(SP target) { + auto it = std::ranges::find_if(m_targetDatas, [target](const auto& data) { return data->target.lock() == target; }); + + if (it == m_targetDatas.end()) + return; + + // unhide window when removing from monocle layout + const auto WINDOW = target->window(); + if (WINDOW) + WINDOW->setHidden(false); + + const auto INDEX = std::distance(m_targetDatas.begin(), it); + m_targetDatas.erase(it); + + if (m_targetDatas.empty()) { + m_currentVisibleIndex = 0; + return; + } + + // try to use the last window in history if we can + for (const auto& historyWindow : Desktop::History::windowTracker()->historyForWorkspace(m_parent->space()->workspace()) | std::views::reverse) { + auto it = std::ranges::find_if(m_targetDatas, [&historyWindow](const auto& d) { return d->target == historyWindow->layoutTarget(); }); + + if (it == m_targetDatas.end()) + continue; + + // we found a historical target, use that first + m_currentVisibleIndex = std::distance(m_targetDatas.begin(), it); + + recalculate(); + + return; + } + + // if we didn't find history, fall back to last + + if (m_currentVisibleIndex >= (int)m_targetDatas.size()) + m_currentVisibleIndex = m_targetDatas.size() - 1; + else if (INDEX <= m_currentVisibleIndex && m_currentVisibleIndex > 0) + m_currentVisibleIndex--; + + recalculate(); +} + +void CMonocleAlgorithm::resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner) { + // monocle layout doesn't support manual resizing, all windows are fullscreen +} + +void CMonocleAlgorithm::recalculate() { + if (m_targetDatas.empty()) + return; + + const auto WORK_AREA = m_parent->space()->workArea(); + + for (size_t i = 0; i < m_targetDatas.size(); ++i) { + const auto& DATA = m_targetDatas[i]; + const auto TARGET = DATA->target.lock(); + + if (!TARGET) + continue; + + const auto WINDOW = TARGET->window(); + if (!WINDOW) + continue; + + DATA->layoutBox = WORK_AREA; + TARGET->setPositionGlobal(WORK_AREA); + + const bool SHOULD_BE_VISIBLE = ((int)i == m_currentVisibleIndex); + WINDOW->setHidden(!SHOULD_BE_VISIBLE); + } +} + +SP CMonocleAlgorithm::getNextCandidate(SP old) { + if (m_targetDatas.empty()) + return nullptr; + + auto it = std::ranges::find_if(m_targetDatas, [old](const auto& data) { return data->target.lock() == old; }); + + if (it == m_targetDatas.end()) { + if (m_currentVisibleIndex >= 0 && m_currentVisibleIndex < (int)m_targetDatas.size()) + return m_targetDatas[m_currentVisibleIndex]->target.lock(); + return nullptr; + } + + auto next = std::next(it); + if (next == m_targetDatas.end()) + next = m_targetDatas.begin(); + + return next->get()->target.lock(); +} + +std::expected CMonocleAlgorithm::layoutMsg(const std::string_view& sv) { + CVarList2 vars(std::string{sv}, 0, 's'); + + if (vars.size() < 1) + return std::unexpected("layoutmsg requires at least 1 argument"); + + const auto COMMAND = vars[0]; + + if (COMMAND == "cyclenext") { + cycleNext(); + return {}; + } else if (COMMAND == "cycleprev") { + cyclePrev(); + return {}; + } + + return std::unexpected(std::format("Unknown monocle layoutmsg: {}", COMMAND)); +} + +std::optional CMonocleAlgorithm::predictSizeForNewTarget() { + const auto WORK_AREA = m_parent->space()->workArea(); + return WORK_AREA.size(); +} + +void CMonocleAlgorithm::swapTargets(SP a, SP b) { + auto nodeA = dataFor(a); + auto nodeB = dataFor(b); + + if (nodeA) + nodeA->target = b; + if (nodeB) + nodeB->target = a; + + recalculate(); +} + +void CMonocleAlgorithm::moveTargetInDirection(SP t, Math::eDirection dir, bool silent) { + static auto PMONITORFALLBACK = CConfigValue("binds:window_direction_monitor_fallback"); + + if (!*PMONITORFALLBACK) + return; // noop + + // try to find a monitor in the specified direction, thats the logical thing + if (!t || !t->space() || !t->space()->workspace()) + return; + + const auto PMONINDIR = g_pCompositor->getMonitorInDirection(t->space()->workspace()->m_monitor.lock(), dir); + + // if we found a monitor, move the window there + if (PMONINDIR && PMONINDIR != t->space()->workspace()->m_monitor.lock()) { + const auto TARGETWS = PMONINDIR->m_activeWorkspace; + + if (t->window()) + t->window()->setAnimationsToMove(); + + t->assignToSpace(TARGETWS->m_space, focalPointForDir(t, dir)); + } +} + +void CMonocleAlgorithm::cycleNext() { + if (m_targetDatas.empty()) + return; + + m_currentVisibleIndex = (m_currentVisibleIndex + 1) % m_targetDatas.size(); + updateVisible(); +} + +void CMonocleAlgorithm::cyclePrev() { + if (m_targetDatas.empty()) + return; + + m_currentVisibleIndex--; + if (m_currentVisibleIndex < 0) + m_currentVisibleIndex = m_targetDatas.size() - 1; + updateVisible(); +} + +void CMonocleAlgorithm::focusTargetUpdate(SP target) { + auto it = std::ranges::find_if(m_targetDatas, [target](const auto& data) { return data->target.lock() == target; }); + + if (it == m_targetDatas.end()) + return; + + const auto NEW_INDEX = std::distance(m_targetDatas.begin(), it); + + if (m_currentVisibleIndex != NEW_INDEX) { + m_currentVisibleIndex = NEW_INDEX; + updateVisible(); + } +} + +void CMonocleAlgorithm::updateVisible() { + recalculate(); + + const auto VISIBLE_TARGET = getVisibleTarget(); + if (!VISIBLE_TARGET) + return; + + const auto WINDOW = VISIBLE_TARGET->window(); + if (!WINDOW) + return; + + Desktop::focusState()->fullWindowFocus(WINDOW, Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE); +} + +SP CMonocleAlgorithm::getVisibleTarget() { + if (m_currentVisibleIndex < 0 || m_currentVisibleIndex >= (int)m_targetDatas.size()) + return nullptr; + + return m_targetDatas[m_currentVisibleIndex]->target.lock(); +} diff --git a/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp b/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp new file mode 100644 index 00000000..b23f85be --- /dev/null +++ b/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include "../../TiledAlgorithm.hpp" +#include "../../../../helpers/signal/Signal.hpp" + +#include + +namespace Layout::Tiled { + + struct SMonocleTargetData { + SMonocleTargetData(SP t) : target(t) { + ; + } + + WP target; + CBox layoutBox; + }; + + class CMonocleAlgorithm : public ITiledAlgorithm { + public: + CMonocleAlgorithm(); + virtual ~CMonocleAlgorithm(); + + virtual void newTarget(SP target); + virtual void movedTarget(SP target, std::optional focalPoint = std::nullopt); + virtual void removeTarget(SP target); + + virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + virtual void recalculate(); + + virtual SP getNextCandidate(SP old); + + virtual std::expected layoutMsg(const std::string_view& sv); + virtual std::optional predictSizeForNewTarget(); + + virtual void swapTargets(SP a, SP b); + virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + private: + std::vector> m_targetDatas; + CHyprSignalListener m_focusCallback; + + int m_currentVisibleIndex = 0; + + SP dataFor(SP t); + void cycleNext(); + void cyclePrev(); + void focusTargetUpdate(SP target); + void updateVisible(); + SP getVisibleTarget(); + }; +}; diff --git a/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp new file mode 100644 index 00000000..93a7dac1 --- /dev/null +++ b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp @@ -0,0 +1,298 @@ +#include "ScrollTapeController.hpp" +#include "ScrollingAlgorithm.hpp" +#include +#include + +using namespace Layout::Tiled; + +CScrollTapeController::CScrollTapeController(eScrollDirection direction) : m_direction(direction) { + ; +} + +void CScrollTapeController::setDirection(eScrollDirection dir) { + m_direction = dir; +} + +eScrollDirection CScrollTapeController::getDirection() const { + return m_direction; +} + +bool CScrollTapeController::isPrimaryHorizontal() const { + return m_direction == SCROLL_DIR_RIGHT || m_direction == SCROLL_DIR_LEFT; +} + +bool CScrollTapeController::isReversed() const { + return m_direction == SCROLL_DIR_LEFT || m_direction == SCROLL_DIR_UP; +} + +size_t CScrollTapeController::stripCount() const { + return m_strips.size(); +} + +SStripData& CScrollTapeController::getStrip(size_t index) { + return m_strips[index]; +} + +const SStripData& CScrollTapeController::getStrip(size_t index) const { + return m_strips[index]; +} + +void CScrollTapeController::setOffset(double offset) { + m_offset = offset; +} + +double CScrollTapeController::getOffset() const { + return m_offset; +} + +void CScrollTapeController::adjustOffset(double delta) { + m_offset += delta; +} + +size_t CScrollTapeController::addStrip(float size) { + m_strips.emplace_back(); + m_strips.back().size = size; + return m_strips.size() - 1; +} + +void CScrollTapeController::insertStrip(ssize_t afterIndex, float size) { + if (afterIndex >= sc(m_strips.size())) { + addStrip(size); + return; + } + + afterIndex = std::clamp(afterIndex, sc(-1L), sc(INT32_MAX)); + + SStripData newStrip; + newStrip.size = size; + m_strips.insert(m_strips.begin() + afterIndex + 1, newStrip); +} + +void CScrollTapeController::removeStrip(size_t index) { + if (index < m_strips.size()) + m_strips.erase(m_strips.begin() + index); +} + +double CScrollTapeController::getPrimary(const Vector2D& v) const { + return isPrimaryHorizontal() ? v.x : v.y; +} + +double CScrollTapeController::getSecondary(const Vector2D& v) const { + return isPrimaryHorizontal() ? v.y : v.x; +} + +void CScrollTapeController::setPrimary(Vector2D& v, double val) const { + if (isPrimaryHorizontal()) + v.x = val; + else + v.y = val; +} + +void CScrollTapeController::setSecondary(Vector2D& v, double val) const { + if (isPrimaryHorizontal()) + v.y = val; + else + v.x = val; +} + +Vector2D CScrollTapeController::makeVector(double primary, double secondary) const { + if (isPrimaryHorizontal()) + return {primary, secondary}; + else + return {secondary, primary}; +} + +double CScrollTapeController::calculateMaxExtent(const CBox& usableArea, bool fullscreenOnOne) const { + if (m_strips.empty()) + return 0.0; + + if (fullscreenOnOne && m_strips.size() == 1) + return getPrimary(usableArea.size()); + + double total = 0.0; + const double usablePrimary = getPrimary(usableArea.size()); + + for (const auto& strip : m_strips) { + total += usablePrimary * strip.size; + } + + return total; +} + +double CScrollTapeController::calculateStripStart(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) const { + if (stripIndex >= m_strips.size()) + return 0.0; + + const double usablePrimary = getPrimary(usableArea.size()); + double current = 0.0; + + for (size_t i = 0; i < stripIndex; ++i) { + const double stripSize = (fullscreenOnOne && m_strips.size() == 1) ? usablePrimary : usablePrimary * m_strips[i].size; + current += stripSize; + } + + return current; +} + +double CScrollTapeController::calculateStripSize(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) const { + if (stripIndex >= m_strips.size()) + return 0.0; + + const double usablePrimary = getPrimary(usableArea.size()); + + if (fullscreenOnOne && m_strips.size() == 1) + return usablePrimary; + + return usablePrimary * m_strips[stripIndex].size; +} + +CBox CScrollTapeController::calculateTargetBox(size_t stripIndex, size_t targetIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne) { + if (stripIndex >= m_strips.size()) + return {}; + + const auto& strip = m_strips[stripIndex]; + if (targetIndex >= strip.targetSizes.size()) + return {}; + + const double usableSecondary = getSecondary(usableArea.size()); + const double usablePrimary = getPrimary(usableArea.size()); + const double cameraOffset = calculateCameraOffset(usableArea, fullscreenOnOne); + + // calculate position along primary axis (strip position) + double primaryPos = calculateStripStart(stripIndex, usableArea, fullscreenOnOne); + double primarySize = calculateStripSize(stripIndex, usableArea, fullscreenOnOne); + + // calculate position along secondary axis (within strip) + double secondaryPos = 0.0; + for (size_t i = 0; i < targetIndex; ++i) { + secondaryPos += strip.targetSizes[i] * usableSecondary; + } + double secondarySize = strip.targetSizes[targetIndex] * usableSecondary; + + // apply camera offset based on direction + // for RIGHT/DOWN: scroll offset moves content left/up (subtract) + // for LEFT/UP: scroll offset moves content right/down (different coordinate system) + if (m_direction == SCROLL_DIR_LEFT) { + // LEFT: flip the entire primary axis, then apply offset + primaryPos = usablePrimary - primaryPos - primarySize + cameraOffset; + } else if (m_direction == SCROLL_DIR_UP) { + // UP: flip the entire primary axis, then apply offset + primaryPos = usablePrimary - primaryPos - primarySize + cameraOffset; + } else { + // RIGHT/DOWN: normal offset + primaryPos -= cameraOffset; + } + + // create the box in primary/secondary coordinates + Vector2D pos = makeVector(primaryPos, secondaryPos); + Vector2D size = makeVector(primarySize, secondarySize); + + // translate to workspace position + pos = pos + workspaceOffset; + + return CBox{pos, size}; +} + +double CScrollTapeController::calculateCameraOffset(const CBox& usableArea, bool fullscreenOnOne) { + const double maxExtent = calculateMaxExtent(usableArea, fullscreenOnOne); + const double usablePrimary = getPrimary(usableArea.size()); + + // don't adjust the offset if we are dragging + if (isBeingDragged()) + return m_offset; + + // if the content fits in viewport, center it + if (maxExtent < usablePrimary) + m_offset = std::round((maxExtent - usablePrimary) / 2.0); + + // if the offset is negative but we already extended, reset offset to 0 + if (maxExtent > usablePrimary && m_offset < 0.0) + m_offset = 0.0; + + return m_offset; +} + +Vector2D CScrollTapeController::getCameraTranslation(const CBox& usableArea, bool fullscreenOnOne) { + const double offset = calculateCameraOffset(usableArea, fullscreenOnOne); + + if (isReversed()) + return makeVector(offset, 0.0); + else + return makeVector(-offset, 0.0); +} + +void CScrollTapeController::centerStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) { + if (stripIndex >= m_strips.size()) + return; + + const double usablePrimary = getPrimary(usableArea.size()); + const double stripStart = calculateStripStart(stripIndex, usableArea, fullscreenOnOne); + const double stripSize = calculateStripSize(stripIndex, usableArea, fullscreenOnOne); + + m_offset = stripStart - (usablePrimary - stripSize) / 2.0; +} + +void CScrollTapeController::fitStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) { + if (stripIndex >= m_strips.size()) + return; + + const double usablePrimary = getPrimary(usableArea.size()); + const double stripStart = calculateStripStart(stripIndex, usableArea, fullscreenOnOne); + const double stripSize = calculateStripSize(stripIndex, usableArea, fullscreenOnOne); + + m_offset = std::clamp(m_offset, stripStart - usablePrimary + stripSize, stripStart); +} + +bool CScrollTapeController::isStripVisible(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne, bool full) const { + if (stripIndex >= m_strips.size()) + return false; + + const double stripStart = calculateStripStart(stripIndex, usableArea, fullscreenOnOne); + const double stripEnd = stripStart + calculateStripSize(stripIndex, usableArea, fullscreenOnOne); + const double viewStart = m_offset; + const double viewEnd = m_offset + getPrimary(usableArea.size()); + + if (!full) + return stripStart < viewEnd && viewStart < stripEnd; + else + return stripStart >= viewStart && stripEnd <= viewEnd; +} + +size_t CScrollTapeController::getStripAtCenter(const CBox& usableArea, bool fullscreenOnOne) const { + if (m_strips.empty()) + return 0; + + const double usablePrimary = getPrimary(usableArea.size()); + double currentPos = m_offset; + + for (size_t i = 0; i < m_strips.size(); ++i) { + const double stripSize = calculateStripSize(i, usableArea, fullscreenOnOne); + currentPos += stripSize; + + if (currentPos >= usablePrimary / 2.0 - 2.0) + return i; + } + + return m_strips.empty() ? 0 : m_strips.size() - 1; +} + +void CScrollTapeController::swapStrips(size_t a, size_t b) { + if (a >= m_strips.size() || b >= m_strips.size()) + return; + + std::swap(m_strips.at(a), m_strips.at(b)); +} + +bool CScrollTapeController::isBeingDragged() const { + for (const auto& s : m_strips) { + if (!s.userData) + continue; + + for (const auto& d : s.userData->targetDatas) { + if (d->target == g_layoutManager->dragController()->target()) + return true; + } + } + + return false; +} diff --git a/src/layout/algorithm/tiled/scrolling/ScrollTapeController.hpp b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.hpp new file mode 100644 index 00000000..da2efbba --- /dev/null +++ b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include "../../../../helpers/math/Math.hpp" +#include "../../../../helpers/memory/Memory.hpp" +#include + +namespace Layout::Tiled { + + struct SColumnData; + + enum eScrollDirection : uint8_t { + SCROLL_DIR_RIGHT = 0, + SCROLL_DIR_LEFT, + SCROLL_DIR_DOWN, + SCROLL_DIR_UP, + }; + + struct SStripData { + float size = 1.F; // size along primary axis + std::vector targetSizes; // sizes along secondary axis for each target in this strip + WP userData; + + SStripData() = default; + }; + + struct STapeLayoutResult { + CBox box; + size_t stripIndex = 0; + size_t targetIndex = 0; + }; + + class CScrollTapeController { + public: + CScrollTapeController(eScrollDirection direction = SCROLL_DIR_RIGHT); + ~CScrollTapeController() = default; + + void setDirection(eScrollDirection dir); + eScrollDirection getDirection() const; + bool isPrimaryHorizontal() const; + bool isReversed() const; + + size_t addStrip(float size = 1.0F); + void insertStrip(ssize_t afterIndex, float size = 1.0F); + void removeStrip(size_t index); + size_t stripCount() const; + SStripData& getStrip(size_t index); + const SStripData& getStrip(size_t index) const; + void swapStrips(size_t a, size_t b); + + void setOffset(double offset); + double getOffset() const; + void adjustOffset(double delta); + + double calculateMaxExtent(const CBox& usableArea, bool fullscreenOnOne = false) const; + double calculateStripStart(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false) const; + double calculateStripSize(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false) const; + + CBox calculateTargetBox(size_t stripIndex, size_t targetIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne = false); + + double calculateCameraOffset(const CBox& usableArea, bool fullscreenOnOne = false); + Vector2D getCameraTranslation(const CBox& usableArea, bool fullscreenOnOne = false); + + void centerStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false); + void fitStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false); + + bool isStripVisible(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false, bool full = false) const; + + size_t getStripAtCenter(const CBox& usableArea, bool fullscreenOnOne = false) const; + + private: + eScrollDirection m_direction = SCROLL_DIR_RIGHT; + std::vector m_strips; + double m_offset = 0.0; + + double getPrimary(const Vector2D& v) const; + double getSecondary(const Vector2D& v) const; + void setPrimary(Vector2D& v, double val) const; + void setSecondary(Vector2D& v, double val) const; + bool isBeingDragged() const; + + Vector2D makeVector(double primary, double secondary) const; + }; +}; diff --git a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp new file mode 100644 index 00000000..ae7c6ecc --- /dev/null +++ b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp @@ -0,0 +1,1518 @@ +#include "ScrollingAlgorithm.hpp" +#include "ScrollTapeController.hpp" + +#include "../../Algorithm.hpp" +#include "../../../space/Space.hpp" +#include "../../../LayoutManager.hpp" + +#include "../../../../Compositor.hpp" +#include "../../../../desktop/state/FocusState.hpp" +#include "../../../../config/ConfigValue.hpp" +#include "../../../../config/ConfigManager.hpp" +#include "../../../../render/Renderer.hpp" +#include "../../../../managers/input/InputManager.hpp" +#include "../../../../event/EventBus.hpp" + +#include +#include +#include + +using namespace Hyprutils::String; +using namespace Hyprutils::Utils; +using namespace Layout; +using namespace Layout::Tiled; + +constexpr float MIN_COLUMN_WIDTH = 0.05F; +constexpr float MAX_COLUMN_WIDTH = 1.F; +constexpr float MIN_ROW_HEIGHT = 0.1F; +constexpr float MAX_ROW_HEIGHT = 1.F; + +// +float SColumnData::getColumnWidth() const { + if (!scrollingData || !scrollingData->controller) + return 1.F; + + auto sd = scrollingData.lock(); + if (!sd) + return 1.F; + + int64_t idx = sd->idx(self.lock()); + if (idx < 0 || (size_t)idx >= sd->controller->stripCount()) + return 1.F; + + return sd->controller->getStrip(idx).size; +} + +void SColumnData::setColumnWidth(float width) { + if (!scrollingData || !scrollingData->controller) + return; + + auto sd = scrollingData.lock(); + if (!sd) + return; + + int64_t idx = sd->idx(self.lock()); + if (idx < 0 || (size_t)idx >= sd->controller->stripCount()) + return; + + sd->controller->getStrip(idx).size = width; +} + +float SColumnData::getTargetSize(size_t idx) const { + if (!scrollingData || !scrollingData->controller) + return 1.F; + + auto sd = scrollingData.lock(); + if (!sd) + return 1.F; + + int64_t colIdx = sd->idx(self.lock()); + if (colIdx < 0 || (size_t)colIdx >= sd->controller->stripCount()) + return 1.F; + + const auto& strip = sd->controller->getStrip(colIdx); + if (idx >= strip.targetSizes.size()) + return 1.F; + + return strip.targetSizes[idx]; +} + +void SColumnData::setTargetSize(size_t idx, float size) { + if (!scrollingData || !scrollingData->controller) + return; + + auto sd = scrollingData.lock(); + if (!sd) + return; + + int64_t colIdx = sd->idx(self.lock()); + if (colIdx < 0 || (size_t)colIdx >= sd->controller->stripCount()) + return; + + auto& strip = sd->controller->getStrip(colIdx); + if (idx >= strip.targetSizes.size()) + strip.targetSizes.resize(idx + 1, 1.F); + + strip.targetSizes[idx] = size; +} + +float SColumnData::getTargetSize(SP target) const { + for (size_t i = 0; i < targetDatas.size(); ++i) { + if (targetDatas[i] == target) + return getTargetSize(i); + } + return 1.F; +} + +void SColumnData::setTargetSize(SP target, float size) { + for (size_t i = 0; i < targetDatas.size(); ++i) { + if (targetDatas[i] == target) { + setTargetSize(i, size); + return; + } + } +} + +void SColumnData::add(SP t) { + const float newSize = 1.F / (float)(targetDatas.size() + 1); + + for (size_t i = 0; i < targetDatas.size(); ++i) { + setTargetSize(i, getTargetSize(i) * (float)targetDatas.size() / (float)(targetDatas.size() + 1)); + } + + targetDatas.emplace_back(makeShared(t, self.lock())); + setTargetSize(targetDatas.size() - 1, newSize); +} + +void SColumnData::add(SP t, int after) { + const float newSize = 1.F / (float)(targetDatas.size() + 1); + + for (size_t i = 0; i < targetDatas.size(); ++i) { + setTargetSize(i, getTargetSize(i) * (float)targetDatas.size() / (float)(targetDatas.size() + 1)); + } + + targetDatas.insert(targetDatas.begin() + after + 1, makeShared(t, self.lock())); + + // Sync sizes - need to insert at the right position + if (scrollingData) { + auto sd = scrollingData.lock(); + if (sd && sd->controller) { + int64_t colIdx = sd->idx(self.lock()); + if (colIdx >= 0 && (size_t)colIdx < sd->controller->stripCount()) { + auto& strip = sd->controller->getStrip(colIdx); + strip.targetSizes.insert(strip.targetSizes.begin() + after + 1, newSize); + } + } + } +} + +void SColumnData::add(SP w) { + const float newSize = 1.F / (float)(targetDatas.size() + 1); + + for (size_t i = 0; i < targetDatas.size(); ++i) { + setTargetSize(i, getTargetSize(i) * (float)targetDatas.size() / (float)(targetDatas.size() + 1)); + } + + targetDatas.emplace_back(w); + w->column = self; + setTargetSize(targetDatas.size() - 1, newSize); +} + +void SColumnData::add(SP w, int after) { + const float newSize = 1.F / (float)(targetDatas.size() + 1); + + for (size_t i = 0; i < targetDatas.size(); ++i) { + setTargetSize(i, getTargetSize(i) * (float)targetDatas.size() / (float)(targetDatas.size() + 1)); + } + + targetDatas.insert(targetDatas.begin() + after + 1, w); + w->column = self; + + // Sync sizes + if (scrollingData) { + auto sd = scrollingData.lock(); + if (sd && sd->controller) { + int64_t colIdx = sd->idx(self.lock()); + if (colIdx >= 0 && (size_t)colIdx < sd->controller->stripCount()) { + auto& strip = sd->controller->getStrip(colIdx); + strip.targetSizes.insert(strip.targetSizes.begin() + after + 1, newSize); + } + } + } +} + +size_t SColumnData::idx(SP t) { + for (size_t i = 0; i < targetDatas.size(); ++i) { + if (targetDatas[i]->target == t) + return i; + } + return 0; +} + +size_t SColumnData::idxForHeight(float y) { + if (targetDatas.empty()) + return 0; + for (size_t i = 0; i < targetDatas.size(); ++i) { + if (targetDatas[i]->target->position().y < y) + continue; + return i == 0 ? 0 : i - 1; + } + return targetDatas.size() - 1; +} + +void SColumnData::remove(SP t) { + const auto SIZE_BEFORE = targetDatas.size(); + size_t removedIdx = 0; + bool found = false; + + for (size_t i = 0; i < targetDatas.size(); ++i) { + if (targetDatas[i]->target == t) { + removedIdx = i; + found = true; + break; + } + } + + std::erase_if(targetDatas, [&t](const auto& e) { return e->target == t; }); + + if (SIZE_BEFORE == targetDatas.size() && SIZE_BEFORE > 0) + return; + + if (found && scrollingData) { + auto sd = scrollingData.lock(); + if (sd && sd->controller) { + int64_t colIdx = sd->idx(self.lock()); + if (colIdx >= 0 && (size_t)colIdx < sd->controller->stripCount()) { + auto& strip = sd->controller->getStrip(colIdx); + if (removedIdx < strip.targetSizes.size()) { + strip.targetSizes.erase(strip.targetSizes.begin() + removedIdx); + } + } + } + } + + // Renormalize sizes + float newMaxSize = 0.F; + for (size_t i = 0; i < targetDatas.size(); ++i) { + newMaxSize += getTargetSize(i); + } + + if (newMaxSize > 0.F) { + for (size_t i = 0; i < targetDatas.size(); ++i) { + setTargetSize(i, getTargetSize(i) / newMaxSize); + } + } + + if (targetDatas.empty() && scrollingData) + scrollingData->remove(self.lock()); +} + +bool SColumnData::up(SP w) { + for (size_t i = 1; i < targetDatas.size(); ++i) { + if (targetDatas[i] != w) + continue; + + std::swap(targetDatas[i], targetDatas[i - 1]); + return true; + } + + return false; +} + +bool SColumnData::down(SP w) { + for (size_t i = 0; i < targetDatas.size() - 1; ++i) { + if (targetDatas[i] != w) + continue; + + std::swap(targetDatas[i], targetDatas[i + 1]); + return true; + } + + return false; +} + +SP SColumnData::next(SP w) { + for (size_t i = 0; i < targetDatas.size() - 1; ++i) { + if (targetDatas[i] != w) + continue; + + return targetDatas[i + 1]; + } + + return nullptr; +} + +SP SColumnData::prev(SP w) { + for (size_t i = 1; i < targetDatas.size(); ++i) { + if (targetDatas[i] != w) + continue; + + return targetDatas[i - 1]; + } + + return nullptr; +} + +bool SColumnData::has(SP t) { + return std::ranges::find_if(targetDatas, [t](const auto& e) { return e->target == t; }) != targetDatas.end(); +} + +SScrollingData::SScrollingData(CScrollingAlgorithm* algo) : algorithm(algo) { + controller = makeUnique(SCROLL_DIR_RIGHT); +} + +SP SScrollingData::add() { + auto col = columns.emplace_back(makeShared(self.lock())); + col->self = col; + + size_t stripIdx = controller->addStrip(algorithm->defaultColumnWidth()); + controller->getStrip(stripIdx).userData = col; + + return col; +} + +SP SScrollingData::add(int after) { + auto col = makeShared(self.lock()); + col->self = col; + columns.insert(columns.begin() + after + 1, col); + + controller->insertStrip(after, algorithm->defaultColumnWidth()); + controller->getStrip(after + 1).userData = col; + + return col; +} + +int64_t SScrollingData::idx(SP c) { + for (size_t i = 0; i < columns.size(); ++i) { + if (columns[i] == c) + return i; + } + + return -1; +} + +void SScrollingData::remove(SP c) { + // find index before removing + int64_t index = idx(c); + + std::erase(columns, c); + + // sync with controller + if (index >= 0) + controller->removeStrip(index); +} + +SP SScrollingData::next(SP c) { + for (size_t i = 0; i < columns.size(); ++i) { + if (columns[i] != c) + continue; + + if (i == columns.size() - 1) + return nullptr; + + return columns[i + 1]; + } + + return nullptr; +} + +SP SScrollingData::prev(SP c) { + for (size_t i = 0; i < columns.size(); ++i) { + if (columns[i] != c) + continue; + + if (i == 0) + return nullptr; + + return columns[i - 1]; + } + + return nullptr; +} + +void SScrollingData::centerCol(SP c) { + if (!c) + return; + + static const auto PFSONONE = CConfigValue("scrolling:fullscreen_on_one_column"); + const auto USABLE = algorithm->usableArea(); + int64_t colIdx = idx(c); + + if (colIdx >= 0) + controller->centerStrip(colIdx, USABLE, *PFSONONE); +} + +void SScrollingData::fitCol(SP c) { + if (!c) + return; + + static const auto PFSONONE = CConfigValue("scrolling:fullscreen_on_one_column"); + const auto USABLE = algorithm->usableArea(); + int64_t colIdx = idx(c); + + if (colIdx >= 0) + controller->fitStrip(colIdx, USABLE, *PFSONONE); +} + +void SScrollingData::centerOrFitCol(SP c) { + if (!c) + return; + + static const auto PFITMETHOD = CConfigValue("scrolling:focus_fit_method"); + + if (*PFITMETHOD == 1) + fitCol(c); + else + centerCol(c); +} + +SP SScrollingData::atCenter() { + static const auto PFSONONE = CConfigValue("scrolling:fullscreen_on_one_column"); + const auto USABLE = algorithm->usableArea(); + + size_t centerIdx = controller->getStripAtCenter(USABLE, *PFSONONE); + + if (centerIdx < columns.size()) + return columns[centerIdx]; + + return nullptr; +} + +void SScrollingData::recalculate(bool forceInstant) { + if (!algorithm->m_parent->space()->workspace() || algorithm->m_parent->space()->workspace()->m_hasFullscreenWindow) + return; + + static const auto PFSONONE = CConfigValue("scrolling:fullscreen_on_one_column"); + + const CBox USABLE = algorithm->usableArea(); + const auto WORKAREA = algorithm->m_parent->space()->workArea(); + + controller->setDirection(algorithm->getDynamicDirection()); + + for (size_t i = 0; i < columns.size(); ++i) { + const auto& COL = columns[i]; + + for (size_t j = 0; j < COL->targetDatas.size(); ++j) { + const auto& TARGET = COL->targetDatas[j]; + + TARGET->layoutBox = controller->calculateTargetBox(i, j, USABLE, WORKAREA.pos(), *PFSONONE); + + if (TARGET->target) + TARGET->target->setPositionGlobal(TARGET->layoutBox); + if (forceInstant && TARGET->target) + TARGET->target->warpPositionSize(); + } + } +} + +double SScrollingData::maxWidth() { + static const auto PFSONONE = CConfigValue("scrolling:fullscreen_on_one_column"); + const auto USABLE = algorithm->usableArea(); + + return controller->calculateMaxExtent(USABLE, *PFSONONE); +} + +bool SScrollingData::visible(SP c, bool full) { + static const auto PFSONONE = CConfigValue("scrolling:fullscreen_on_one_column"); + const auto USABLE = algorithm->usableArea(); + int64_t colIdx = idx(c); + + if (colIdx >= 0) + return controller->isStripVisible(colIdx, USABLE, *PFSONONE, full); + + return false; +} + +CScrollingAlgorithm::CScrollingAlgorithm() { + static const auto PCONFWIDTHS = CConfigValue("scrolling:explicit_column_widths"); + static const auto PCONFDIRECTION = CConfigValue("scrolling:direction"); + + m_scrollingData = makeShared(this); + m_scrollingData->self = m_scrollingData; + + // Helper to parse explicit_column_widths string + auto parseColumnWidths = [](const std::string& dir) -> std::vector { + auto widthVec = std::vector(); + + CConstVarList widths(dir, 0, ','); + for (auto& w : widths) { + try { + widthVec.emplace_back(std::clamp(std::stof(std::string{w}), MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH)); + } catch (...) { Log::logger->log(Log::ERR, "scrolling: Failed to parse width {} as float", w); } + } + if (widthVec.empty()) + widthVec = {0.333, 0.5, 0.667, 1.0}; // default + return widthVec; + }; + + // Helper to parse direction string + auto parseDirection = [](const std::string& dir) -> eScrollDirection { + if (dir == "left") + return SCROLL_DIR_LEFT; + else if (dir == "down") + return SCROLL_DIR_DOWN; + else if (dir == "up") + return SCROLL_DIR_UP; + else + return SCROLL_DIR_RIGHT; // default + }; + + m_configCallback = Event::bus()->m_events.config.reloaded.listen([this, parseColumnWidths, parseDirection] { + static const auto PCONFDIRECTION = CConfigValue("scrolling:direction"); + + m_config.configuredWidths.clear(); + m_config.configuredWidths = parseColumnWidths(*PCONFWIDTHS); + + // Update scroll direction + m_scrollingData->controller->setDirection(parseDirection(*PCONFDIRECTION)); + }); + + m_mouseButtonCallback = Event::bus()->m_events.input.mouse.button.listen([this](IPointer::SButtonEvent e, Event::SCallbackInfo&) { + static const auto PFOLLOW_FOCUS = CConfigValue("scrolling:follow_focus"); + + if (*PFOLLOW_FOCUS && e.state == WL_POINTER_BUTTON_STATE_RELEASED && Desktop::focusState()->window()) + focusOnInput(Desktop::focusState()->window()->layoutTarget(), INPUT_MODE_CLICK); + }); + + m_focusCallback = Event::bus()->m_events.window.active.listen([this](PHLWINDOW pWindow, Desktop::eFocusReason reason) { + if (!pWindow) + return; + + static const auto PFOLLOW_FOCUS = CConfigValue("scrolling:follow_focus"); + + if (!*PFOLLOW_FOCUS && !Desktop::isHardInputFocusReason(reason)) + return; + + if (pWindow->m_workspace != m_parent->space()->workspace()) + return; + + const auto TARGET = pWindow->layoutTarget(); + if (!TARGET || TARGET->floating()) + return; + + focusOnInput(TARGET, reason == Desktop::FOCUS_REASON_CLICK ? INPUT_MODE_CLICK : (Desktop::isHardInputFocusReason(reason) ? INPUT_MODE_KB : INPUT_MODE_SOFT)); + }); + + // Initialize default widths and direction + m_config.configuredWidths = parseColumnWidths(*PCONFWIDTHS); + m_scrollingData->controller->setDirection(parseDirection(*PCONFDIRECTION)); +} + +CScrollingAlgorithm::~CScrollingAlgorithm() { + m_configCallback.reset(); + m_focusCallback.reset(); +} + +void CScrollingAlgorithm::focusOnInput(SP target, eInputMode input) { + static const auto PFOLLOW_FOCUS_MIN_PERC = CConfigValue("scrolling:follow_min_visible"); + + if (!target || target->space() != m_parent->space()) + return; + + const auto TARGETDATA = dataFor(target); + if (!TARGETDATA) + return; + + if (*PFOLLOW_FOCUS_MIN_PERC > 0.F && input == INPUT_MODE_SOFT) { + // check how much of the window is visible, unless hard input focus + + const auto IS_HORIZ = m_scrollingData->controller->isPrimaryHorizontal(); + + const auto MON_BOX = m_parent->space()->workspace()->m_monitor->logicalBox(); + const auto TARGET_POS = target->position(); + const double VISIBLE_LEN = IS_HORIZ ? // + std::abs(std::min(MON_BOX.x + MON_BOX.w, TARGET_POS.x + TARGET_POS.w) - (std::max(MON_BOX.x, TARGET_POS.x))) // + : + std::abs(std::min(MON_BOX.y + MON_BOX.h, TARGET_POS.y + TARGET_POS.h) - (std::max(MON_BOX.y, TARGET_POS.y))); + + // if the amount of visible X is below minimum, reject + if (VISIBLE_LEN < (IS_HORIZ ? MON_BOX.w : MON_BOX.h) * std::clamp(*PFOLLOW_FOCUS_MIN_PERC, 0.F, 1.F)) + return; + } + + // if we moved via non-kb, and it's fully visible, ignore + if (m_scrollingData->visible(TARGETDATA->column.lock(), true) && input != INPUT_MODE_KB) + return; + + static const auto PFITMETHOD = CConfigValue("scrolling:focus_fit_method"); + if (*PFITMETHOD == 1 || input == INPUT_MODE_CLICK) + m_scrollingData->fitCol(TARGETDATA->column.lock()); + else + m_scrollingData->centerCol(TARGETDATA->column.lock()); + m_scrollingData->recalculate(); +} + +void CScrollingAlgorithm::newTarget(SP target) { + auto droppingOn = Desktop::focusState()->window(); + + if (droppingOn && droppingOn->layoutTarget() == target) + droppingOn = g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS); + + SP droppingData = droppingOn ? dataFor(droppingOn->layoutTarget()) : nullptr; + SP droppingColumn = droppingData ? droppingData->column.lock() : nullptr; + + if (!droppingColumn) { + auto col = m_scrollingData->add(); + col->add(target); + m_scrollingData->fitCol(col); + } else { + if (g_layoutManager->dragController()->wasDraggingWindow() && g_layoutManager->dragController()->draggingTiled()) { + if (droppingOn) { + const auto IDX = droppingColumn->idx(droppingOn->layoutTarget()); + const auto TOP = droppingOn->getWindowIdealBoundingBoxIgnoreReserved().middle().y > g_pInputManager->getMouseCoordsInternal().y; + droppingColumn->add(target, TOP ? (IDX == 0 ? -1 : IDX - 1) : (IDX)); + } else + droppingColumn->add(target); + m_scrollingData->fitCol(droppingColumn); + } else { + auto idx = m_scrollingData->idx(droppingColumn); + auto col = idx == -1 ? m_scrollingData->add() : m_scrollingData->add(idx); + col->add(target); + m_scrollingData->fitCol(col); + } + } + + m_scrollingData->recalculate(); +} + +void CScrollingAlgorithm::movedTarget(SP target, std::optional focalPoint) { + newTarget(target); +} + +void CScrollingAlgorithm::removeTarget(SP target) { + const auto DATA = dataFor(target); + + if (!DATA) + return; + + if (!m_scrollingData->next(DATA->column.lock()) && DATA->column->targetDatas.size() <= 1) { + // move the view if this is the last column + const auto USABLE = usableArea(); + const bool isPrimaryHoriz = m_scrollingData->controller->isPrimaryHorizontal(); + const double usablePrimary = isPrimaryHoriz ? USABLE.w : USABLE.h; + m_scrollingData->controller->adjustOffset(-(usablePrimary * DATA->column->getColumnWidth())); + } + + DATA->column->remove(target); + + if (!DATA->column) { + // column got removed, let's ensure we don't leave any cringe extra space + const auto USABLE = usableArea(); + const bool isPrimaryHoriz = m_scrollingData->controller->isPrimaryHorizontal(); + const double usablePrimary = isPrimaryHoriz ? USABLE.w : USABLE.h; + const double newOffset = std::clamp(m_scrollingData->controller->getOffset(), 0.0, std::max(m_scrollingData->maxWidth() - usablePrimary, 1.0)); + m_scrollingData->controller->setOffset(newOffset); + } + + m_scrollingData->recalculate(); +} + +void CScrollingAlgorithm::resizeTarget(const Vector2D& delta, SP target, eRectCorner corner) { + if (!validMapped(target->window())) + return; + + const auto DATA = dataFor(target); + + if (!DATA) { + const auto PWINDOW = target->window(); + *PWINDOW->m_realSize = (PWINDOW->m_realSize->goal() + delta) + .clamp(PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}), + PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY})); + PWINDOW->updateWindowDecos(); + return; + } + + if (!DATA->column || !DATA->column->scrollingData) + return; + + static const auto PFSONONE = CConfigValue("scrolling:fullscreen_on_one_column"); + + const auto ADJUSTED_DELTA = m_scrollingData->controller->isPrimaryHorizontal() ? delta : Vector2D{delta.y, delta.x}; + const auto USABLE = usableArea(); + const auto DELTA_AS_PERC = ADJUSTED_DELTA / USABLE.size(); + Vector2D modDelta = ADJUSTED_DELTA; + + const auto CURR_COLUMN = DATA->column.lock(); + const int64_t COL_IDX = m_scrollingData->idx(CURR_COLUMN); + + if (COL_IDX < 0) + return; + + const double currentStart = m_scrollingData->controller->calculateStripStart(COL_IDX, USABLE, *PFSONONE); + const double currentSize = m_scrollingData->controller->calculateStripSize(COL_IDX, USABLE, *PFSONONE); + const double currentEnd = currentStart + currentSize; + + const double cameraOffset = m_scrollingData->controller->getOffset(); + const bool isPrimaryHoriz = m_scrollingData->controller->isPrimaryHorizontal(); + const double usablePrimary = isPrimaryHoriz ? USABLE.w : USABLE.h; + + const double onScreenStart = currentStart - cameraOffset; + const double onScreenEnd = currentEnd - cameraOffset; + + // set the offset because we'll prevent centering during a drag + m_scrollingData->controller->setOffset(cameraOffset); + + const bool RESIZING_LEFT = isPrimaryHoriz ? corner == CORNER_BOTTOMLEFT || corner == CORNER_TOPLEFT : corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT; + + if (RESIZING_LEFT) { + // resize from left edge (inner edge) - grow/shrink column width and adjust offset to keep RIGHT edge stationary + const float oldWidth = CURR_COLUMN->getColumnWidth(); + const float requestedDelta = -(float)DELTA_AS_PERC.x; // negative delta means grow when dragging left + float actualDelta = requestedDelta; + + // clamp delta so we don't shrink below MIN or grow above MAX + const float newWidthUnclamped = oldWidth + actualDelta; + const float newWidthClamped = std::clamp(newWidthUnclamped, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH); + actualDelta = newWidthClamped - oldWidth; + + if (actualDelta * usablePrimary > onScreenStart) + actualDelta = onScreenStart / usablePrimary; + + if (actualDelta != 0.F) { + CURR_COLUMN->setColumnWidth(oldWidth + actualDelta); + // adjust camera offset so the RIGHT edge stays stationary on screen + // when column grows (actualDelta > 0), we need to increase offset by the same amount + m_scrollingData->controller->adjustOffset(actualDelta * usablePrimary); + } + + } else { + // resize from right edge (outer edge) - adjust column width only, keep left edge fixed + const float oldWidth = CURR_COLUMN->getColumnWidth(); + const float requestedDelta = (float)DELTA_AS_PERC.x; + float actualDelta = requestedDelta; + + // clamp delta so we don't shrink below MIN or grow above MAX + const float newWidthUnclamped = oldWidth + actualDelta; + const float newWidthClamped = std::clamp(newWidthUnclamped, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH); + actualDelta = newWidthClamped - oldWidth; + + // also clamp so right edge doesn't go past right viewport boundary + if (onScreenEnd + (actualDelta * usablePrimary) > usablePrimary) + actualDelta = (usablePrimary - onScreenEnd) / usablePrimary; + + if (actualDelta != 0.F) + CURR_COLUMN->setColumnWidth(oldWidth + actualDelta); + } + + if (DATA->column->targetDatas.size() > 1) { + const auto& CURR_TD = DATA; + const auto NEXT_TD = DATA->column->next(DATA); + const auto PREV_TD = DATA->column->prev(DATA); + if (corner == CORNER_NONE) { + if (!PREV_TD) + corner = CORNER_BOTTOMRIGHT; + else { + corner = CORNER_TOPRIGHT; + modDelta.y *= -1.0f; + } + } + + switch (corner) { + case CORNER_BOTTOMLEFT: + case CORNER_BOTTOMRIGHT: { + if (!NEXT_TD) + break; + + float nextSize = CURR_COLUMN->getTargetSize(NEXT_TD); + float currSize = CURR_COLUMN->getTargetSize(CURR_TD); + + if (nextSize <= MIN_ROW_HEIGHT && delta.y >= 0) + break; + + float adjust = std::clamp((float)(delta.y / USABLE.h), (-currSize + MIN_ROW_HEIGHT), (nextSize - MIN_ROW_HEIGHT)); + + CURR_COLUMN->setTargetSize(NEXT_TD, std::clamp(nextSize - adjust, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT)); + CURR_COLUMN->setTargetSize(CURR_TD, std::clamp(currSize + adjust, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT)); + break; + } + case CORNER_TOPLEFT: + case CORNER_TOPRIGHT: { + if (!PREV_TD) + break; + + float prevSize = CURR_COLUMN->getTargetSize(PREV_TD); + float currSize = CURR_COLUMN->getTargetSize(CURR_TD); + + if ((prevSize <= MIN_ROW_HEIGHT && modDelta.y <= 0) || (currSize <= MIN_ROW_HEIGHT && delta.y >= 0)) + break; + + float adjust = std::clamp((float)(modDelta.y / USABLE.h), -(prevSize - MIN_ROW_HEIGHT), (currSize - MIN_ROW_HEIGHT)); + + CURR_COLUMN->setTargetSize(PREV_TD, std::clamp(prevSize + adjust, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT)); + CURR_COLUMN->setTargetSize(CURR_TD, std::clamp(currSize - adjust, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT)); + break; + } + + default: break; + } + } + + m_scrollingData->recalculate(true); +} + +void CScrollingAlgorithm::recalculate() { + if (Desktop::focusState()->window()) { + const auto TARGET = Desktop::focusState()->window()->layoutTarget(); + + const auto TARGETDATA = dataFor(TARGET); + + if (TARGETDATA && !m_scrollingData->visible(TARGETDATA->column.lock(), true)) + focusOnInput(Desktop::focusState()->window()->layoutTarget(), INPUT_MODE_KB); + } + + m_scrollingData->recalculate(); +} + +SP CScrollingAlgorithm::closestNode(const Vector2D& posGlobglobgabgalab) { + SP res = nullptr; + double distClosest = -1; + for (auto& c : m_scrollingData->columns) { + for (auto& n : c->targetDatas) { + if (n->target && Desktop::View::validMapped(n->target->window())) { + auto distAnother = vecToRectDistanceSquared(posGlobglobgabgalab, n->layoutBox.pos(), n->layoutBox.pos() + n->layoutBox.size()); + if (!res || distAnother < distClosest) { + res = n; + distClosest = distAnother; + } + } + } + } + return res; +} + +SP CScrollingAlgorithm::getNextCandidate(SP old) { + const auto CENTER = old->position().middle(); + + const auto NODE = closestNode(CENTER); + + if (!NODE) + return nullptr; + + return NODE->target.lock(); +} + +void CScrollingAlgorithm::swapTargets(SP a, SP b) { + auto nodeA = dataFor(a); + auto nodeB = dataFor(b); + + if (nodeA) + nodeA->target = b; + if (nodeB) + nodeB->target = a; + + m_scrollingData->recalculate(); +} + +void CScrollingAlgorithm::moveTargetInDirection(SP t, Math::eDirection dir, bool silent) { + moveTargetTo(t, dir, silent); +} + +void CScrollingAlgorithm::moveTargetTo(SP t, Math::eDirection dir, bool silent) { + static auto PMONITORFALLBACK = CConfigValue("binds:window_direction_monitor_fallback"); + + const auto DATA = dataFor(t); + + if (!DATA) + return; + + const auto CURRENT_COL = DATA->column.lock(); + const auto current_idx = m_scrollingData->idx(CURRENT_COL); + + auto rotateDir = [this](Math::eDirection dir) -> Math::eDirection { + switch (m_scrollingData->controller->getDirection()) { + case SCROLL_DIR_RIGHT: return dir; + case SCROLL_DIR_LEFT: { + if (dir == Math::DIRECTION_LEFT) + return Math::DIRECTION_RIGHT; + if (dir == Math::DIRECTION_RIGHT) + return Math::DIRECTION_LEFT; + return dir; + } + case SCROLL_DIR_UP: { + switch (dir) { + case Math::DIRECTION_UP: return Math::DIRECTION_RIGHT; + case Math::DIRECTION_DOWN: return Math::DIRECTION_LEFT; + case Math::DIRECTION_LEFT: return Math::DIRECTION_DOWN; + case Math::DIRECTION_RIGHT: return Math::DIRECTION_UP; + default: break; + } + + return dir; + } + case SCROLL_DIR_DOWN: { + switch (dir) { + case Math::DIRECTION_UP: return Math::DIRECTION_LEFT; + case Math::DIRECTION_DOWN: return Math::DIRECTION_RIGHT; + case Math::DIRECTION_LEFT: return Math::DIRECTION_DOWN; + case Math::DIRECTION_RIGHT: return Math::DIRECTION_UP; + default: break; + } + + return dir; + } + default: break; + } + + return dir; + }; + + const auto ROTATED_DIR = rotateDir(dir); + + auto commenceDir = [&]() -> bool { + if (ROTATED_DIR == Math::DIRECTION_LEFT) { + const auto COL = m_scrollingData->prev(DATA->column.lock()); + + // ignore moves to the origin if we are alone + if (!COL && current_idx == 0 && DATA->column->targetDatas.size() == 1) + return false; + + DATA->column->remove(t); + + if (!COL) { + const auto NEWCOL = m_scrollingData->add(-1); + NEWCOL->add(DATA); + m_scrollingData->centerOrFitCol(NEWCOL); + } else { + if (COL->targetDatas.size() > 0) + COL->add(DATA, COL->idxForHeight(g_pInputManager->getMouseCoordsInternal().y)); + else + COL->add(DATA); + m_scrollingData->centerOrFitCol(COL); + } + + return true; + } else if (ROTATED_DIR == Math::DIRECTION_RIGHT) { + const auto COL = m_scrollingData->next(DATA->column.lock()); + + // ignore move to the right when there is no next column and we're alone + if (!COL && current_idx == (int64_t)m_scrollingData->columns.size() - 1 && DATA->column->targetDatas.size() == 1) + return false; + + DATA->column->remove(t); + + if (!COL) { + // make a new one + const auto NEWCOL = m_scrollingData->add(); + NEWCOL->add(DATA); + m_scrollingData->centerOrFitCol(NEWCOL); + } else { + if (COL->targetDatas.size() > 0) + COL->add(DATA, COL->idxForHeight(g_pInputManager->getMouseCoordsInternal().y)); + else + COL->add(DATA); + m_scrollingData->centerOrFitCol(COL); + } + + return true; + } else if (ROTATED_DIR == Math::DIRECTION_UP) + return DATA->column->up(DATA); + else if (ROTATED_DIR == Math::DIRECTION_DOWN) + return DATA->column->down(DATA); + + return false; + }; + + if (!commenceDir()) { + // dir wasn't commenced, move to a workspace if possible + // with the original dir + + if (!*PMONITORFALLBACK) + return; // noop + + const auto MONINDIR = g_pCompositor->getMonitorInDirection(m_parent->space()->workspace()->m_monitor.lock(), dir); + if (MONINDIR && MONINDIR != m_parent->space()->workspace()->m_monitor && MONINDIR->m_activeWorkspace) { + t->assignToSpace(MONINDIR->m_activeWorkspace->m_space, focalPointForDir(t, dir)); + + m_scrollingData->recalculate(); + + return; + } + } + + m_scrollingData->recalculate(); + focusTargetUpdate(t); +} + +std::expected CScrollingAlgorithm::layoutMsg(const std::string_view& sv) { + auto centerOrFit = [this](const SP COL) -> void { + static const auto PFITMETHOD = CConfigValue("scrolling:focus_fit_method"); + if (*PFITMETHOD == 1) + m_scrollingData->fitCol(COL); + else + m_scrollingData->centerCol(COL); + }; + + const auto ARGS = CVarList(std::string{sv}, 0, ' '); + if (ARGS[0] == "move") { + if (ARGS[1] == "+col" || ARGS[1] == "col") { + const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr); + if (!TDATA) + return std::unexpected("no window"); + + const auto COL = m_scrollingData->next(TDATA->column.lock()); + if (!COL) { + // move to max + double maxOffset = m_scrollingData->maxWidth(); + m_scrollingData->controller->setOffset(maxOffset); + m_scrollingData->recalculate(); + focusTargetUpdate(nullptr); + return {}; + } + + centerOrFit(COL); + m_scrollingData->recalculate(); + + focusTargetUpdate(COL->targetDatas.front()->target.lock()); + if (COL->targetDatas.front()->target->window()) + g_pCompositor->warpCursorTo(COL->targetDatas.front()->target->window()->middle()); + + return {}; + } else if (ARGS[1] == "-col") { + const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr); + if (!TDATA) { + if (m_scrollingData->columns.size() > 0) { + m_scrollingData->centerCol(m_scrollingData->columns.back()); + m_scrollingData->recalculate(); + focusTargetUpdate((m_scrollingData->columns.back()->targetDatas.back())->target.lock()); + if (m_scrollingData->columns.back()->targetDatas.back()->target->window()) + g_pCompositor->warpCursorTo((m_scrollingData->columns.back()->targetDatas.back())->target->window()->middle()); + } + + return {}; + } + + const auto COL = m_scrollingData->prev(TDATA->column.lock()); + if (!COL) + return {}; + + centerOrFit(COL); + m_scrollingData->recalculate(); + + focusTargetUpdate(COL->targetDatas.back()->target.lock()); + if (COL->targetDatas.front()->target->window()) + g_pCompositor->warpCursorTo(COL->targetDatas.front()->target->window()->middle()); + + return {}; + } + + const auto PLUSMINUS = getPlusMinusKeywordResult(ARGS[1], 0); + + if (!PLUSMINUS.has_value()) + return std::unexpected("failed to parse offset"); + + m_scrollingData->controller->adjustOffset(-(*PLUSMINUS)); + m_scrollingData->recalculate(); + + const auto ATCENTER = m_scrollingData->atCenter(); + + focusTargetUpdate(ATCENTER ? (*ATCENTER->targetDatas.begin())->target.lock() : nullptr); + } else if (ARGS[0] == "colresize") { + const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr); + + if (!TDATA) + return {}; + + if (ARGS[1] == "all") { + float abs = 0; + try { + abs = std::stof(ARGS[2]); + } catch (...) { return {}; } + + for (const auto& c : m_scrollingData->columns) { + c->setColumnWidth(abs); + } + + m_scrollingData->recalculate(); + return {}; + } + + CScopeGuard x([this, TDATA] { + auto col = TDATA->column.lock(); + if (col) { + col->setColumnWidth(std::clamp(col->getColumnWidth(), MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH)); + m_scrollingData->centerOrFitCol(col); + } + m_scrollingData->recalculate(); + }); + + if (ARGS[1][0] == '+' || ARGS[1][0] == '-') { + if (ARGS[1] == "+conf") { + auto col = TDATA->column.lock(); + if (col) { + for (size_t i = 0; i < m_config.configuredWidths.size(); ++i) { + if (m_config.configuredWidths[i] > col->getColumnWidth()) { + col->setColumnWidth(m_config.configuredWidths[i]); + break; + } + + if (i == m_config.configuredWidths.size() - 1) + col->setColumnWidth(m_config.configuredWidths[0]); + } + } + + return {}; + } else if (ARGS[1] == "-conf") { + auto col = TDATA->column.lock(); + if (col) { + for (size_t i = m_config.configuredWidths.size() - 1;; --i) { + if (m_config.configuredWidths[i] < col->getColumnWidth()) { + col->setColumnWidth(m_config.configuredWidths[i]); + break; + } + + if (i == 0) { + col->setColumnWidth(m_config.configuredWidths.back()); + break; + } + } + } + + return {}; + } + + const auto PLUSMINUS = getPlusMinusKeywordResult(ARGS[1], 0); + + if (!PLUSMINUS.has_value()) + return {}; + + auto col = TDATA->column.lock(); + if (col) + col->setColumnWidth(col->getColumnWidth() + *PLUSMINUS); + } else { + float abs = 0; + try { + abs = std::stof(ARGS[1]); + } catch (...) { return {}; } + + auto col = TDATA->column.lock(); + if (col) + col->setColumnWidth(abs); + } + } else if (ARGS[0] == "fit") { + const auto PWINDOW = Desktop::focusState()->window(); + + if (!PWINDOW) + return std::unexpected("no focused window"); + + const auto WDATA = dataFor(PWINDOW->layoutTarget()); + + if (!WDATA || m_scrollingData->columns.size() == 0) + return std::unexpected("can't fit: no window or columns"); + + if (ARGS[1] == "active") { + // fit the current column to 1.F + const auto USABLE = usableArea(); + + WDATA->column->setColumnWidth(1.F); + + double off = 0.F; + for (size_t i = 0; i < m_scrollingData->columns.size(); ++i) { + if (m_scrollingData->columns[i]->has(PWINDOW->layoutTarget())) + break; + + off += USABLE.w * m_scrollingData->columns[i]->getColumnWidth(); + } + + m_scrollingData->controller->setOffset(off); + m_scrollingData->recalculate(); + } else if (ARGS[1] == "all") { + // fit all columns on screen + const size_t LEN = m_scrollingData->columns.size(); + for (const auto& c : m_scrollingData->columns) { + c->setColumnWidth(1.F / (float)LEN); + } + + m_scrollingData->controller->setOffset(0); + m_scrollingData->recalculate(); + } else if (ARGS[1] == "toend") { + // fit all columns on screen that start from the current and end on the last + bool begun = false; + size_t foundAt = 0; + for (size_t i = 0; i < m_scrollingData->columns.size(); ++i) { + if (!begun && !m_scrollingData->columns[i]->has(PWINDOW->layoutTarget())) + continue; + + if (!begun) { + begun = true; + foundAt = i; + } + + m_scrollingData->columns[i]->setColumnWidth(1.F / (float)(m_scrollingData->columns.size() - foundAt)); + } + + if (!begun) + return std::unexpected("couldn't find beginning"); + + const auto USABLE = usableArea(); + + double off = 0; + for (size_t i = 0; i < foundAt; ++i) { + off += USABLE.w * m_scrollingData->columns[i]->getColumnWidth(); + } + + m_scrollingData->controller->setOffset(off); + m_scrollingData->recalculate(); + } else if (ARGS[1] == "tobeg") { + // fit all columns on screen that start from the current and end on the last + bool begun = false; + size_t foundAt = 0; + for (int64_t i = (int64_t)m_scrollingData->columns.size() - 1; i >= 0; --i) { + if (!begun && !m_scrollingData->columns[i]->has(PWINDOW->layoutTarget())) + continue; + + if (!begun) { + begun = true; + foundAt = i; + } + + m_scrollingData->columns[i]->setColumnWidth(1.F / (float)(foundAt + 1)); + } + + if (!begun) + return {}; + + m_scrollingData->controller->setOffset(0); + m_scrollingData->recalculate(); + } else if (ARGS[1] == "visible") { + // fit all columns on screen that start from the current and end on the last + + bool begun = false; + size_t foundAt = 0; + std::vector> visible; + for (size_t i = 0; i < m_scrollingData->columns.size(); ++i) { + if (!begun && !m_scrollingData->visible(m_scrollingData->columns[i])) + continue; + + if (!begun) { + begun = true; + foundAt = i; + } + + if (!m_scrollingData->visible(m_scrollingData->columns[i])) + break; + + visible.emplace_back(m_scrollingData->columns[i]); + } + + if (!begun) + return {}; + + double off = 0; + + if (foundAt != 0) { + const auto USABLE = usableArea(); + + for (size_t i = 0; i < foundAt; ++i) { + off += USABLE.w * m_scrollingData->columns[i]->getColumnWidth(); + } + } + + for (const auto& v : visible) { + v->setColumnWidth(1.F / (float)visible.size()); + } + + m_scrollingData->controller->setOffset(off); + m_scrollingData->recalculate(); + } + } else if (ARGS[0] == "focus") { + const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr); + static const auto PNOFALLBACK = CConfigValue("general:no_focus_fallback"); + static const auto PCONFWRAPFOCUS = CConfigValue("scrolling:wrap_focus"); + + if (!TDATA || ARGS[1].empty()) + return std::unexpected("no window to focus"); + + // Determine if we're in vertical scroll mode (strips are horizontal) + const bool isVerticalScroll = (getDynamicDirection() == SCROLL_DIR_DOWN || getDynamicDirection() == SCROLL_DIR_UP); + + // Map direction keys based on scroll mode: + // Horizontal scroll (RIGHT/LEFT): u/d move within strip, l/r move between strips + // Vertical scroll (DOWN/UP): l/r move within strip, u/d move between strips + char dirChar = ARGS[1][0]; + + // Convert to semantic directions + bool isPrevInStrip = (!isVerticalScroll && (dirChar == 'u' || dirChar == 't')) || (isVerticalScroll && dirChar == 'l'); + bool isNextInStrip = (!isVerticalScroll && (dirChar == 'b' || dirChar == 'd')) || (isVerticalScroll && dirChar == 'r'); + bool isPrevStrip = (!isVerticalScroll && dirChar == 'l') || (isVerticalScroll && (dirChar == 'u' || dirChar == 't')); + bool isNextStrip = (!isVerticalScroll && dirChar == 'r') || (isVerticalScroll && (dirChar == 'b' || dirChar == 'd')); + + if (isPrevInStrip) { + // Move to previous target within current strip + auto PREV = TDATA->column->prev(TDATA); + if (!PREV) { + if (!*PNOFALLBACK) + PREV = TDATA->column->targetDatas.back(); + else + return std::unexpected("fallback disabled (no target)"); + } + + focusTargetUpdate(PREV->target.lock()); + if (PREV->target->window()) + g_pCompositor->warpCursorTo(PREV->target->window()->middle()); + } else if (isNextInStrip) { + // Move to next target within current strip + auto NEXT = TDATA->column->next(TDATA); + if (!NEXT) { + if (!*PNOFALLBACK) + NEXT = TDATA->column->targetDatas.front(); + else + return std::unexpected("fallback disabled (no target)"); + } + + focusTargetUpdate(NEXT->target.lock()); + if (NEXT->target->window()) + g_pCompositor->warpCursorTo(NEXT->target->window()->middle()); + } else if (isPrevStrip) { + // Move to previous strip + auto PREV = m_scrollingData->prev(TDATA->column.lock()); + if (!PREV) { + if (*PNOFALLBACK) { + centerOrFit(TDATA->column.lock()); + m_scrollingData->recalculate(); + if (TDATA->target->window()) + g_pCompositor->warpCursorTo(TDATA->target->window()->middle()); + return {}; + } else + PREV = (*PCONFWRAPFOCUS == 1) ? m_scrollingData->columns.back() : m_scrollingData->columns.front(); + } + + auto pTargetData = findBestNeighbor(TDATA, PREV); + if (pTargetData) { + focusTargetUpdate(pTargetData->target.lock()); + centerOrFit(PREV); + m_scrollingData->recalculate(); + if (pTargetData->target->window()) + g_pCompositor->warpCursorTo(pTargetData->target->window()->middle()); + } + } else if (isNextStrip) { + // Move to next strip + auto NEXT = m_scrollingData->next(TDATA->column.lock()); + if (!NEXT) { + if (*PNOFALLBACK) { + centerOrFit(TDATA->column.lock()); + m_scrollingData->recalculate(); + if (TDATA->target->window()) + g_pCompositor->warpCursorTo(TDATA->target->window()->middle()); + return {}; + } else + NEXT = (*PCONFWRAPFOCUS == 1) ? m_scrollingData->columns.front() : m_scrollingData->columns.back(); + } + + auto pTargetData = findBestNeighbor(TDATA, NEXT); + if (pTargetData) { + focusTargetUpdate(pTargetData->target.lock()); + centerOrFit(NEXT); + m_scrollingData->recalculate(); + if (pTargetData->target->window()) + g_pCompositor->warpCursorTo(pTargetData->target->window()->middle()); + } + } + } else if (ARGS[0] == "promote") { + const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr); + + if (!TDATA) + return std::unexpected("no window focused"); + + auto idx = m_scrollingData->idx(TDATA->column.lock()); + auto col = idx == -1 ? m_scrollingData->add() : m_scrollingData->add(idx); + + TDATA->column->remove(TDATA->target.lock()); + + col->add(TDATA); + + m_scrollingData->recalculate(); + } else if (ARGS[0] == "swapcol") { + static const auto PCONFWRAPSWAPCOL = CConfigValue("scrolling:wrap_swapcol"); + + if (ARGS.size() < 2) + return std::unexpected("not enough args"); + + const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr); + if (!TDATA) + return std::unexpected("no window"); + + const auto CURRENT_COL = TDATA->column.lock(); + if (!CURRENT_COL) + return std::unexpected("no current col"); + + if (m_scrollingData->columns.size() < 2) + return std::unexpected("not enough columns to swap"); + + const int64_t currentIdx = m_scrollingData->idx(CURRENT_COL); + const size_t colCount = m_scrollingData->columns.size(); + + if (currentIdx == -1) + return std::unexpected("no current column"); + + const std::string& direction = ARGS[1]; + int64_t targetIdx = -1; + + // wrap around swaps + if (direction == "l") + if (*PCONFWRAPSWAPCOL == 1) + targetIdx = (currentIdx == 0) ? (colCount - 1) : (currentIdx - 1); + else + targetIdx = (currentIdx == 0) ? 0 : (currentIdx - 1); + else if (direction == "r") + if (*PCONFWRAPSWAPCOL == 1) + targetIdx = (currentIdx == (int64_t)colCount - 1) ? 0 : (currentIdx + 1); + else + targetIdx = (currentIdx == (int64_t)colCount - 1) ? (colCount - 1) : (currentIdx + 1); + else + return std::unexpected("no target (invalid direction?)"); + ; + + std::swap(m_scrollingData->columns.at(currentIdx), m_scrollingData->columns.at(targetIdx)); + + m_scrollingData->controller->swapStrips(currentIdx, targetIdx); + + m_scrollingData->centerOrFitCol(CURRENT_COL); + m_scrollingData->recalculate(); + } else + return std::unexpected("no such layoutmsg for scrolling"); + + return {}; +} + +std::optional CScrollingAlgorithm::predictSizeForNewTarget() { + return std::nullopt; +} + +void CScrollingAlgorithm::focusTargetUpdate(SP target) { + if (!target || !validMapped(target->window())) { + Desktop::focusState()->fullWindowFocus(nullptr, Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE); + return; + } + Desktop::focusState()->fullWindowFocus(target->window(), Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE); + const auto TARGETDATA = dataFor(target); + if (TARGETDATA) { + if (auto col = TARGETDATA->column.lock()) + col->lastFocusedTarget = TARGETDATA; + } +} + +SP CScrollingAlgorithm::findBestNeighbor(SP pCurrent, SP pTargetCol) { + if (!pCurrent || !pTargetCol || pTargetCol->targetDatas.empty()) + return nullptr; + + const double currentTop = pCurrent->layoutBox.y; + const double currentBottom = pCurrent->layoutBox.y + pCurrent->layoutBox.h; + std::vector> overlappingTargets; + for (const auto& candidate : pTargetCol->targetDatas) { + const double candidateTop = candidate->layoutBox.y; + const double candidateBottom = candidate->layoutBox.y + candidate->layoutBox.h; + const bool overlaps = (candidateTop < currentBottom) && (candidateBottom > currentTop); + + if (overlaps) + overlappingTargets.emplace_back(candidate); + } + if (!overlappingTargets.empty()) { + auto lastFocused = pTargetCol->lastFocusedTarget.lock(); + + if (lastFocused) { + auto it = std::ranges::find(overlappingTargets, lastFocused); + if (it != overlappingTargets.end()) + return lastFocused; + } + + auto topmost = std::ranges::min_element(overlappingTargets, std::less<>{}, [](const SP& t) { return t->layoutBox.y; }); + return *topmost; + } + if (!pTargetCol->targetDatas.empty()) + return pTargetCol->targetDatas.front(); + return nullptr; +} + +SP CScrollingAlgorithm::dataFor(SP t) { + if (!t) + return nullptr; + + for (const auto& c : m_scrollingData->columns) { + for (const auto& d : c->targetDatas) { + if (d->target == t) + return d; + } + } + + return nullptr; +} + +eScrollDirection CScrollingAlgorithm::getDynamicDirection() { + const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(m_parent->space()->workspace()); + std::string directionString; + if (WORKSPACERULE.layoutopts.contains("direction")) + directionString = WORKSPACERULE.layoutopts.at("direction"); + + static const auto PCONFDIRECTION = CConfigValue("scrolling:direction"); + std::string configDirection = *PCONFDIRECTION; + + // Workspace rule overrides global config + if (!directionString.empty()) + configDirection = directionString; + + // Parse direction string + if (configDirection == "left") + return SCROLL_DIR_LEFT; + else if (configDirection == "down") + return SCROLL_DIR_DOWN; + else if (configDirection == "up") + return SCROLL_DIR_UP; + else + return SCROLL_DIR_RIGHT; // default +} + +CBox CScrollingAlgorithm::usableArea() { + CBox box = m_parent->space()->workArea(); + + // doesn't matter, this happens when this algo is about to be destroyed + if (!m_parent->space()->workspace() || !m_parent->space()->workspace()->m_monitor) + return box; + + box.translate(-m_parent->space()->workspace()->m_monitor->m_position); + return box; +} + +float CScrollingAlgorithm::defaultColumnWidth() { + static const auto PCOLWIDTH = CConfigValue("scrolling:column_width"); + return std::clamp(*PCOLWIDTH, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH); +} diff --git a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp new file mode 100644 index 00000000..d95b3197 --- /dev/null +++ b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp @@ -0,0 +1,145 @@ +#pragma once + +#include "../../TiledAlgorithm.hpp" +#include "../../../../helpers/math/Direction.hpp" +#include "ScrollTapeController.hpp" +#include "../../../../helpers/signal/Signal.hpp" + +#include + +namespace Layout::Tiled { + class CScrollingAlgorithm; + struct SColumnData; + struct SScrollingData; + + struct SScrollingTargetData { + SScrollingTargetData(SP t, SP col) : target(t), column(col) { + ; + } + + WP target; + WP column; + bool ignoreFullscreenChecks = false; + + CBox layoutBox; + }; + + struct SColumnData { + SColumnData(SP data) : scrollingData(data) { + ; + } + + void add(SP t); + void add(SP t, int after); + void add(SP w); + void add(SP w, int after); + void remove(SP t); + bool has(SP t); + size_t idx(SP t); + + // index of lowest target that is above y. + size_t idxForHeight(float y); + + bool up(SP w); + bool down(SP w); + + SP next(SP w); + SP prev(SP w); + + std::vector> targetDatas; + WP scrollingData; + WP lastFocusedTarget; + + WP self; + + // Helper methods to access controller-managed data + float getColumnWidth() const; + void setColumnWidth(float width); + float getTargetSize(size_t idx) const; + void setTargetSize(size_t idx, float size); + float getTargetSize(SP target) const; + void setTargetSize(SP target, float size); + }; + + struct SScrollingData { + SScrollingData(CScrollingAlgorithm* algo); + + std::vector> columns; + + UP controller; + + SP add(); + SP add(int after); + int64_t idx(SP c); + void remove(SP c); + double maxWidth(); + SP next(SP c); + SP prev(SP c); + SP atCenter(); + + bool visible(SP c, bool full = false); + void centerCol(SP c); + void fitCol(SP c); + void centerOrFitCol(SP c); + + void recalculate(bool forceInstant = false); + + CScrollingAlgorithm* algorithm = nullptr; + WP self; + std::optional lockedCameraOffset; + }; + + class CScrollingAlgorithm : public ITiledAlgorithm { + public: + CScrollingAlgorithm(); + virtual ~CScrollingAlgorithm(); + + virtual void newTarget(SP target); + virtual void movedTarget(SP target, std::optional focalPoint = std::nullopt); + virtual void removeTarget(SP target); + + virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + virtual void recalculate(); + + virtual SP getNextCandidate(SP old); + + virtual std::expected layoutMsg(const std::string_view& sv); + virtual std::optional predictSizeForNewTarget(); + + virtual void swapTargets(SP a, SP b); + virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + CBox usableArea(); + + enum eInputMode : uint8_t { + INPUT_MODE_SOFT = 0, + INPUT_MODE_CLICK, + INPUT_MODE_KB + }; + + private: + SP m_scrollingData; + + CHyprSignalListener m_configCallback; + CHyprSignalListener m_focusCallback; + CHyprSignalListener m_mouseButtonCallback; + + struct { + std::vector configuredWidths; + } m_config; + + eScrollDirection getDynamicDirection(); + + SP findBestNeighbor(SP pCurrent, SP pTargetCol); + SP dataFor(SP t); + SP closestNode(const Vector2D& posGlobglobgabgalab); + + void focusTargetUpdate(SP target); + void moveTargetTo(SP t, Math::eDirection dir, bool silent); + void focusOnInput(SP target, eInputMode input); + + float defaultColumnWidth(); + + friend struct SScrollingData; + }; +}; diff --git a/src/layout/space/Space.cpp b/src/layout/space/Space.cpp new file mode 100644 index 00000000..db3925f6 --- /dev/null +++ b/src/layout/space/Space.cpp @@ -0,0 +1,197 @@ +#include "Space.hpp" + +#include "../target/Target.hpp" +#include "../algorithm/Algorithm.hpp" + +#include "../../debug/log/Logger.hpp" +#include "../../desktop/Workspace.hpp" +#include "../../config/ConfigManager.hpp" +#include "../../event/EventBus.hpp" + +using namespace Layout; + +SP CSpace::create(PHLWORKSPACE w) { + auto space = SP(new CSpace(w)); + space->m_self = space; + return space; +} + +CSpace::CSpace(PHLWORKSPACE parent) : m_parent(parent) { + recheckWorkArea(); + + // NOLINTNEXTLINE + m_geomUpdateCallback = Event::bus()->m_events.monitor.layoutChanged.listen([this] { + recheckWorkArea(); + m_algorithm->recalculate(); + }); +} + +void CSpace::add(SP t) { + m_targets.emplace_back(t); + + recheckWorkArea(); + + if (m_algorithm) + m_algorithm->addTarget(t); + + m_parent->updateWindows(); +} + +void CSpace::move(SP t, std::optional focalPoint) { + m_targets.emplace_back(t); + + recheckWorkArea(); + + if (m_algorithm) + m_algorithm->moveTarget(t, focalPoint); + + m_parent->updateWindows(); +} + +void CSpace::remove(SP t) { + std::erase_if(m_targets, [&t](const auto& e) { return !e || e == t; }); + + recheckWorkArea(); + + if (m_algorithm) + m_algorithm->removeTarget(t); + + if (m_parent) // can be null if the workspace is gone + m_parent->updateWindows(); +} + +void CSpace::setAlgorithmProvider(SP algo) { + m_algorithm = algo; +} + +void CSpace::recheckWorkArea() { + if (!m_parent || !m_parent->m_monitor) { + Log::logger->log(Log::ERR, "CSpace: recheckWorkArea on no parent / mon?!"); + return; + } + + const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(m_parent.lock()); + + auto workArea = m_parent->m_monitor->logicalBoxMinusReserved(); + + static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); + static auto PFLOATGAPSDATA = CConfigValue("general:float_gaps"); + auto* const PGAPSOUT = sc((PGAPSOUTDATA.ptr())->getData()); + auto* PFLOATGAPS = sc(PFLOATGAPSDATA.ptr()->getData()); + if (PFLOATGAPS->m_bottom < 0 || PFLOATGAPS->m_left < 0 || PFLOATGAPS->m_right < 0 || PFLOATGAPS->m_top < 0) + PFLOATGAPS = PGAPSOUT; + + auto gapsOut = WORKSPACERULE.gapsOut.value_or(*PGAPSOUT); + auto gapsFloat = WORKSPACERULE.gapsOut.value_or(*PFLOATGAPS); + + Desktop::CReservedArea reservedGaps{gapsOut.m_top, gapsOut.m_right, gapsOut.m_bottom, gapsOut.m_left}; + Desktop::CReservedArea reservedFloatGaps{gapsFloat.m_top, gapsFloat.m_right, gapsFloat.m_bottom, gapsFloat.m_left}; + + auto floatWorkArea = workArea; + + reservedFloatGaps.applyip(floatWorkArea); + reservedGaps.applyip(workArea); + + m_workArea = workArea; + m_floatingWorkArea = floatWorkArea; +} + +const CBox& CSpace::workArea(bool floating) const { + return floating ? m_floatingWorkArea : m_workArea; +} + +PHLWORKSPACE CSpace::workspace() const { + return m_parent.lock(); +} + +void CSpace::toggleTargetFloating(SP t) { + t->setWasTiling(true); + m_algorithm->setFloating(t, !t->floating()); + t->setWasTiling(false); + + m_parent->updateWindows(); + + recalculate(); +} + +CBox CSpace::targetPositionLocal(SP t) const { + return t->position().translate(-m_workArea.pos()); +} + +void CSpace::resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner) { + if (!m_algorithm) + return; + + m_algorithm->resizeTarget(Δ, target, corner); +} + +void CSpace::moveTarget(const Vector2D& Δ, SP target) { + if (!m_algorithm) + return; + + m_algorithm->moveTarget(Δ, target); +} + +SP CSpace::algorithm() const { + return m_algorithm; +} + +void CSpace::recalculate() { + recheckWorkArea(); + + if (m_algorithm) + m_algorithm->recalculate(); +} + +void CSpace::setFullscreen(SP t, eFullscreenMode mode) { + t->setFullscreenMode(mode); + + if (mode == FSMODE_NONE && m_algorithm && t->floating()) + m_algorithm->recenter(t); + + recalculate(); +} + +std::expected CSpace::layoutMsg(const std::string_view& sv) { + if (m_algorithm) + return m_algorithm->layoutMsg(sv); + + return {}; +} + +std::optional CSpace::predictSizeForNewTiledTarget() { + if (m_algorithm) + return m_algorithm->predictSizeForNewTiledTarget(); + + return std::nullopt; +} + +void CSpace::swap(SP a, SP b) { + for (auto& t : m_targets) { + if (t == a) + t = b; + else if (t == b) + t = a; + } + + if (m_algorithm) + m_algorithm->swapTargets(a, b); +} + +void CSpace::moveTargetInDirection(SP t, Math::eDirection dir, bool silent) { + if (m_algorithm) + m_algorithm->moveTargetInDirection(t, dir, silent); +} + +void CSpace::setTargetGeom(const CBox& box, SP target) { + if (m_algorithm) + m_algorithm->setTargetGeom(box, target); +} + +SP CSpace::getNextCandidate(SP old) { + return !m_algorithm ? nullptr : m_algorithm->getNextCandidate(old); +} + +const std::vector>& CSpace::targets() const { + return m_targets; +} diff --git a/src/layout/space/Space.hpp b/src/layout/space/Space.hpp new file mode 100644 index 00000000..e29a6d8f --- /dev/null +++ b/src/layout/space/Space.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include "../../helpers/math/Math.hpp" +#include "../../helpers/math/Direction.hpp" +#include "../../helpers/memory/Memory.hpp" + +#include "../../desktop/DesktopTypes.hpp" + +#include "../LayoutManager.hpp" + +#include +#include + +namespace Layout { + class ITarget; + class CAlgorithm; + + class CSpace { + public: + static SP create(PHLWORKSPACE w); + ~CSpace() = default; + + void add(SP t); + void remove(SP t); + void move(SP t, std::optional focalPoint = std::nullopt); + + void swap(SP a, SP b); + + SP getNextCandidate(SP old); + + void setAlgorithmProvider(SP algo); + void recheckWorkArea(); + void setFullscreen(SP t, eFullscreenMode mode); + + void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + void recalculate(); + + void toggleTargetFloating(SP t); + + std::expected layoutMsg(const std::string_view& sv); + std::optional predictSizeForNewTiledTarget(); + + const CBox& workArea(bool floating = false) const; + PHLWORKSPACE workspace() const; + CBox targetPositionLocal(SP t) const; + + void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + void moveTarget(const Vector2D& Δ, SP target); + void setTargetGeom(const CBox& box, SP target); // only for float + + SP algorithm() const; + + const std::vector>& targets() const; + + private: + CSpace(PHLWORKSPACE parent); + + WP m_self; + + std::vector> m_targets; + SP m_algorithm; + PHLWORKSPACEREF m_parent; + + // work area is in global coords + CBox m_workArea, m_floatingWorkArea; + + // for recalc + CHyprSignalListener m_geomUpdateCallback; + }; +}; \ No newline at end of file diff --git a/src/layout/supplementary/DragController.cpp b/src/layout/supplementary/DragController.cpp new file mode 100644 index 00000000..be70f4ac --- /dev/null +++ b/src/layout/supplementary/DragController.cpp @@ -0,0 +1,398 @@ +#include "DragController.hpp" + +#include "../space/Space.hpp" + +#include "../../Compositor.hpp" +#include "../../managers/cursor/CursorShapeOverrideController.hpp" +#include "../../desktop/state/FocusState.hpp" +#include "../../desktop/view/Group.hpp" +#include "../../render/Renderer.hpp" + +using namespace Layout; +using namespace Layout::Supplementary; + +SP CDragStateController::target() const { + return m_target.lock(); +} + +eMouseBindMode CDragStateController::mode() const { + return m_dragMode; +} + +bool CDragStateController::wasDraggingWindow() const { + return m_wasDraggingWindow; +} + +bool CDragStateController::dragThresholdReached() const { + return m_dragThresholdReached; +} + +void CDragStateController::resetDragThresholdReached() { + m_dragThresholdReached = false; +} + +bool CDragStateController::draggingTiled() const { + return m_draggingTiled; +} + +bool CDragStateController::updateDragWindow() { + const auto DRAGGINGTARGET = m_target.lock(); + const bool WAS_FULLSCREEN = DRAGGINGTARGET->fullscreenMode() != FSMODE_NONE; + + if (m_dragThresholdReached) { + if (WAS_FULLSCREEN) { + Log::logger->log(Log::DEBUG, "Dragging a fullscreen window"); + g_pCompositor->setWindowFullscreenInternal(DRAGGINGTARGET->window(), FSMODE_NONE); + } + + const auto PWORKSPACE = DRAGGINGTARGET->workspace(); + const auto DRAGGINGWINDOW = DRAGGINGTARGET->window(); + + if (PWORKSPACE->m_hasFullscreenWindow && (!DRAGGINGTARGET->floating() || (!DRAGGINGWINDOW->m_createdOverFullscreen && !DRAGGINGWINDOW->m_pinned))) { + Log::logger->log(Log::DEBUG, "Rejecting drag on a fullscreen workspace. (window under fullscreen)"); + CKeybindManager::changeMouseBindMode(MBIND_INVALID); + return true; + } + } + + m_draggingTiled = false; + m_draggingWindowOriginalFloatSize = DRAGGINGTARGET->lastFloatingSize(); + + if (WAS_FULLSCREEN && DRAGGINGTARGET->floating()) { + const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); + DRAGGINGTARGET->setPositionGlobal(CBox{MOUSECOORDS - DRAGGINGTARGET->position().size() / 2.F, DRAGGINGTARGET->position().size()}); + } else if (!DRAGGINGTARGET->floating() && m_dragMode == MBIND_MOVE) { + Vector2D MINSIZE = DRAGGINGTARGET->minSize().value_or(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}); + DRAGGINGTARGET->rememberFloatingSize((DRAGGINGTARGET->position().size() * 0.8489).clamp(MINSIZE, Vector2D{}).floor()); + DRAGGINGTARGET->setPositionGlobal(CBox{g_pInputManager->getMouseCoordsInternal() - DRAGGINGTARGET->position().size() / 2.F, DRAGGINGTARGET->position().size()}); + + if (m_dragThresholdReached) { + g_layoutManager->changeFloatingMode(DRAGGINGTARGET); + m_draggingTiled = true; + } + } + + const auto DRAG_ORIGINAL_BOX = DRAGGINGTARGET->position(); + + m_beginDragXY = g_pInputManager->getMouseCoordsInternal(); + m_beginDragPositionXY = DRAG_ORIGINAL_BOX.pos(); + m_beginDragSizeXY = DRAG_ORIGINAL_BOX.size(); + m_lastDragXY = m_beginDragXY; + + return false; +} + +void CDragStateController::dragBegin(SP target, eMouseBindMode mode) { + m_target = target; + m_dragMode = mode; + + const auto DRAGGINGTARGET = m_target.lock(); + static auto PDRAGTHRESHOLD = CConfigValue("binds:drag_threshold"); + + m_mouseMoveEventCount = 1; + m_beginDragSizeXY = Vector2D(); + + // Window will be floating. Let's check if it's valid. It should be, but I don't like crashing. + if (!validMapped(DRAGGINGTARGET->window())) { + Log::logger->log(Log::ERR, "Dragging attempted on an invalid window (not mapped)"); + CKeybindManager::changeMouseBindMode(MBIND_INVALID); + return; + } + + if (!DRAGGINGTARGET->workspace()) { + Log::logger->log(Log::ERR, "Dragging attempted on an invalid window (no workspace)"); + CKeybindManager::changeMouseBindMode(MBIND_INVALID); + return; + } + + // Try to pick up dragged window now if drag_threshold is disabled + // or at least update dragging related variables for the cursors + m_dragThresholdReached = *PDRAGTHRESHOLD <= 0; + if (updateDragWindow()) + return; + + // get the grab corner + static auto RESIZECORNER = CConfigValue("general:resize_corner"); + if (*RESIZECORNER != 0 && *RESIZECORNER <= 4 && DRAGGINGTARGET->floating()) { + switch (*RESIZECORNER) { + case 1: + m_grabbedCorner = CORNER_TOPLEFT; + Cursor::overrideController->setOverride("nw-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + break; + case 2: + m_grabbedCorner = CORNER_TOPRIGHT; + Cursor::overrideController->setOverride("ne-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + break; + case 3: + m_grabbedCorner = CORNER_BOTTOMRIGHT; + Cursor::overrideController->setOverride("se-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + break; + case 4: + m_grabbedCorner = CORNER_BOTTOMLEFT; + Cursor::overrideController->setOverride("sw-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + break; + } + } else if (m_beginDragXY.x < m_beginDragPositionXY.x + m_beginDragSizeXY.x / 2.F) { + if (m_beginDragXY.y < m_beginDragPositionXY.y + m_beginDragSizeXY.y / 2.F) { + m_grabbedCorner = CORNER_TOPLEFT; + Cursor::overrideController->setOverride("nw-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + } else { + m_grabbedCorner = CORNER_BOTTOMLEFT; + Cursor::overrideController->setOverride("sw-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + } + } else { + if (m_beginDragXY.y < m_beginDragPositionXY.y + m_beginDragSizeXY.y / 2.F) { + m_grabbedCorner = CORNER_TOPRIGHT; + Cursor::overrideController->setOverride("ne-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + } else { + m_grabbedCorner = CORNER_BOTTOMRIGHT; + Cursor::overrideController->setOverride("se-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + } + } + + if (m_dragMode != MBIND_RESIZE && m_dragMode != MBIND_RESIZE_FORCE_RATIO && m_dragMode != MBIND_RESIZE_BLOCK_RATIO) + Cursor::overrideController->setOverride("grabbing", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + + DRAGGINGTARGET->damageEntire(); + + g_pKeybindManager->shadowKeybinds(); + + if (DRAGGINGTARGET->window()) { + Desktop::focusState()->rawWindowFocus(DRAGGINGTARGET->window(), Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE); + g_pCompositor->changeWindowZOrder(DRAGGINGTARGET->window(), true); + } +} +void CDragStateController::dragEnd() { + auto draggingTarget = m_target.lock(); + + m_mouseMoveEventCount = 1; + + if (!validMapped(draggingTarget->window())) { + if (draggingTarget->window()) { + Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + m_target.reset(); + } + return; + } + + Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + m_target.reset(); + m_wasDraggingWindow = true; + + if (m_dragMode == MBIND_MOVE && draggingTarget->window()) { + draggingTarget->damageEntire(); + + const auto DRAGGING_WINDOW = draggingTarget->window(); + + const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); + PHLWINDOW pWindow = + g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING, DRAGGING_WINDOW); + + if (pWindow) { + if (pWindow->checkInputOnDecos(INPUT_TYPE_DRAG_END, MOUSECOORDS, DRAGGING_WINDOW)) + return; + + const bool FLOATEDINTOTILED = !pWindow->m_isFloating && !m_draggingTiled; + static auto PDRAGINTOGROUP = CConfigValue("group:drag_into_group"); + + if (pWindow->m_group && DRAGGING_WINDOW->canBeGroupedInto(pWindow->m_group) && *PDRAGINTOGROUP == 1 && !FLOATEDINTOTILED) { + pWindow->m_group->add(DRAGGING_WINDOW); + // fix the draggingTarget, now it's DRAGGING_WINDOW + draggingTarget = DRAGGING_WINDOW->m_target; + } + } + } + + if (m_draggingTiled) { + // static auto PPRECISEMOUSE = CConfigValue("dwindle:precise_mouse_move"); + + // FIXME: remove or rethink + // if (*PPRECISEMOUSE) { + // eDirection direction = DIRECTION_DEFAULT; + + // const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); + // const PHLWINDOW pReferenceWindow = + // g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING, DRAGGINGWINDOW); + + // if (pReferenceWindow && pReferenceWindow != DRAGGINGWINDOW) { + // const Vector2D draggedCenter = DRAGGINGWINDOW->m_realPosition->goal() + DRAGGINGWINDOW->m_realSize->goal() / 2.f; + // const Vector2D referenceCenter = pReferenceWindow->m_realPosition->goal() + pReferenceWindow->m_realSize->goal() / 2.f; + // const float xDiff = draggedCenter.x - referenceCenter.x; + // const float yDiff = draggedCenter.y - referenceCenter.y; + + // if (fabs(xDiff) > fabs(yDiff)) + // direction = xDiff < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT; + // else + // direction = yDiff < 0 ? DIRECTION_UP : DIRECTION_DOWN; + // } + + // onWindowRemovedTiling(DRAGGINGWINDOW); + // onWindowCreatedTiling(DRAGGINGWINDOW, direction); + // } else + + // make sure to check if we are floating because drag into group could make us tiled already + if (draggingTarget->floating()) + g_layoutManager->changeFloatingMode(draggingTarget); + + draggingTarget->rememberFloatingSize(m_draggingWindowOriginalFloatSize); + } + + draggingTarget->damageEntire(); + + g_layoutManager->setTargetGeom(draggingTarget->position(), draggingTarget); + + Desktop::focusState()->fullWindowFocus(draggingTarget->window(), Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE); + + m_wasDraggingWindow = false; + m_dragMode = MBIND_INVALID; +} + +void CDragStateController::mouseMove(const Vector2D& mousePos) { + if (m_target.expired()) + return; + + const auto DRAGGINGTARGET = m_target.lock(); + static auto PDRAGTHRESHOLD = CConfigValue("binds:drag_threshold"); + + // Window invalid or drag begin size 0,0 meaning we rejected it. + if ((!validMapped(DRAGGINGTARGET->window()) || m_beginDragSizeXY == Vector2D())) { + CKeybindManager::changeMouseBindMode(MBIND_INVALID); + return; + } + + // Yoink dragged window here instead if using drag_threshold and it has been reached + if (*PDRAGTHRESHOLD > 0 && !m_dragThresholdReached) { + if ((m_beginDragXY.distanceSq(mousePos) <= std::pow(*PDRAGTHRESHOLD, 2) && m_beginDragXY == m_lastDragXY)) + return; + m_dragThresholdReached = true; + if (updateDragWindow()) + return; + } + + static auto TIMER = std::chrono::high_resolution_clock::now(), MSTIMER = TIMER; + + const auto DELTA = Vector2D(mousePos.x - m_beginDragXY.x, mousePos.y - m_beginDragXY.y); + const auto TICKDELTA = Vector2D(mousePos.x - m_lastDragXY.x, mousePos.y - m_lastDragXY.y); + + static auto SNAPENABLED = CConfigValue("general:snap:enabled"); + + const auto TIMERDELTA = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - TIMER).count(); + const auto MSDELTA = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - MSTIMER).count(); + const auto MSMONITOR = 1000.0 / g_pHyprRenderer->m_mostHzMonitor->m_refreshRate; + static int totalMs = 0; + bool canSkipUpdate = true; + + MSTIMER = std::chrono::high_resolution_clock::now(); + + if (m_mouseMoveEventCount == 1) + totalMs = 0; + + if (MSMONITOR > 16.0) { + totalMs += MSDELTA < MSMONITOR ? MSDELTA : std::round(totalMs * 1.0 / m_mouseMoveEventCount); + m_mouseMoveEventCount += 1; + + // check if time-window is enough to skip update on 60hz monitor + canSkipUpdate = std::clamp(MSMONITOR - TIMERDELTA, 0.0, MSMONITOR) > totalMs * 1.0 / m_mouseMoveEventCount; + } + + if ((abs(TICKDELTA.x) < 1.f && abs(TICKDELTA.y) < 1.f) || (TIMERDELTA < MSMONITOR && canSkipUpdate && (m_dragMode != MBIND_MOVE))) + return; + + TIMER = std::chrono::high_resolution_clock::now(); + + m_lastDragXY = mousePos; + + DRAGGINGTARGET->damageEntire(); + + if (m_dragMode == MBIND_MOVE) { + + Vector2D newPos = m_beginDragPositionXY + DELTA; + Vector2D newSize = DRAGGINGTARGET->position().size(); + + if (*SNAPENABLED && !m_draggingTiled) + g_layoutManager->performSnap(newPos, newSize, DRAGGINGTARGET, MBIND_MOVE, -1, m_beginDragSizeXY); + + newPos = newPos.round(); + + DRAGGINGTARGET->setPositionGlobal({newPos, newSize}); + DRAGGINGTARGET->warpPositionSize(); + } else if (m_dragMode == MBIND_RESIZE || m_dragMode == MBIND_RESIZE_FORCE_RATIO || m_dragMode == MBIND_RESIZE_BLOCK_RATIO) { + if (DRAGGINGTARGET->floating()) { + + Vector2D MINSIZE = DRAGGINGTARGET->minSize().value_or(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}); + Vector2D MAXSIZE = DRAGGINGTARGET->maxSize().value_or(Math::VECTOR2D_MAX); + + Vector2D newSize = m_beginDragSizeXY; + Vector2D newPos = m_beginDragPositionXY; + + if (m_grabbedCorner == CORNER_BOTTOMRIGHT) + newSize = newSize + DELTA; + else if (m_grabbedCorner == CORNER_TOPLEFT) + newSize = newSize - DELTA; + else if (m_grabbedCorner == CORNER_TOPRIGHT) + newSize = newSize + Vector2D(DELTA.x, -DELTA.y); + else if (m_grabbedCorner == CORNER_BOTTOMLEFT) + newSize = newSize + Vector2D(-DELTA.x, DELTA.y); + + eMouseBindMode mode = m_dragMode; + if (DRAGGINGTARGET->window() && DRAGGINGTARGET->window()->m_ruleApplicator->keepAspectRatio().valueOrDefault() && mode != MBIND_RESIZE_BLOCK_RATIO) + mode = MBIND_RESIZE_FORCE_RATIO; + + if (m_beginDragSizeXY.x >= 1 && m_beginDragSizeXY.y >= 1 && mode == MBIND_RESIZE_FORCE_RATIO) { + + const float RATIO = m_beginDragSizeXY.y / m_beginDragSizeXY.x; + + if (MINSIZE.x * RATIO > MINSIZE.y) + MINSIZE = Vector2D(MINSIZE.x, MINSIZE.x * RATIO); + else + MINSIZE = Vector2D(MINSIZE.y / RATIO, MINSIZE.y); + + if (MAXSIZE.x * RATIO < MAXSIZE.y) + MAXSIZE = Vector2D(MAXSIZE.x, MAXSIZE.x * RATIO); + else + MAXSIZE = Vector2D(MAXSIZE.y / RATIO, MAXSIZE.y); + + if (newSize.x * RATIO > newSize.y) + newSize = Vector2D(newSize.x, newSize.x * RATIO); + else + newSize = Vector2D(newSize.y / RATIO, newSize.y); + } + + newSize = newSize.clamp(MINSIZE, MAXSIZE); + + if (m_grabbedCorner == CORNER_TOPLEFT) + newPos = newPos - newSize + m_beginDragSizeXY; + else if (m_grabbedCorner == CORNER_TOPRIGHT) + newPos = newPos + Vector2D(0.0, (m_beginDragSizeXY - newSize).y); + else if (m_grabbedCorner == CORNER_BOTTOMLEFT) + newPos = newPos + Vector2D((m_beginDragSizeXY - newSize).x, 0.0); + + if (*SNAPENABLED) { + g_layoutManager->performSnap(newPos, newSize, DRAGGINGTARGET, mode, m_grabbedCorner, m_beginDragSizeXY); + newSize = newSize.clamp(MINSIZE, MAXSIZE); + } + + CBox wb = {newPos, newSize}; + wb.round(); + + DRAGGINGTARGET->setPositionGlobal(wb); + DRAGGINGTARGET->warpPositionSize(); + } else { + g_layoutManager->resizeTarget(TICKDELTA, DRAGGINGTARGET, m_grabbedCorner); + DRAGGINGTARGET->warpPositionSize(); + } + } + + // get middle point + Vector2D middle = DRAGGINGTARGET->position().middle(); + + // and check its monitor + const auto PMONITOR = g_pCompositor->getMonitorFromVector(middle); + + if (PMONITOR && PMONITOR->m_activeWorkspace && DRAGGINGTARGET->floating() /* If we're resaizing a tiled target, don't do this */) { + const auto WS = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace; + DRAGGINGTARGET->assignToSpace(WS->m_space); + } + + DRAGGINGTARGET->damageEntire(); +} diff --git a/src/layout/supplementary/DragController.hpp b/src/layout/supplementary/DragController.hpp new file mode 100644 index 00000000..3a0d8071 --- /dev/null +++ b/src/layout/supplementary/DragController.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include "../target/Target.hpp" +#include "../../managers/input/InputManager.hpp" + +namespace Layout { + enum eRectCorner : uint8_t; +} + +namespace Layout::Supplementary { + + // DragStateController contains logic to begin and end a drag, which shouldn't be part of the layout's job. It's stuff like + // toggling float when dragging tiled, remembering sizes, checking deltas, etc. + class CDragStateController { + public: + CDragStateController() = default; + ~CDragStateController() = default; + + void dragBegin(SP target, eMouseBindMode mode); + void dragEnd(); + + void mouseMove(const Vector2D& mousePos); + eMouseBindMode mode() const; + bool wasDraggingWindow() const; + bool dragThresholdReached() const; + void resetDragThresholdReached(); + bool draggingTiled() const; + + /* + Called to try to pick up window for dragging. + Updates drag related variables and floats window if threshold reached. + Return true to reject + */ + bool updateDragWindow(); + + SP target() const; + + private: + WP m_target; + + eMouseBindMode m_dragMode = MBIND_INVALID; + bool m_wasDraggingWindow = false; + bool m_dragThresholdReached = false; + bool m_draggingTiled = false; + + int m_mouseMoveEventCount = 0; + Vector2D m_beginDragXY; + Vector2D m_lastDragXY; + Vector2D m_beginDragPositionXY; + Vector2D m_beginDragSizeXY; + Vector2D m_draggingWindowOriginalFloatSize; + Layout::eRectCorner m_grabbedCorner = sc(0) /* CORNER_NONE */; + }; +}; diff --git a/src/layout/supplementary/WorkspaceAlgoMatcher.cpp b/src/layout/supplementary/WorkspaceAlgoMatcher.cpp new file mode 100644 index 00000000..b476c3a0 --- /dev/null +++ b/src/layout/supplementary/WorkspaceAlgoMatcher.cpp @@ -0,0 +1,139 @@ +#include "WorkspaceAlgoMatcher.hpp" + +#include "../../config/ConfigValue.hpp" +#include "../../config/ConfigManager.hpp" + +#include "../algorithm/Algorithm.hpp" +#include "../space/Space.hpp" + +#include "../algorithm/floating/default/DefaultFloatingAlgorithm.hpp" +#include "../algorithm/tiled/dwindle/DwindleAlgorithm.hpp" +#include "../algorithm/tiled/master/MasterAlgorithm.hpp" +#include "../algorithm/tiled/scrolling/ScrollingAlgorithm.hpp" +#include "../algorithm/tiled/monocle/MonocleAlgorithm.hpp" + +#include "../../Compositor.hpp" + +using namespace Layout; +using namespace Layout::Supplementary; + +constexpr const char* DEFAULT_FLOATING_ALGO = "default"; +constexpr const char* DEFAULT_TILED_ALGO = "dwindle"; + +const UP& Supplementary::algoMatcher() { + static UP m = makeUnique(); + return m; +} + +CWorkspaceAlgoMatcher::CWorkspaceAlgoMatcher() { + m_tiledAlgos = { + {"dwindle", [] { return makeUnique(); }}, + {"master", [] { return makeUnique(); }}, + {"scrolling", [] { return makeUnique(); }}, + {"monocle", [] { return makeUnique(); }}, + }; + + m_floatingAlgos = { + {"default", [] { return makeUnique(); }}, + }; + + m_algoNames = { + {&typeid(Tiled::CDwindleAlgorithm), "dwindle"}, + {&typeid(Tiled::CMasterAlgorithm), "master"}, + {&typeid(Tiled::CScrollingAlgorithm), "scrolling"}, + {&typeid(Tiled::CMonocleAlgorithm), "monocle"}, + {&typeid(Floating::CDefaultFloatingAlgorithm), "default"}, + }; +} + +bool CWorkspaceAlgoMatcher::registerTiledAlgo(const std::string& name, const std::type_info* typeInfo, std::function()>&& factory) { + if (m_tiledAlgos.contains(name) || m_floatingAlgos.contains(name)) + return false; + + m_tiledAlgos.emplace(name, std::move(factory)); + m_algoNames.emplace(typeInfo, name); + + updateWorkspaceLayouts(); + + return true; +} + +bool CWorkspaceAlgoMatcher::registerFloatingAlgo(const std::string& name, const std::type_info* typeInfo, std::function()>&& factory) { + if (m_tiledAlgos.contains(name) || m_floatingAlgos.contains(name)) + return false; + + m_floatingAlgos.emplace(name, std::move(factory)); + m_algoNames.emplace(typeInfo, name); + + updateWorkspaceLayouts(); + + return true; +} + +bool CWorkspaceAlgoMatcher::unregisterAlgo(const std::string& name) { + if (!m_tiledAlgos.contains(name) && !m_floatingAlgos.contains(name)) + return false; + + std::erase_if(m_algoNames, [&name](const auto& e) { return e.second == name; }); + + if (m_floatingAlgos.contains(name)) + m_floatingAlgos.erase(name); + else + m_tiledAlgos.erase(name); + + // this is needed here to avoid situations where a plugin unloads and we still have a UP + // to a plugin layout + updateWorkspaceLayouts(); + + return true; +} + +UP CWorkspaceAlgoMatcher::algoForNameTiled(const std::string& s) { + if (m_tiledAlgos.contains(s)) + return m_tiledAlgos.at(s)(); + return m_tiledAlgos.at(DEFAULT_TILED_ALGO)(); +} + +UP CWorkspaceAlgoMatcher::algoForNameFloat(const std::string& s) { + if (m_floatingAlgos.contains(s)) + return m_floatingAlgos.at(s)(); + return m_floatingAlgos.at(DEFAULT_FLOATING_ALGO)(); +} + +std::string CWorkspaceAlgoMatcher::tiledAlgoForWorkspace(const PHLWORKSPACE& w) { + static auto PLAYOUT = CConfigValue("general:layout"); + + auto rule = g_pConfigManager->getWorkspaceRuleFor(w); + return rule.layout.value_or(*PLAYOUT); +} + +SP CWorkspaceAlgoMatcher::createAlgorithmForWorkspace(PHLWORKSPACE w) { + return CAlgorithm::create(algoForNameTiled(tiledAlgoForWorkspace(w)), makeUnique(), w->m_space); +} + +void CWorkspaceAlgoMatcher::updateWorkspaceLayouts() { + // TODO: make this ID-based, string comparison is slow + for (const auto& ws : g_pCompositor->getWorkspaces()) { + if (!ws) + continue; + + const auto& TILED_ALGO = ws->m_space->algorithm()->tiledAlgo(); + + if (!TILED_ALGO) + continue; + + const auto LAYOUT_TO_USE = tiledAlgoForWorkspace(ws.lock()); + + if (m_algoNames.contains(&typeid(*TILED_ALGO.get())) && m_algoNames.at(&typeid(*TILED_ALGO.get())) == LAYOUT_TO_USE) + continue; + + // needs a switchup + ws->m_space->algorithm()->updateTiledAlgo(algoForNameTiled(LAYOUT_TO_USE)); + } +} + +std::string CWorkspaceAlgoMatcher::getNameForTiledAlgo(const std::type_info* type) { + if (m_algoNames.contains(type)) + return m_algoNames.at(type); + return "unknown"; +} diff --git a/src/layout/supplementary/WorkspaceAlgoMatcher.hpp b/src/layout/supplementary/WorkspaceAlgoMatcher.hpp new file mode 100644 index 00000000..d39e2998 --- /dev/null +++ b/src/layout/supplementary/WorkspaceAlgoMatcher.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "../../desktop/DesktopTypes.hpp" + +#include +#include +#include +#include + +namespace Layout { + class CAlgorithm; + class ITiledAlgorithm; + class IFloatingAlgorithm; +} + +namespace Layout::Supplementary { + class CWorkspaceAlgoMatcher { + public: + CWorkspaceAlgoMatcher(); + ~CWorkspaceAlgoMatcher() = default; + + SP createAlgorithmForWorkspace(PHLWORKSPACE w); + void updateWorkspaceLayouts(); + std::string getNameForTiledAlgo(const std::type_info* type); + + // these fns can fail due to name collisions + bool registerTiledAlgo(const std::string& name, const std::type_info* typeInfo, std::function()>&& factory); + bool registerFloatingAlgo(const std::string& name, const std::type_info* typeInfo, std::function()>&& factory); + + // this fn fails if the algo isn't registered + bool unregisterAlgo(const std::string& name); + + private: + UP algoForNameTiled(const std::string& s); + UP algoForNameFloat(const std::string& s); + + std::string tiledAlgoForWorkspace(const PHLWORKSPACE&); + + std::map()>> m_tiledAlgos; + std::map()>> m_floatingAlgos; + + std::map m_algoNames; + }; + + const UP& algoMatcher(); +} \ No newline at end of file diff --git a/src/layout/target/Target.cpp b/src/layout/target/Target.cpp new file mode 100644 index 00000000..e433c237 --- /dev/null +++ b/src/layout/target/Target.cpp @@ -0,0 +1,146 @@ +#include "Target.hpp" +#include "../space/Space.hpp" +#include "../../debug/log/Logger.hpp" + +#include + +using namespace Layout; +using namespace Hyprutils::Utils; + +void ITarget::setPositionGlobal(const CBox& box) { + m_box = box; + m_box.round(); +} + +void ITarget::assignToSpace(const SP& space, std::optional focalPoint) { + if (m_space == space && !m_ghostSpace) + return; + + const bool HAD_SPACE = !!m_space; + + if (m_space && !m_ghostSpace) + m_space->remove(m_self.lock()); + + m_space = space; + + if (space && HAD_SPACE) + space->move(m_self.lock(), focalPoint); + else if (space) + space->add(m_self.lock()); + + if (!space) + Log::logger->log(Log::WARN, "ITarget: assignToSpace with a null space?"); + + m_ghostSpace = false; + + onUpdateSpace(); +} + +void ITarget::setSpaceGhost(const SP& space) { + if (m_space) + assignToSpace(nullptr); + + m_space = space; + + m_ghostSpace = true; +} + +SP ITarget::space() const { + return m_space; +} + +PHLWORKSPACE ITarget::workspace() const { + if (!m_space) + return nullptr; + + return m_space->workspace(); +} + +CBox ITarget::position() const { + return m_box; +} + +void ITarget::rememberFloatingSize(const Vector2D& size) { + m_floatingSize = size; +} + +Vector2D ITarget::lastFloatingSize() const { + return m_floatingSize; +} + +void ITarget::recalc() { + setPositionGlobal(m_box); +} + +void ITarget::setPseudo(bool x) { + if (m_pseudo == x) + return; + + m_pseudo = x; + + recalc(); +} + +bool ITarget::isPseudo() const { + return m_pseudo; +} + +void ITarget::setPseudoSize(const Vector2D& size) { + m_pseudoSize = size; + + recalc(); +} + +Vector2D ITarget::pseudoSize() { + return m_pseudoSize; +} + +void ITarget::swap(SP b) { + const auto IS_FLOATING = floating(); + const auto IS_FLOATING_B = b->floating(); + + // Keep workspaces alive during a swap: moving one window will unref the ws + + // NOLINTNEXTLINE + const auto PWS1 = workspace(); + // NOLINTNEXTLINE + const auto PWS2 = b->workspace(); + + CScopeGuard x([&] { + b->setFloating(IS_FLOATING); + setFloating(IS_FLOATING_B); + + // update the spaces + b->onUpdateSpace(); + onUpdateSpace(); + }); + + if (b->space() == m_space) { + // simplest + m_space->swap(m_self.lock(), b); + m_space->recalculate(); + return; + } + + // spaces differ + if (m_space) + m_space->swap(m_self.lock(), b); + if (b->space()) + b->space()->swap(b, m_self.lock()); + + std::swap(m_space, b->m_space); + + // recalc both + if (m_space) + m_space->recalculate(); + if (b->space()) + b->space()->recalculate(); +} + +bool ITarget::wasTiling() const { + return m_wasTiling; +} + +void ITarget::setWasTiling(bool x) { + m_wasTiling = x; +} diff --git a/src/layout/target/Target.hpp b/src/layout/target/Target.hpp new file mode 100644 index 00000000..dcaefdb4 --- /dev/null +++ b/src/layout/target/Target.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include "../../helpers/math/Math.hpp" +#include "../../helpers/memory/Memory.hpp" +#include "../../desktop/Workspace.hpp" + +#include +#include + +namespace Layout { + enum eTargetType : uint8_t { + TARGET_TYPE_WINDOW = 0, + TARGET_TYPE_GROUP, + }; + + enum eGeometryFailure : uint8_t { + GEOMETRY_NO_DESIRED = 0, + GEOMETRY_INVALID_DESIRED = 1, + }; + + class CSpace; + + struct SGeometryRequested { + Vector2D size; + std::optional pos; + }; + + class ITarget { + public: + virtual ~ITarget() = default; + + virtual eTargetType type() = 0; + + // position is within its space + virtual void setPositionGlobal(const CBox& box); + virtual CBox position() const; + virtual void assignToSpace(const SP& space, std::optional focalPoint = std::nullopt); + virtual void setSpaceGhost(const SP& space); + virtual SP space() const; + virtual PHLWORKSPACE workspace() const; + virtual PHLWINDOW window() const = 0; + virtual void recalc(); + virtual bool wasTiling() const; + virtual void setWasTiling(bool x); + + virtual void rememberFloatingSize(const Vector2D& size); + virtual Vector2D lastFloatingSize() const; + + virtual void setPseudo(bool x); + virtual bool isPseudo() const; + virtual void setPseudoSize(const Vector2D& size); + virtual Vector2D pseudoSize(); + virtual void swap(SP b); + + // + virtual bool floating() = 0; + virtual void setFloating(bool x) = 0; + virtual std::expected desiredGeometry() = 0; + virtual eFullscreenMode fullscreenMode() = 0; + virtual void setFullscreenMode(eFullscreenMode mode) = 0; + virtual std::optional minSize() = 0; + virtual std::optional maxSize() = 0; + virtual void damageEntire() = 0; + virtual void warpPositionSize() = 0; + virtual void onUpdateSpace() = 0; + + protected: + ITarget() = default; + + CBox m_box; + SP m_space; + WP m_self; + Vector2D m_floatingSize; + bool m_pseudo = false; + bool m_ghostSpace = false; // ghost space means a target belongs to a space, but isn't sent to the layout + Vector2D m_pseudoSize = {1280, 720}; + bool m_wasTiling = false; + }; +}; \ No newline at end of file diff --git a/src/layout/target/WindowGroupTarget.cpp b/src/layout/target/WindowGroupTarget.cpp new file mode 100644 index 00000000..ae883751 --- /dev/null +++ b/src/layout/target/WindowGroupTarget.cpp @@ -0,0 +1,92 @@ +#include "WindowGroupTarget.hpp" + +#include "../space/Space.hpp" +#include "../algorithm/Algorithm.hpp" +#include "WindowTarget.hpp" +#include "Target.hpp" + +#include "../../render/Renderer.hpp" + +using namespace Layout; + +SP CWindowGroupTarget::create(SP g) { + auto target = SP(new CWindowGroupTarget(g)); + target->m_self = target; + return target; +} + +CWindowGroupTarget::CWindowGroupTarget(SP g) : m_group(g) { + ; +} + +eTargetType CWindowGroupTarget::type() { + return TARGET_TYPE_GROUP; +} + +void CWindowGroupTarget::setPositionGlobal(const CBox& box) { + ITarget::setPositionGlobal(box); + + updatePos(); +} + +void CWindowGroupTarget::updatePos() { + for (const auto& w : m_group->windows()) { + w->m_target->setPositionGlobal(m_box); + } +} + +void CWindowGroupTarget::assignToSpace(const SP& space, std::optional focalPoint) { + ITarget::assignToSpace(space, focalPoint); + + m_group->updateWorkspace(space->workspace()); +} + +bool CWindowGroupTarget::floating() { + return m_group->current()->m_target->floating(); +} + +void CWindowGroupTarget::setFloating(bool x) { + for (const auto& w : m_group->windows()) { + w->m_target->setFloating(x); + } +} + +std::expected CWindowGroupTarget::desiredGeometry() { + return m_group->current()->m_target->desiredGeometry(); +} + +PHLWINDOW CWindowGroupTarget::window() const { + return m_group->current(); +} + +eFullscreenMode CWindowGroupTarget::fullscreenMode() { + return m_group->current()->m_fullscreenState.internal; +} + +void CWindowGroupTarget::setFullscreenMode(eFullscreenMode mode) { + m_group->current()->m_fullscreenState.internal = mode; +} + +std::optional CWindowGroupTarget::minSize() { + return m_group->current()->minSize(); +} + +std::optional CWindowGroupTarget::maxSize() { + return m_group->current()->maxSize(); +} + +void CWindowGroupTarget::damageEntire() { + g_pHyprRenderer->damageWindow(m_group->current()); +} + +void CWindowGroupTarget::warpPositionSize() { + for (const auto& w : m_group->windows()) { + w->m_target->warpPositionSize(); + } +} + +void CWindowGroupTarget::onUpdateSpace() { + for (const auto& w : m_group->windows()) { + w->m_target->onUpdateSpace(); + } +} diff --git a/src/layout/target/WindowGroupTarget.hpp b/src/layout/target/WindowGroupTarget.hpp new file mode 100644 index 00000000..3d4b85a0 --- /dev/null +++ b/src/layout/target/WindowGroupTarget.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "Target.hpp" + +#include "../../desktop/view/Window.hpp" +#include "../../desktop/view/Group.hpp" + +namespace Layout { + + class CWindowGroupTarget : public ITarget { + public: + static SP create(SP g); + virtual ~CWindowGroupTarget() = default; + + virtual eTargetType type(); + + virtual void setPositionGlobal(const CBox& box); + virtual void assignToSpace(const SP& space, std::optional focalPoint = std::nullopt); + virtual PHLWINDOW window() const; + + virtual bool floating(); + virtual void setFloating(bool x); + virtual std::expected desiredGeometry(); + virtual eFullscreenMode fullscreenMode(); + virtual void setFullscreenMode(eFullscreenMode mode); + virtual std::optional minSize(); + virtual std::optional maxSize(); + virtual void damageEntire(); + virtual void warpPositionSize(); + virtual void onUpdateSpace(); + + private: + CWindowGroupTarget(SP g); + + void updatePos(); + + WP m_group; + }; +}; \ No newline at end of file diff --git a/src/layout/target/WindowTarget.cpp b/src/layout/target/WindowTarget.cpp new file mode 100644 index 00000000..db03a385 --- /dev/null +++ b/src/layout/target/WindowTarget.cpp @@ -0,0 +1,382 @@ +#include "WindowTarget.hpp" + +#include "../space/Space.hpp" +#include "../algorithm/Algorithm.hpp" + +#include "../../protocols/core/Compositor.hpp" +#include "../../config/ConfigManager.hpp" +#include "../../helpers/Monitor.hpp" +#include "../../xwayland/XSurface.hpp" +#include "../../Compositor.hpp" +#include "../../render/Renderer.hpp" + +#include + +using namespace Hyprutils::Utils; +using namespace Layout; + +SP CWindowTarget::create(PHLWINDOW w) { + auto target = SP(new CWindowTarget(w)); + target->m_self = target; + return target; +} + +CWindowTarget::CWindowTarget(PHLWINDOW w) : m_window(w) { + ; +} + +eTargetType CWindowTarget::type() { + return TARGET_TYPE_WINDOW; +} + +void CWindowTarget::setPositionGlobal(const CBox& box) { + ITarget::setPositionGlobal(box); + + updatePos(); +} + +void CWindowTarget::updatePos() { + + g_pHyprRenderer->damageWindow(m_window.lock()); + CScopeGuard x([this] { g_pHyprRenderer->damageWindow(m_window.lock()); }); + + if (!m_space) + return; + + if (fullscreenMode() == FSMODE_FULLSCREEN) + return; + + if (floating() && fullscreenMode() != FSMODE_MAXIMIZED) { + m_window->m_position = m_box.pos(); + m_window->m_size = m_box.size(); + + *m_window->m_realPosition = m_box.pos(); + *m_window->m_realSize = m_box.size(); + + m_window->sendWindowSize(); + m_window->updateWindowDecos(); + + return; + } + + // Tiled is more complicated. + + // if we are in maximized, force the box to be max work area. + // TODO: this shouldn't be here. + if (fullscreenMode() == FSMODE_MAXIMIZED) + ITarget::setPositionGlobal(m_space->workArea(floating())); + + const auto PMONITOR = m_space->workspace()->m_monitor; + const auto PWORKSPACE = m_space->workspace(); + + // for gaps outer + const auto MONITOR_WORKAREA = m_space->workArea(); + const bool DISPLAYLEFT = STICKS(m_box.x, MONITOR_WORKAREA.x); + const bool DISPLAYRIGHT = STICKS(m_box.x + m_box.w, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w); + const bool DISPLAYTOP = STICKS(m_box.y, MONITOR_WORKAREA.y); + const bool DISPLAYBOTTOM = STICKS(m_box.y + m_box.h, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h); + + // this is used for scrolling, so that the gaps are correct when a window is the full width and has neighbors + const bool DISPLAYINVERSELEFT = STICKS(m_box.x, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w); + const bool DISPLAYINVERSERIGHT = STICKS(m_box.x + m_box.w, MONITOR_WORKAREA.x); + + // get specific gaps and rules for this workspace, + // if user specified them in config + const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(PWORKSPACE); + + if (!validMapped(m_window)) { + if (m_window) + g_layoutManager->removeTarget(m_window->layoutTarget()); + return; + } + + if (fullscreenMode() == FSMODE_FULLSCREEN) + return; + + g_pHyprRenderer->damageWindow(window()); + + static auto PGAPSINDATA = CConfigValue("general:gaps_in"); + auto* const PGAPSIN = sc((PGAPSINDATA.ptr())->getData()); + + auto gapsIn = WORKSPACERULE.gapsIn.value_or(*PGAPSIN); + CBox nodeBox = m_box; + nodeBox.round(); + + m_window->m_size = nodeBox.size(); + m_window->m_position = nodeBox.pos(); + + m_window->updateWindowDecos(); + + auto calcPos = m_window->m_position; + auto calcSize = m_window->m_size; + + const static auto REQUESTEDRATIO = CConfigValue("layout:single_window_aspect_ratio"); + const static auto REQUESTEDRATIOTOLERANCE = CConfigValue("layout:single_window_aspect_ratio_tolerance"); + + Vector2D ratioPadding; + + if ((*REQUESTEDRATIO).y != 0 && m_space->algorithm()->tiledTargets() <= 1 && fullscreenMode() == FSMODE_NONE) { + const Vector2D originalSize = MONITOR_WORKAREA.size(); + + const double requestedRatio = (*REQUESTEDRATIO).x / (*REQUESTEDRATIO).y; + const double originalRatio = originalSize.x / originalSize.y; + + if (requestedRatio > originalRatio) { + double padding = originalSize.y - (originalSize.x / requestedRatio); + + if (padding / 2 > (*REQUESTEDRATIOTOLERANCE) * originalSize.y) + ratioPadding = Vector2D{0., padding}; + } else if (requestedRatio < originalRatio) { + double padding = originalSize.x - (originalSize.y * requestedRatio); + + if (padding / 2 > (*REQUESTEDRATIOTOLERANCE) * originalSize.x) + ratioPadding = Vector2D{padding, 0.}; + } + } + + const auto GAPOFFSETTOPLEFT = Vector2D(sc(DISPLAYLEFT ? 0 : (DISPLAYINVERSELEFT ? 2 * gapsIn.m_left : gapsIn.m_left)), sc(DISPLAYTOP ? 0 : gapsIn.m_top)); + + const auto GAPOFFSETBOTTOMRIGHT = + Vector2D(sc(DISPLAYRIGHT ? 0 : (DISPLAYINVERSERIGHT ? 2 * gapsIn.m_right : gapsIn.m_right)), sc(DISPLAYBOTTOM ? 0 : gapsIn.m_bottom)); + + calcPos = calcPos + GAPOFFSETTOPLEFT + ratioPadding / 2; + calcSize = calcSize - GAPOFFSETTOPLEFT - GAPOFFSETBOTTOMRIGHT - ratioPadding; + + if (isPseudo() && fullscreenMode() == FSMODE_NONE) { + // Calculate pseudo + float scale = 1; + + // adjust if doesn't fit + if (m_pseudoSize.x > calcSize.x || m_pseudoSize.y > calcSize.y) { + if (m_pseudoSize.x > calcSize.x) + scale = calcSize.x / m_pseudoSize.x; + + if (m_pseudoSize.y * scale > calcSize.y) + scale = calcSize.y / m_pseudoSize.y; + + auto DELTA = calcSize - m_pseudoSize * scale; + calcSize = m_pseudoSize * scale; + calcPos = calcPos + DELTA / 2.f; // center + } else { + auto DELTA = calcSize - m_pseudoSize; + calcPos = calcPos + DELTA / 2.f; // center + calcSize = m_pseudoSize; + } + } + + const auto RESERVED = m_window->getFullWindowReservedArea(); + calcPos = calcPos + RESERVED.topLeft; + calcSize = calcSize - (RESERVED.topLeft + RESERVED.bottomRight); + + Vector2D availableSpace = calcSize; + + static auto PCLAMP_TILED = CConfigValue("misc:size_limits_tiled"); + + if (*PCLAMP_TILED) { + Vector2D minSize = m_window->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}); + Vector2D maxSize = m_window->isFullscreen() ? Vector2D{INFINITY, INFINITY} : m_window->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}); + calcSize = calcSize.clamp(minSize, maxSize); + + calcPos += (availableSpace - calcSize) / 2.0; + + calcPos.x = std::clamp(calcPos.x, MONITOR_WORKAREA.x, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w - calcSize.x); + calcPos.y = std::clamp(calcPos.y, MONITOR_WORKAREA.y, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h - calcSize.y); + } + + if (m_window->onSpecialWorkspace() && !m_window->isFullscreen()) { + // if special, we adjust the coords a bit + static auto PSCALEFACTOR = CConfigValue("dwindle:special_scale_factor"); + + CBox wb = {calcPos + (calcSize - calcSize * *PSCALEFACTOR) / 2.f, calcSize * *PSCALEFACTOR}; + wb.round(); // avoid rounding mess + + *m_window->m_realPosition = wb.pos(); + *m_window->m_realSize = wb.size(); + } else { + CBox wb = {calcPos, calcSize}; + wb.round(); // avoid rounding mess + + *m_window->m_realSize = wb.size(); + *m_window->m_realPosition = wb.pos(); + } + + m_window->updateWindowDecos(); +} + +void CWindowTarget::assignToSpace(const SP& space, std::optional focalPoint) { + if (!space) { + ITarget::assignToSpace(space, focalPoint); + return; + } + + // keep the ref here so that moveToWorkspace doesn't unref the workspace + // and assignToSpace doesn't think this is a new target because space wp is dead + const auto WSREF = space->workspace(); + + m_window->m_monitor = space->workspace()->m_monitor; + m_window->moveToWorkspace(space->workspace()); + + // layout and various update fns want the target to already have m_workspace set + ITarget::assignToSpace(space, focalPoint); + + m_window->updateToplevel(); + m_window->updateWindowDecos(); +} + +bool CWindowTarget::floating() { + return m_window->m_isFloating; +} + +void CWindowTarget::setFloating(bool x) { + if (x == m_window->m_isFloating) + return; + + m_window->m_isFloating = x; + m_window->m_pinned = false; + + m_window->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FLOATING); +} + +Vector2D CWindowTarget::clampSizeForDesired(const Vector2D& size) const { + Vector2D newSize = size; + if (const auto m = m_window->minSize(); m) + newSize = newSize.clamp(*m); + if (const auto m = m_window->maxSize(); m) + newSize = newSize.clamp(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}, *m); + return newSize; +} + +std::expected CWindowTarget::desiredGeometry() { + + SGeometryRequested requested; + + CBox DESIRED_GEOM = g_pXWaylandManager->getGeometryForWindow(m_window.lock()); + const auto PMONITOR = m_window->m_monitor.lock(); + + requested.size = clampSizeForDesired(DESIRED_GEOM.size()); + + if (m_window->m_isX11) { + Vector2D xy = {DESIRED_GEOM.x, DESIRED_GEOM.y}; + xy = g_pXWaylandManager->xwaylandToWaylandCoords(xy); + requested.pos = xy; + DESIRED_GEOM.x = xy.x; + DESIRED_GEOM.y = xy.y; + } + + const auto STOREDSIZE = m_window->m_ruleApplicator->persistentSize().valueOrDefault() ? g_pConfigManager->getStoredFloatingSize(m_window.lock()) : std::nullopt; + + if (STOREDSIZE) + requested.size = clampSizeForDesired(*STOREDSIZE); + + if (!PMONITOR) { + Log::logger->log(Log::ERR, "{:m} has an invalid monitor in desiredGeometry!", m_window.lock()); + return std::unexpected(GEOMETRY_NO_DESIRED); + } + + static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); + const auto toLogical = [&](SGeometryRequested& req) { + if (m_window->m_isX11 && *PXWLFORCESCALEZERO && PMONITOR) + req.size /= PMONITOR->m_scale; + }; + + if (DESIRED_GEOM.width <= 2 || DESIRED_GEOM.height <= 2) { + const auto SURFACE = m_window->wlSurface()->resource(); + + if (SURFACE->m_current.size.x > 5 && SURFACE->m_current.size.y > 5) { + // center on mon and call it a day + requested.pos.reset(); + requested.size = clampSizeForDesired(SURFACE->m_current.size); + toLogical(requested); + return requested; + } + + if (m_window->m_isX11 && m_window->isX11OverrideRedirect()) { + // check OR windows, they like their shit + const auto SIZE = clampSizeForDesired(m_window->m_xwaylandSurface->m_geometry.w > 0 && m_window->m_xwaylandSurface->m_geometry.h > 0 ? + m_window->m_xwaylandSurface->m_geometry.size() : + Vector2D{600, 400}); + + if (m_window->m_xwaylandSurface->m_geometry.x != 0 && m_window->m_xwaylandSurface->m_geometry.y != 0) { + requested.size = SIZE; + requested.pos = g_pXWaylandManager->xwaylandToWaylandCoords(m_window->m_xwaylandSurface->m_geometry.pos()); + toLogical(requested); + return requested; + } + } + + return std::unexpected(m_window->m_isX11 && m_window->isX11OverrideRedirect() ? GEOMETRY_INVALID_DESIRED : GEOMETRY_NO_DESIRED); + } + + // TODO: detect a popup in a more consistent way. + if ((DESIRED_GEOM.x == 0 && DESIRED_GEOM.y == 0) || !m_window->m_isX11) { + // middle of parent if available + if (!m_window->m_isX11) { + if (const auto PARENT = m_window->parent(); PARENT) { + const auto POS = PARENT->m_realPosition->goal() + PARENT->m_realSize->goal() / 2.F - DESIRED_GEOM.size() / 2.F; + requested.pos = POS; + } + } + } else { + // if it is, we respect where it wants to put itself, but apply monitor offset if outside + // most of these are popups + + Vector2D pos; + + if (const auto POPENMON = g_pCompositor->getMonitorFromVector(DESIRED_GEOM.middle()); POPENMON->m_id != PMONITOR->m_id) + pos = Vector2D(DESIRED_GEOM.x, DESIRED_GEOM.y) - POPENMON->m_position + PMONITOR->m_position; + else + pos = Vector2D(DESIRED_GEOM.x, DESIRED_GEOM.y); + + requested.pos = pos; + } + + if (DESIRED_GEOM.w <= 2 || DESIRED_GEOM.h <= 2) + return std::unexpected(GEOMETRY_NO_DESIRED); + + toLogical(requested); + return requested; +} + +PHLWINDOW CWindowTarget::window() const { + return m_window.lock(); +} + +eFullscreenMode CWindowTarget::fullscreenMode() { + return m_window->m_fullscreenState.internal; +} + +void CWindowTarget::setFullscreenMode(eFullscreenMode mode) { + if (floating() && m_window->m_fullscreenState.internal == FSMODE_NONE) + rememberFloatingSize(m_box.size()); + + m_window->m_fullscreenState.internal = mode; +} + +std::optional CWindowTarget::minSize() { + return m_window->minSize(); +} + +std::optional CWindowTarget::maxSize() { + return m_window->maxSize(); +} + +void CWindowTarget::damageEntire() { + g_pHyprRenderer->damageWindow(m_window.lock()); +} + +void CWindowTarget::warpPositionSize() { + m_window->m_realSize->warp(); + m_window->m_realPosition->warp(); + m_window->updateWindowDecos(); +} + +void CWindowTarget::onUpdateSpace() { + if (!space()) + return; + + m_window->m_monitor = space()->workspace()->m_monitor; + m_window->moveToWorkspace(space()->workspace()); + m_window->updateToplevel(); + m_window->updateWindowData(); + m_window->updateWindowDecos(); +} diff --git a/src/layout/target/WindowTarget.hpp b/src/layout/target/WindowTarget.hpp new file mode 100644 index 00000000..2939fd74 --- /dev/null +++ b/src/layout/target/WindowTarget.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "Target.hpp" + +#include "../../desktop/view/Window.hpp" + +namespace Layout { + + class CWindowTarget : public ITarget { + public: + static SP create(PHLWINDOW w); + virtual ~CWindowTarget() = default; + + virtual eTargetType type(); + + virtual void setPositionGlobal(const CBox& box); + virtual void assignToSpace(const SP& space, std::optional focalPoint = std::nullopt); + virtual PHLWINDOW window() const; + + virtual bool floating(); + virtual void setFloating(bool x); + virtual std::expected desiredGeometry(); + virtual eFullscreenMode fullscreenMode(); + virtual void setFullscreenMode(eFullscreenMode mode); + virtual std::optional minSize(); + virtual std::optional maxSize(); + virtual void damageEntire(); + virtual void warpPositionSize(); + virtual void onUpdateSpace(); + + private: + CWindowTarget(PHLWINDOW w); + + Vector2D clampSizeForDesired(const Vector2D& size) const; + + void updatePos(); + + PHLWINDOWREF m_window; + }; +}; \ No newline at end of file diff --git a/src/macros.hpp b/src/macros.hpp index 7fa25cfb..fc109296 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 2574d822..99e64675 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 "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; - 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 65e0dea1..9f613df8 100644 --- a/src/managers/ANRManager.cpp +++ b/src/managers/ANRManager.cpp @@ -1,13 +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; @@ -15,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; } @@ -24,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; } @@ -35,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; @@ -47,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); @@ -83,7 +86,7 @@ void CANRManager::onTick() { if (data->missedResponses >= *PANRTHRESHOLD) { if (!data->isRunning() && !data->dialogSaidWait) { - data->runDialog("Application Not Responding", 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) @@ -176,29 +179,46 @@ CANRManager::SANRData::~SANRData() { killDialog(); } -void CANRManager::SANRData::runDialog(const std::string& title, const std::string& appName, const std::string appClass, pid_t dialogWmPID) { +void CANRManager::SANRData::runDialog(const std::string& appName, const std::string appClass, pid_t dialogWmPID) { if (dialogBox && dialogBox->isRunning()) killDialog(); - dialogBox = CAsyncDialogBox::create(title, - std::format("Application {} with class of {} is not responding.\nWhat do you want to do with it?", appName.empty() ? "unknown" : appName, - appClass.empty() ? "unknown" : appClass), - std::vector{"Terminate", "Wait"}); + 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->open()->then([dialogWmPID, this](SP> r) { + 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; } const auto& result = r->result(); - if (result.starts_with("Terminate")) + if (result.starts_with(OPTION_TERMINATE_STR)) ::kill(dialogWmPID, SIGKILL); - else if (result.starts_with("Wait")) + 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); }); } @@ -226,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 f5c0085b..3880249d 100644 --- a/src/managers/ANRManager.hpp +++ b/src/managers/ANRManager.hpp @@ -39,12 +39,12 @@ class CANRManager { bool dialogSaidWait = false; SP dialogBox; - void runDialog(const std::string& title, const std::string& appName, const std::string appClass, pid_t dialogWmPID); + void runDialog(const std::string& appName, const std::string appClass, pid_t dialogWmPID); bool isRunning(); 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 d2905a1e..7564ca75 100644 --- a/src/managers/CursorManager.cpp +++ b/src/managers/CursorManager.cpp @@ -3,8 +3,8 @@ #include "../config/ConfigValue.hpp" #include "PointerManager.hpp" #include "../xwayland/XWayland.hpp" -#include "../managers/HookSystemManager.hpp" #include "../helpers/Monitor.hpp" +#include "../event/EventBus.hpp" static int cursorAnimTimer(SP self, void* data) { const auto cursorMgr = sc(data); @@ -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 dd3238af..f4c42d30 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 826439b2..62dd15b7 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 c4c6c5d7..6d42a818 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 a5623f08..00000000 --- a/src/managers/HookSystemManager.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "HookSystemManager.hpp" - -#include "../plugins/PluginSystem.hpp" - -CHookSystemManager::CHookSystemManager() { - ; // -} - -// returns the pointer to the function -SP CHookSystemManager::hookDynamic(const std::string& event, HOOK_CALLBACK_FN fn, HANDLE handle) { - SP hookFN = makeShared(fn); - m_registeredHooks[event].emplace_back(SCallbackFNPtr{.fn = hookFN, .handle = handle}); - return hookFN; -} - -void CHookSystemManager::unhook(SP fn) { - for (auto& [k, v] : m_registeredHooks) { - std::erase_if(v, [&](const auto& other) { - SP fn_ = other.fn.lock(); - - return fn_.get() == fn.get(); - }); - } -} - -void CHookSystemManager::emit(std::vector* const callbacks, SCallbackInfo& info, std::any data) { - if (callbacks->empty()) - return; - - std::vector faultyHandles; - volatile bool needsDeadCleanup = false; - - for (auto const& cb : *callbacks) { - - m_currentEventPlugin = false; - - if (!cb.handle) { - // we don't guard hl hooks - - if (SP fn = cb.fn.lock()) - (*fn)(fn.get(), info, data); - else - needsDeadCleanup = true; - continue; - } - - m_currentEventPlugin = true; - - if (std::ranges::find(faultyHandles, cb.handle) != faultyHandles.end()) - continue; - - try { - if (!setjmp(m_hookFaultJumpBuf)) { - if (SP fn = cb.fn.lock()) - (*fn)(fn.get(), info, data); - else - needsDeadCleanup = true; - } else { - // this module crashed. - throw std::exception(); - } - } catch (std::exception& e) { - // TODO: this works only once...? - faultyHandles.push_back(cb.handle); - 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 647e9670..00000000 --- a/src/managers/HookSystemManager.hpp +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#include "../defines.hpp" - -#include -#include -#include -#include - -#include - -#define HANDLE void* - -// global type alias for hooked functions. Passes itself as a ptr when called, and `data` additionally. - -using HOOK_CALLBACK_FN = std::function; - -struct SCallbackFNPtr { - WP fn; - HANDLE handle = nullptr; -}; - -#define EMIT_HOOK_EVENT(name, param) \ - { \ - static auto* const PEVENTVEC = g_pHookSystem->getVecForEvent(name); \ - SCallbackInfo info; \ - g_pHookSystem->emit(PEVENTVEC, info, param); \ - } - -#define EMIT_HOOK_EVENT_CANCELLABLE(name, param) \ - { \ - static auto* const PEVENTVEC = g_pHookSystem->getVecForEvent(name); \ - SCallbackInfo info; \ - g_pHookSystem->emit(PEVENTVEC, info, param); \ - if (info.cancelled) \ - return; \ - } - -class CHookSystemManager { - public: - CHookSystemManager(); - - // returns the pointer to the function. - // losing this pointer (letting it get destroyed) - // will equal to unregistering the callback. - [[nodiscard("Losing this pointer instantly unregisters the callback")]] SP hookDynamic(const std::string& event, HOOK_CALLBACK_FN fn, - HANDLE handle = nullptr); - void unhook(SP fn); - - void emit(std::vector* const callbacks, SCallbackInfo& info, std::any data = 0); - std::vector* getVecForEvent(const std::string& event); - - bool m_currentEventPlugin = false; - jmp_buf m_hookFaultJumpBuf; - - private: - std::unordered_map> m_registeredHooks; -}; - -inline UP g_pHookSystem; diff --git a/src/managers/KeybindManager.cpp b/src/managers/KeybindManager.cpp index 68750ad7..fd7c4e72 100644 --- a/src/managers/KeybindManager.cpp +++ b/src/managers/KeybindManager.cpp @@ -1,5 +1,8 @@ #include "../config/ConfigValue.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" @@ -12,15 +15,23 @@ #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 "../config/ConfigManager.hpp" +#include "../desktop/rule/windowRule/WindowRule.hpp" +#include "../desktop/rule/Engine.hpp" +#include "../desktop/view/Group.hpp" +#include "../layout/LayoutManager.hpp" +#include "../layout/target/WindowTarget.hpp" +#include "../layout/space/Space.hpp" +#include "../layout/algorithm/Algorithm.hpp" +#include "../layout/algorithm/tiled/master/MasterAlgorithm.hpp" +#include "../layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp" +#include "../event/EventBus.hpp" #include #include @@ -51,7 +62,7 @@ static std::vector> getHyprlandLaunchEnv(PHL if (!*PINITIALWSTRACKING || g_pConfigManager->m_isLaunchingExecOnce) return {}; - const auto PMONITOR = g_pCompositor->m_lastMonitor; + const auto PMONITOR = Desktop::focusState()->monitor(); if (!PMONITOR || !PMONITOR->m_activeWorkspace) return {}; @@ -65,7 +76,7 @@ static std::vector> getHyprlandLaunchEnv(PHL } result.push_back(std::make_pair<>("HL_INITIAL_WORKSPACE_TOKEN", - g_pTokenManager->registerNewToken(SInitialWorkspaceToken{{}, pInitialWorkspace->getConfigName()}, std::chrono::months(1337)))); + g_pTokenManager->registerNewToken(Desktop::View::SInitialWorkspaceToken{{}, pInitialWorkspace->getConfigName()}, std::chrono::months(1337)))); return result; } @@ -98,9 +109,6 @@ CKeybindManager::CKeybindManager() { m_dispatchers["togglegroup"] = toggleGroup; m_dispatchers["changegroupactive"] = changeGroupActive; m_dispatchers["movegroupwindow"] = moveGroupWindow; - m_dispatchers["togglesplit"] = toggleSplit; - m_dispatchers["swapsplit"] = swapSplit; - m_dispatchers["splitratio"] = alterSplitRatio; m_dispatchers["focusmonitor"] = focusMonitor; m_dispatchers["movecursortocorner"] = moveCursorToCorner; m_dispatchers["movecursor"] = moveCursor; @@ -160,7 +168,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); @@ -178,7 +186,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); } @@ -192,8 +200,7 @@ CKeybindManager::CKeybindManager() { g_pEventLoopManager->addTimer(m_repeatKeyTimer); } - static auto P = g_pHookSystem->hookDynamic("configReloaded", [this](void* hk, SCallbackInfo& info, std::any param) { - // clear cuz realloc'd + static auto P = Event::bus()->m_events.config.reloaded.listen([this] { m_activeKeybinds.clear(); m_lastLongPressKeybind.reset(); m_pressedSpecialBinds.clear(); @@ -304,8 +311,8 @@ void CKeybindManager::updateXKBTranslationState() { ", layout: " + LAYOUT + " )", CHyprColor(1.0, 50.0 / 255.0, 50.0 / 255.0, 1.0)); - 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); @@ -317,10 +324,10 @@ void CKeybindManager::updateXKBTranslationState() { } bool CKeybindManager::ensureMouseBindState() { - if (!g_pInputManager->m_currentlyDraggedWindow) + if (!g_layoutManager->dragController()->target()) return false; - if (!g_pInputManager->m_currentlyDraggedWindow.expired()) { + if (g_layoutManager->dragController()->target()) { changeMouseBindMode(MBIND_INVALID); return true; } @@ -334,37 +341,36 @@ static void updateRelativeCursorCoords() { if (*PNOWARPS) return; - if (g_pCompositor->m_lastWindow) - g_pCompositor->m_lastWindow->m_relativeCursorCoordsOnLastWarp = g_pInputManager->getMouseCoordsInternal() - g_pCompositor->m_lastWindow->m_position; + if (Desktop::focusState()->window()) + Desktop::focusState()->window()->m_relativeCursorCoordsOnLastWarp = g_pInputManager->getMouseCoordsInternal() - Desktop::focusState()->window()->m_position; } bool CKeybindManager::tryMoveFocusToMonitor(PHLMONITOR monitor) { if (!monitor) return false; - const auto LASTMONITOR = g_pCompositor->m_lastMonitor.lock(); + const auto LASTMONITOR = Desktop::focusState()->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; } static auto PFOLLOWMOUSE = CConfigValue("input:follow_mouse"); static auto PNOWARPS = CConfigValue("cursor:no_warps"); - const auto PWORKSPACE = g_pCompositor->m_lastMonitor->m_activeWorkspace; + const auto PWORKSPACE = Desktop::focusState()->monitor()->m_activeWorkspace; 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(); - g_pCompositor->focusWindow(PNEWWINDOW); + Desktop::focusState()->fullWindowFocus(PNEWWINDOW, Desktop::FOCUS_REASON_KEYBIND); PNEWWINDOW->warpCursor(); if (*PNOWARPS == 0 || *PFOLLOWMOUSE < 2) { @@ -373,19 +379,19 @@ bool CKeybindManager::tryMoveFocusToMonitor(PHLMONITOR monitor) { g_pInputManager->m_forcedFocus.reset(); } } else { - g_pCompositor->focusWindow(nullptr); + Desktop::focusState()->rawWindowFocus(nullptr, Desktop::FOCUS_REASON_KEYBIND); g_pCompositor->warpCursorTo(monitor->middle()); } - g_pCompositor->setActiveMonitor(monitor); + Desktop::focusState()->rawMonitorFocus(monitor); return true; } -void CKeybindManager::switchToWindow(PHLWINDOW PWINDOWTOCHANGETO, bool preserveFocusHistory) { +void CKeybindManager::switchToWindow(PHLWINDOW PWINDOWTOCHANGETO, bool forceFSCycle) { static auto PFOLLOWMOUSE = CConfigValue("input:follow_mouse"); static auto PNOWARPS = CConfigValue("cursor:no_warps"); - const auto PLASTWINDOW = g_pCompositor->m_lastWindow.lock(); + const auto PLASTWINDOW = Desktop::focusState()->window(); if (PWINDOWTOCHANGETO == PLASTWINDOW || !PWINDOWTOCHANGETO) return; @@ -393,24 +399,11 @@ void CKeybindManager::switchToWindow(PHLWINDOW PWINDOWTOCHANGETO, bool preserveF // remove constraints g_pInputManager->unconstrainMouse(); - if (PLASTWINDOW && PLASTWINDOW->m_workspace == PWINDOWTOCHANGETO->m_workspace && PLASTWINDOW->isFullscreen()) { - const auto PWORKSPACE = PLASTWINDOW->m_workspace; - const auto MODE = PWORKSPACE->m_fullscreenMode; - - if (!PWINDOWTOCHANGETO->m_pinned) - g_pCompositor->setWindowFullscreenInternal(PLASTWINDOW, FSMODE_NONE); - - g_pCompositor->focusWindow(PWINDOWTOCHANGETO, nullptr, preserveFocusHistory); - - if (!PWINDOWTOCHANGETO->m_pinned) - g_pCompositor->setWindowFullscreenInternal(PWINDOWTOCHANGETO, MODE); - - // warp the position + size animation, otherwise it looks weird. - PWINDOWTOCHANGETO->m_realPosition->warp(); - PWINDOWTOCHANGETO->m_realSize->warp(); - } else { + if (PLASTWINDOW && PLASTWINDOW->m_workspace == PWINDOWTOCHANGETO->m_workspace && PLASTWINDOW->isFullscreen()) + Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, Desktop::FOCUS_REASON_KEYBIND, nullptr, forceFSCycle); + else { updateRelativeCursorCoords(); - g_pCompositor->focusWindow(PWINDOWTOCHANGETO, nullptr, preserveFocusHistory); + 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 @@ -424,7 +417,7 @@ void CKeybindManager::switchToWindow(PHLWINDOW PWINDOWTOCHANGETO, bool preserveF // event const auto PNEWMON = PWINDOWTOCHANGETO->m_monitor.lock(); - g_pCompositor->setActiveMonitor(PNEWMON); + Desktop::focusState()->rawMonitorFocus(PNEWMON); } } }; @@ -439,7 +432,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) @@ -507,7 +500,7 @@ 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; } @@ -592,7 +585,7 @@ 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; } @@ -656,16 +649,22 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP 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) { @@ -680,7 +679,7 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP if (!k->locked && g_pSessionLockManager->isSessionLocked()) continue; - if (!IGNORECONDITIONS && ((modmask != k->modmask && !k->ignoreMods) || k->submap != m_currentSelectedSubmap || k->shadowed)) + if (!IGNORECONDITIONS && ((modmask != k->modmask && !k->ignoreMods) || (k->submap != m_currentSelectedSubmap && !k->submapUniversal) || k->shadowed)) continue; if (k->multiKey) { @@ -754,9 +753,9 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP // Require mouse to stay inside drag_threshold for clicks, outside for drags // Check if either a mouse bind has triggered or currently over the threshold (maybe there is no mouse bind on the same key) const auto THRESHOLDREACHED = key.mousePosAtPress.distanceSq(g_pInputManager->getMouseCoordsInternal()) > std::pow(*PDRAGTHRESHOLD, 2); - if (k->click && (g_pInputManager->m_dragThresholdReached || THRESHOLDREACHED)) + if (k->click && (g_layoutManager->dragController()->dragThresholdReached() || THRESHOLDREACHED)) continue; - else if (k->drag && !g_pInputManager->m_dragThresholdReached && !THRESHOLDREACHED) + else if (k->drag && !g_layoutManager->dragController()->dragThresholdReached() && !THRESHOLDREACHED) continue; } @@ -778,10 +777,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); @@ -813,13 +812,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()) @@ -886,7 +885,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); } @@ -913,11 +912,13 @@ bool CKeybindManager::handleInternalKeybinds(xkb_keysym_t keysym) { // Dispatchers SDispatchResult CKeybindManager::spawn(std::string args) { - const uint64_t PROC = spawnWithRules(args, nullptr); - return {.success = PROC > 0, .error = std::format("Failed to start process {}", args)}; + const auto PROC = spawnWithRules(args, nullptr); + if (!PROC.has_value()) + return {.success = false, .error = std::format("Failed to start process. No closing bracket in exec rule. {}", args)}; + return {.success = PROC.value() > 0, .error = std::format("Failed to start process {}", args)}; } -uint64_t CKeybindManager::spawnWithRules(std::string args, PHLWORKSPACE pInitialWorkspace) { +std::optional CKeybindManager::spawnWithRules(std::string args, PHLWORKSPACE pInitialWorkspace) { args = trim(args); @@ -925,21 +926,30 @@ uint64_t CKeybindManager::spawnWithRules(std::string args, PHLWORKSPACE pInitial if (args[0] == '[') { // we have exec rules - RULES = args.substr(1, args.substr(1).find_first_of(']')); - args = args.substr(args.find_first_of(']') + 1); + const auto end = args.find_first_of(']'); + if (end == std::string::npos) + return std::nullopt; + + RULES = args.substr(1, end - 1); + args = args.substr(end + 1); } - const uint64_t PROC = spawnRawProc(args, pInitialWorkspace); + std::string execToken = ""; if (!RULES.empty()) { - const auto RULESLIST = CVarList(RULES, 0, ';'); + auto rule = Desktop::Rule::CWindowRule::buildFromExecString(std::move(RULES)); - for (auto const& r : RULESLIST) { - g_pConfigManager->addExecRule({r, sc(PROC)}); - } + const auto TOKEN = g_pTokenManager->registerNewToken(nullptr, std::chrono::seconds(1)); - Debug::log(LOG, "Applied {} rule arguments for exec.", RULESLIST.size()); + const uint64_t PROC = spawnRawProc(args, pInitialWorkspace, TOKEN); + rule->markAsExecRule(TOKEN, PROC, false /* TODO: could be nice. */); + rule->registerMatch(Desktop::Rule::RULE_PROP_EXEC_TOKEN, TOKEN); + rule->registerMatch(Desktop::Rule::RULE_PROP_EXEC_PID, std::to_string(PROC)); + Desktop::Rule::ruleEngine()->registerRule(std::move(rule)); + Log::logger->log(Log::DEBUG, "Applied rule arguments for exec."); + return PROC; } + const uint64_t PROC = spawnRawProc(args, pInitialWorkspace, execToken); return PROC; } @@ -949,14 +959,14 @@ SDispatchResult CKeybindManager::spawnRaw(std::string args) { return {.success = PROC > 0, .error = std::format("Failed to start process {}", args)}; } -uint64_t CKeybindManager::spawnRawProc(std::string args, PHLWORKSPACE pInitialWorkspace) { - Debug::log(LOG, "Executing {}", args); +uint64_t CKeybindManager::spawnRawProc(std::string args, PHLWORKSPACE pInitialWorkspace, const std::string& execRuleToken) { + Log::logger->log(Log::DEBUG, "Executing {}", args); const auto HLENV = getHyprlandLaunchEnv(pInitialWorkspace); pid_t child = fork(); if (child < 0) { - Debug::log(LOG, "Fail to fork"); + Log::logger->log(Log::DEBUG, "Fail to fork"); return 0; } if (child == 0) { @@ -971,6 +981,8 @@ uint64_t CKeybindManager::spawnRawProc(std::string args, PHLWORKSPACE pInitialWo 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) { @@ -986,16 +998,16 @@ uint64_t CKeybindManager::spawnRawProc(std::string args, PHLWORKSPACE pInitialWo } // run in parent - Debug::log(LOG, "Process Created with pid {}", child); + Log::logger->log(Log::DEBUG, "Process Created with pid {}", child); return child; } SDispatchResult CKeybindManager::killActive(std::string args) { - const auto PWINDOW = g_pCompositor->m_lastWindow.lock(); + 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"}; } @@ -1005,10 +1017,10 @@ SDispatchResult CKeybindManager::killActive(std::string args) { } SDispatchResult CKeybindManager::closeActive(std::string args) { - if (g_pCompositor->m_lastWindow && g_pCompositor->m_lastWindow->m_closeableSince > Time::steadyNow()) + if (Desktop::focusState()->window() && Desktop::focusState()->window()->m_closeableSince > Time::steadyNow()) return {.success = false, .error = "can't close window, it's not closeable yet (noclosefor)"}; - g_pCompositor->closeWindow(g_pCompositor->m_lastWindow.lock()); + g_pCompositor->closeWindow(Desktop::focusState()->window()); return {}; } @@ -1017,7 +1029,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"}; } @@ -1033,7 +1045,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"}; } @@ -1049,16 +1061,16 @@ 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(g_pCompositor->m_lastWindow.lock()->getPID(), 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)}; } - kill(g_pCompositor->m_lastWindow.lock()->getPID(), std::stoi(args)); + kill(Desktop::focusState()->window()->getPID(), std::stoi(args)); return {}; } @@ -1070,7 +1082,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"}; } @@ -1080,12 +1092,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)}; } @@ -1102,7 +1114,7 @@ static SDispatchResult toggleActiveFloatingCore(std::string args, std::optional< if (args != "active" && args.length() > 1) PWINDOW = g_pCompositor->getWindowByRegex(args); else - PWINDOW = g_pCompositor->m_lastWindow.lock(); + PWINDOW = Desktop::focusState()->window(); if (!PWINDOW) return {.success = false, .error = "Window not found"}; @@ -1111,32 +1123,16 @@ static SDispatchResult toggleActiveFloatingCore(std::string args, std::optional< return {}; // remove drag status - if (!g_pInputManager->m_currentlyDraggedWindow.expired()) - g_pKeybindManager->changeMouseBindMode(MBIND_INVALID); + if (g_layoutManager->dragController()->target()) + CKeybindManager::changeMouseBindMode(MBIND_INVALID); - if (PWINDOW->m_groupData.pNextWindow.lock() && PWINDOW->m_groupData.pNextWindow.lock() != PWINDOW) { - const auto PCURRENT = PWINDOW->getGroupCurrent(); - - PCURRENT->m_isFloating = !PCURRENT->m_isFloating; - g_pLayoutManager->getCurrentLayout()->changeWindowFloatingMode(PCURRENT); - - PHLWINDOW curr = PCURRENT->m_groupData.pNextWindow.lock(); - while (curr != PCURRENT) { - curr->m_isFloating = PCURRENT->m_isFloating; - curr = curr->m_groupData.pNextWindow.lock(); - } - } else { - PWINDOW->m_isFloating = !PWINDOW->m_isFloating; - - g_pLayoutManager->getCurrentLayout()->changeWindowFloatingMode(PWINDOW); - } + g_layoutManager->changeFloatingMode(PWINDOW->layoutTarget()); if (PWINDOW->m_workspace) { PWINDOW->m_workspace->updateWindows(); PWINDOW->m_workspace->updateWindowData(); } - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID()); g_pCompositor->updateAllWindowsAnimatedDecorationValues(); return {}; @@ -1155,19 +1151,14 @@ SDispatchResult CKeybindManager::setActiveTiled(std::string args) { } SDispatchResult CKeybindManager::centerWindow(std::string args) { - const auto PWINDOW = g_pCompositor->m_lastWindow.lock(); + const auto PWINDOW = Desktop::focusState()->window(); if (!PWINDOW || !PWINDOW->m_isFloating || PWINDOW->isFullscreen()) return {.success = false, .error = "No floating window found"}; 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 {}; } @@ -1178,15 +1169,12 @@ SDispatchResult CKeybindManager::toggleActivePseudo(std::string args) { if (args != "active" && args.length() > 1) PWINDOW = g_pCompositor->getWindowByRegex(args); else - PWINDOW = g_pCompositor->m_lastWindow.lock(); + PWINDOW = Desktop::focusState()->window(); 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 {}; } @@ -1197,10 +1185,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}; } @@ -1215,11 +1204,10 @@ 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"); - const auto PMONITOR = g_pCompositor->m_lastMonitor.lock(); + const auto PMONITOR = Desktop::focusState()->monitor(); if (!PMONITOR) return {.success = false, .error = "Last monitor not found"}; @@ -1229,14 +1217,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)) { @@ -1269,15 +1258,7 @@ SDispatchResult CKeybindManager::changeworkspace(std::string args) { updateRelativeCursorCoords(); - g_pCompositor->setActiveMonitor(PMONITORWORKSPACEOWNER); - - if (BISWORKSPACECURRENT) { - if (*PALLOWWORKSPACECYCLES) - pWorkspaceToChangeTo->rememberPrevWorkspace(PCURRENTWORKSPACE); - else if (!EXPLICITPREVIOUS && !*PBACKANDFORTH) - pWorkspaceToChangeTo->rememberPrevWorkspace(nullptr); - } else - pWorkspaceToChangeTo->rememberPrevWorkspace(PCURRENTWORKSPACE); + Desktop::focusState()->rawMonitorFocus(PMONITORWORKSPACEOWNER); if (*PHIDESPECIALONWORKSPACECHANGE) PMONITORWORKSPACEOWNER->setSpecialWorkspace(nullptr); @@ -1286,7 +1267,7 @@ SDispatchResult CKeybindManager::changeworkspace(std::string args) { if (PMONITOR != PMONITORWORKSPACEOWNER) { Vector2D middle = PMONITORWORKSPACEOWNER->middle(); if (const auto PLAST = pWorkspaceToChangeTo->getLastFocusedWindow(); PLAST) { - g_pCompositor->focusWindow(PLAST); + Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND); if (*PWORKSPACECENTERON == 1) middle = PLAST->middle(); } @@ -1294,7 +1275,7 @@ SDispatchResult CKeybindManager::changeworkspace(std::string args) { } if (!g_pInputManager->m_lastFocusOnLS) { - if (g_pCompositor->m_lastFocus) + if (Desktop::focusState()->surface()) g_pInputManager->sendMotionEventsToFocused(); else g_pInputManager->simulateMouseMovement(); @@ -1304,9 +1285,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); } @@ -1314,7 +1295,7 @@ SDispatchResult CKeybindManager::changeworkspace(std::string args) { } SDispatchResult CKeybindManager::fullscreenActive(std::string args) { - const auto PWINDOW = g_pCompositor->m_lastWindow.lock(); + const auto PWINDOW = Desktop::focusState()->window(); const auto ARGS = CConstVarList(args, 2, ' '); if (!PWINDOW) @@ -1338,13 +1319,13 @@ SDispatchResult CKeybindManager::fullscreenActive(std::string args) { } SDispatchResult CKeybindManager::fullscreenStateActive(std::string args) { - const auto PWINDOW = g_pCompositor->m_lastWindow.lock(); + const auto PWINDOW = Desktop::focusState()->window(); const auto ARGS = CVarList(args, 3, ' '); if (!PWINDOW) return {.success = false, .error = "Window not found"}; - PWINDOW->m_windowData.syncFullscreen = CWindowOverridableVar(false, PRIORITY_SET_PROP); + PWINDOW->m_ruleApplicator->syncFullscreenOverride(Desktop::Types::COverridableVar(false, Desktop::Types::PRIORITY_SET_PROP)); int internalMode, clientMode; try { @@ -1354,23 +1335,25 @@ 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") { g_pCompositor->setWindowFullscreenState(PWINDOW, STATE); } - PWINDOW->m_windowData.syncFullscreen = CWindowOverridableVar(PWINDOW->m_fullscreenState.internal == PWINDOW->m_fullscreenState.client, PRIORITY_SET_PROP); + PWINDOW->m_ruleApplicator->syncFullscreenOverride( + Desktop::Types::COverridableVar(PWINDOW->m_fullscreenState.internal == PWINDOW->m_fullscreenState.client, Desktop::Types::PRIORITY_SET_PROP)); return {}; } @@ -1383,7 +1366,7 @@ SDispatchResult CKeybindManager::moveActiveToWorkspace(std::string args) { PWINDOW = g_pCompositor->getWindowByRegex(args.substr(args.find_last_of(',') + 1)); args = args.substr(0, args.find_last_of(',')); } else { - PWINDOW = g_pCompositor->m_lastWindow.lock(); + PWINDOW = Desktop::focusState()->window(); } if (!PWINDOW) @@ -1391,19 +1374,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(); @@ -1413,7 +1395,7 @@ SDispatchResult CKeybindManager::moveActiveToWorkspace(std::string args) { const auto FULLSCREENMODE = PWINDOW->m_fullscreenState.internal; g_pCompositor->moveWindowToWorkspaceSafe(PWINDOW, pWorkspace); pMonitor = pWorkspace->m_monitor.lock(); - g_pCompositor->setActiveMonitor(pMonitor); + Desktop::focusState()->rawMonitorFocus(pMonitor); g_pCompositor->setWindowFullscreenInternal(PWINDOW, FULLSCREENMODE); } else { pWorkspace = g_pCompositor->createNewWorkspace(WORKSPACEID, PWINDOW->monitorID(), workspaceName, false); @@ -1428,12 +1410,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); - g_pCompositor->focusWindow(PWINDOW); + Desktop::focusState()->fullWindowFocus(PWINDOW, Desktop::FOCUS_REASON_KEYBIND); PWINDOW->warpCursor(); return {}; @@ -1446,7 +1425,7 @@ SDispatchResult CKeybindManager::moveActiveToWorkspaceSilent(std::string args) { PWINDOW = g_pCompositor->getWindowByRegex(args.substr(args.find_last_of(',') + 1)); args = args.substr(0, args.find_last_of(',')); } else { - PWINDOW = g_pCompositor->m_lastWindow.lock(); + PWINDOW = Desktop::focusState()->window(); } if (!PWINDOW) @@ -1454,7 +1433,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"}; } @@ -1473,9 +1452,11 @@ SDispatchResult CKeybindManager::moveActiveToWorkspaceSilent(std::string args) { g_pCompositor->moveWindowToWorkspaceSafe(PWINDOW, pWorkspace); } - if (PWINDOW == g_pCompositor->m_lastWindow) { - if (const auto PATCOORDS = g_pCompositor->vectorToWindowUnified(OLDMIDDLE, RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING, PWINDOW); PATCOORDS) - g_pCompositor->focusWindow(PATCOORDS); + if (PWINDOW == Desktop::focusState()->window()) { + 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(); } @@ -1484,94 +1465,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 = g_pCompositor->m_lastWindow.lock(); - if (!PLASTWINDOW) { + const auto PLASTWINDOW = Desktop::focusState()->window(); + if (!PLASTWINDOW || !PLASTWINDOW->aliveAndVisible()) { if (*PMONITORFALLBACK) - tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(arg)); - + tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(dir)); return {}; } const auto PWINDOWTOCHANGETO = *PFULLCYCLE && PLASTWINDOW->isFullscreen() ? - g_pCompositor->getWindowCycle(PLASTWINDOW, true, {}, false, arg != 'd' && arg != 'b' && arg != 'r') : - g_pCompositor->getWindowInDirection(PLASTWINDOW, arg); + g_pCompositor->getWindowCycle(PLASTWINDOW, true, {}, false, dir != Math::DIRECTION_DOWN && dir != Math::DIRECTION_RIGHT) : + g_pCompositor->getWindowInDirection(PLASTWINDOW, dir); // Prioritize focus change within groups if the window is a part of it. - if (*PGROUPCYCLE && PLASTWINDOW->m_groupData.pNextWindow) { + if (*PGROUPCYCLE && PLASTWINDOW->m_group) { auto isTheOnlyGroupOnWs = !PWINDOWTOCHANGETO && g_pCompositor->m_monitors.size() == 1; - if (arg == 'l' && (PLASTWINDOW != PLASTWINDOW->getGroupHead() || isTheOnlyGroupOnWs)) { - PLASTWINDOW->setGroupCurrent(PLASTWINDOW->getGroupPrevious()); + if (dir == Math::DIRECTION_LEFT && (PLASTWINDOW != PLASTWINDOW->m_group->head() || isTheOnlyGroupOnWs)) { + PLASTWINDOW->m_group->moveCurrent(false); return {}; } - else if (arg == 'r' && (PLASTWINDOW != PLASTWINDOW->getGroupTail() || isTheOnlyGroupOnWs)) { - PLASTWINDOW->setGroupCurrent(PLASTWINDOW->m_groupData.pNextWindow.lock()); + else if (dir == Math::DIRECTION_RIGHT && (PLASTWINDOW != PLASTWINDOW->m_group->tail() || isTheOnlyGroupOnWs)) { + PLASTWINDOW->m_group->moveCurrent(true); return {}; } } // Found window in direction, switch to it if (PWINDOWTOCHANGETO) { - switchToWindow(PWINDOWTOCHANGETO); + 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); @@ -1579,9 +1558,9 @@ SDispatchResult CKeybindManager::moveFocusTo(std::string args) { } SDispatchResult CKeybindManager::focusUrgentOrLast(std::string args) { - const auto PWINDOWURGENT = g_pCompositor->getUrgentWindow(); - const auto PWINDOWPREV = g_pCompositor->m_lastWindow.lock() ? (g_pCompositor->m_windowFocusHistory.size() < 2 ? nullptr : g_pCompositor->m_windowFocusHistory[1].lock()) : - (g_pCompositor->m_windowFocusHistory.empty() ? nullptr : g_pCompositor->m_windowFocusHistory[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"}; @@ -1592,8 +1571,12 @@ SDispatchResult CKeybindManager::focusUrgentOrLast(std::string args) { } SDispatchResult CKeybindManager::focusCurrentOrLast(std::string args) { - const auto PWINDOWPREV = g_pCompositor->m_lastWindow.lock() ? (g_pCompositor->m_windowFocusHistory.size() < 2 ? nullptr : g_pCompositor->m_windowFocusHistory[1].lock()) : - (g_pCompositor->m_windowFocusHistory.empty() ? nullptr : g_pCompositor->m_windowFocusHistory[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"}; @@ -1604,8 +1587,7 @@ SDispatchResult CKeybindManager::focusCurrentOrLast(std::string args) { } SDispatchResult CKeybindManager::swapActive(std::string args) { - char arg = args[0]; - const auto PLASTWINDOW = g_pCompositor->m_lastWindow.lock(); + const auto PLASTWINDOW = Desktop::focusState()->window(); PHLWINDOW PWINDOWTOCHANGETO = nullptr; if (!PLASTWINDOW) @@ -1614,26 +1596,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); @@ -1651,12 +1633,13 @@ 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 = g_pCompositor->m_lastWindow.lock(); + const auto PLASTWINDOW = Desktop::focusState()->window(); if (!PLASTWINDOW) return {.success = false, .error = "Window to move not found"}; @@ -1664,65 +1647,17 @@ 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 {}; } SDispatchResult CKeybindManager::toggleGroup(std::string args) { - const auto PWINDOW = g_pCompositor->m_lastWindow.lock(); + const auto PWINDOW = Desktop::focusState()->window(); if (!PWINDOW) return {.success = false, .error = "Window not found"}; @@ -1730,102 +1665,43 @@ 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 {}; } SDispatchResult CKeybindManager::changeGroupActive(std::string args) { - const auto PWINDOW = g_pCompositor->m_lastWindow.lock(); + const auto PWINDOW = Desktop::focusState()->window(); 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()); - } else { - PWINDOW->setGroupCurrent(PWINDOW->getGroupPrevious()); - } - - return {}; -} - -SDispatchResult CKeybindManager::toggleSplit(std::string args) { - SLayoutMessageHeader header; - header.pWindow = g_pCompositor->m_lastWindow.lock(); - - 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 = g_pCompositor->m_lastWindow.lock(); - - 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 = g_pCompositor->m_lastWindow.lock(); - - if (!PLASTWINDOW) - return {.success = false, .error = "Window not found"}; - - g_pLayoutManager->getCurrentLayout()->alterSplitRatio(PLASTWINDOW, splitResult.value(), exact); + if (args != "b" && args != "prev") + PWINDOW->m_group->moveCurrent(true); + else + PWINDOW->m_group->moveCurrent(false); return {}; } @@ -1839,18 +1715,18 @@ 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."}; } - const auto PWINDOW = g_pCompositor->m_lastWindow.lock(); + const auto PWINDOW = Desktop::focusState()->window(); if (!PWINDOW) return {.success = false, .error = "Window not found"}; @@ -1884,7 +1760,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"}; } @@ -1892,11 +1768,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."}; } @@ -1910,58 +1786,7 @@ SDispatchResult CKeybindManager::moveCursor(std::string args) { } SDispatchResult CKeybindManager::workspaceOpt(std::string args) { - - // current workspace - const auto PWORKSPACE = g_pCompositor->m_lastMonitor->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(g_pCompositor->m_lastMonitor->m_id); - - return {}; + return {.success = false, .error = "workspaceopt is deprecated"}; } SDispatchResult CKeybindManager::renameWorkspace(std::string args) { @@ -1979,7 +1804,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())}; } @@ -2000,14 +1825,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 = g_pCompositor->m_lastMonitor->m_activeWorkspace; + 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!"}; } @@ -2026,21 +1851,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!"}; } @@ -2052,14 +1877,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 = g_pCompositor->m_lastMonitor.lock(); + 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!"}; } @@ -2073,7 +1898,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 @@ -2087,7 +1912,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) { @@ -2106,12 +1931,12 @@ 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"}; } bool requestedWorkspaceIsAlreadyOpen = false; - const auto PMONITOR = g_pCompositor->m_lastMonitor; + const auto PMONITOR = Desktop::focusState()->monitor(); auto specialOpenOnMonitor = PMONITOR->activeSpecialWorkspaceID(); for (auto const& m : g_pCompositor->m_monitors) { @@ -2127,12 +1952,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) @@ -2147,9 +1972,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); } @@ -2177,7 +2002,7 @@ SDispatchResult CKeybindManager::forceRendererReload(std::string args) { } SDispatchResult CKeybindManager::resizeActive(std::string args) { - const auto PLASTWINDOW = g_pCompositor->m_lastWindow.lock(); + const auto PLASTWINDOW = Desktop::focusState()->window(); if (!PLASTWINDOW) return {.success = false, .error = "No window found"}; @@ -2190,7 +2015,7 @@ SDispatchResult CKeybindManager::resizeActive(std::string args) { if (SIZ.x < 1 || SIZ.y < 1) return {.success = false, .error = "Invalid size provided"}; - g_pLayoutManager->getCurrentLayout()->resizeActiveWindow(SIZ - PLASTWINDOW->m_realSize->goal()); + g_layoutManager->resizeTarget(SIZ - PLASTWINDOW->m_realSize->goal(), PLASTWINDOW->layoutTarget()); if (PLASTWINDOW->m_realSize->goal().x > 1 && PLASTWINDOW->m_realSize->goal().y > 1) PLASTWINDOW->setHidden(false); @@ -2199,7 +2024,7 @@ SDispatchResult CKeybindManager::resizeActive(std::string args) { } SDispatchResult CKeybindManager::moveActive(std::string args) { - const auto PLASTWINDOW = g_pCompositor->m_lastWindow.lock(); + const auto PLASTWINDOW = Desktop::focusState()->window(); if (!PLASTWINDOW) return {.success = false, .error = "No window found"}; @@ -2209,7 +2034,7 @@ SDispatchResult CKeybindManager::moveActive(std::string args) { const auto POS = g_pCompositor->parseWindowVectorArgsRelative(args, PLASTWINDOW->m_realPosition->goal()); - g_pLayoutManager->getCurrentLayout()->moveActiveWindow(POS - PLASTWINDOW->m_realPosition->goal()); + g_layoutManager->moveTarget(POS - PLASTWINDOW->m_realPosition->goal(), PLASTWINDOW->layoutTarget()); return {}; } @@ -2222,7 +2047,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"}; } @@ -2231,7 +2056,7 @@ SDispatchResult CKeybindManager::moveWindow(std::string args) { const auto POS = g_pCompositor->parseWindowVectorArgsRelative(MOVECMD, PWINDOW->m_realPosition->goal()); - g_pLayoutManager->getCurrentLayout()->moveActiveWindow(POS - PWINDOW->m_realPosition->goal(), PWINDOW); + g_layoutManager->moveTarget(POS - PWINDOW->m_realPosition->goal(), PWINDOW->layoutTarget()); return {}; } @@ -2244,7 +2069,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"}; } @@ -2256,7 +2081,7 @@ SDispatchResult CKeybindManager::resizeWindow(std::string args) { if (SIZ.x < 1 || SIZ.y < 1) return {.success = false, .error = "Invalid size provided"}; - g_pLayoutManager->getCurrentLayout()->resizeActiveWindow(SIZ - PWINDOW->m_realSize->goal(), CORNER_NONE, PWINDOW); + g_layoutManager->resizeTarget(SIZ - PWINDOW->m_realSize->goal(), PWINDOW->layoutTarget(), Layout::CORNER_NONE); if (PWINDOW->m_realSize->goal().x > 1 && PWINDOW->m_realSize->goal().y > 1) PWINDOW->setHidden(false); @@ -2265,9 +2090,9 @@ SDispatchResult CKeybindManager::resizeWindow(std::string args) { } SDispatchResult CKeybindManager::circleNext(std::string arg) { - if (g_pCompositor->m_lastWindow.expired()) { + if (!Desktop::focusState()->window()) { // if we have a clear focus, find the first window and get the next focusable. - const auto PWS = g_pCompositor->m_lastMonitor->m_activeWorkspace; + const auto PWS = Desktop::focusState()->monitor()->m_activeWorkspace; if (PWS && PWS->getWindows() > 0) { const auto PWINDOW = PWS->getFirstWindow(); switchToWindow(PWINDOW); @@ -2278,18 +2103,36 @@ 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(g_pCompositor->m_lastWindow, true, floatStatus, VISIBLE, NEXT) : - g_pCompositor->getWindowCycle(g_pCompositor->m_lastWindow.lock(), true, floatStatus, VISIBLE, PREV); + const auto& w = HIST ? g_pCompositor->getWindowCycleHist(Desktop::focusState()->window(), true, floatStatus, VISIBLE, NEXT) : + g_pCompositor->getWindowCycle(Desktop::focusState()->window(), true, floatStatus, VISIBLE, PREV); switchToWindow(w, HIST); @@ -2302,50 +2145,23 @@ 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"}; } updateRelativeCursorCoords(); - if (g_pCompositor->m_lastMonitor && g_pCompositor->m_lastMonitor->m_activeWorkspace != PWINDOW->m_workspace && - g_pCompositor->m_lastMonitor->m_activeSpecialWorkspace != PWINDOW->m_workspace) { - Debug::log(LOG, "Fake executing workspace to move focus"); + if (Desktop::focusState()->monitor() && Desktop::focusState()->monitor()->m_activeWorkspace != PWINDOW->m_workspace && + Desktop::focusState()->monitor()->m_activeSpecialWorkspace != PWINDOW->m_workspace) { + Log::logger->log(Log::DEBUG, "Fake executing workspace to move focus"); changeworkspace(PWORKSPACE->getConfigName()); } - if (PWORKSPACE->m_hasFullscreenWindow) { - const auto FSWINDOW = PWORKSPACE->getFullscreenWindow(); - const auto FSMODE = PWORKSPACE->m_fullscreenMode; - - if (PWINDOW->m_isFloating) { - // don't make floating implicitly fs - if (!PWINDOW->m_createdOverFullscreen) { - g_pCompositor->changeWindowZOrder(PWINDOW, true); - g_pDesktopAnimationManager->setFullscreenFadeAnimation( - PWORKSPACE, PWORKSPACE->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT); - } - - g_pCompositor->focusWindow(PWINDOW); - } else { - if (FSWINDOW != PWINDOW && !PWINDOW->m_pinned) - g_pCompositor->setWindowFullscreenClient(FSWINDOW, FSMODE_NONE); - - g_pCompositor->focusWindow(PWINDOW); - - if (FSWINDOW != PWINDOW && !PWINDOW->m_pinned) - g_pCompositor->setWindowFullscreenClient(PWINDOW, FSMODE); - - // warp the position + size animation, otherwise it looks weird. - PWINDOW->m_realPosition->warp(); - PWINDOW->m_realSize->warp(); - } - } else - g_pCompositor->focusWindow(PWINDOW); + Desktop::focusState()->fullWindowFocus(PWINDOW, Desktop::FOCUS_REASON_KEYBIND, nullptr, false); PWINDOW->warpCursor(); @@ -2357,22 +2173,22 @@ SDispatchResult CKeybindManager::tagWindow(std::string args) { CVarList vars{args, 0, 's', true}; if (vars.size() == 1) - PWINDOW = g_pCompositor->m_lastWindow.lock(); + PWINDOW = Desktop::focusState()->window(); else if (vars.size() == 2) PWINDOW = g_pCompositor->getWindowByRegex(vars[1]); else return {.success = false, .error = "Invalid number of arguments, expected 1 or 2 arguments"}; - if (PWINDOW && PWINDOW->m_tags.applyTag(vars[0])) { - PWINDOW->updateDynamicRules(); - g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW->m_self.lock()); + if (PWINDOW && PWINDOW->m_ruleApplicator->m_tagKeeper.applyTag(vars[0])) { + PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_TAG); + PWINDOW->updateDecorationValues(); } return {}; } SDispatchResult CKeybindManager::toggleSwallow(std::string args) { - PHLWINDOWREF pWindow = g_pCompositor->m_lastWindow; + PHLWINDOWREF pWindow = Desktop::focusState()->window(); if (!valid(pWindow) || !valid(pWindow->m_swallowed)) return {}; @@ -2381,12 +2197,12 @@ SDispatchResult CKeybindManager::toggleSwallow(std::string args) { // Unswallow pWindow->m_swallowed->m_currentlySwallowed = false; pWindow->m_swallowed->setHidden(false); - g_pLayoutManager->getCurrentLayout()->onWindowCreated(pWindow->m_swallowed.lock()); + g_layoutManager->newTarget(pWindow->m_swallowed->layoutTarget(), pWindow->m_workspace->m_space); } else { // Reswallow pWindow->m_swallowed->m_currentlySwallowed = true; pWindow->m_swallowed->setHidden(true); - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow->m_swallowed.lock()); + g_layoutManager->removeTarget(pWindow->m_swallowed->layoutTarget()); } return {}; @@ -2395,23 +2211,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)}; } @@ -2421,25 +2237,25 @@ 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?"}; } - const auto XWTOXW = PWINDOW->m_isX11 && g_pCompositor->m_lastWindow.lock() && g_pCompositor->m_lastWindow->m_isX11; + const auto XWTOXW = PWINDOW->m_isX11 && Desktop::focusState()->window() && Desktop::focusState()->window()->m_isX11; const auto LASTMOUSESURF = g_pSeatManager->m_state.pointerFocus.lock(); const auto LASTKBSURF = g_pSeatManager->m_state.keyboardFocus.lock(); // 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); @@ -2495,7 +2311,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"}; } @@ -2513,7 +2329,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 { @@ -2528,7 +2344,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"}; } @@ -2552,7 +2368,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"}; } @@ -2561,13 +2377,13 @@ 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"}; } const std::string regexp = ARGS[2]; PHLWINDOW PWINDOW = nullptr; - const auto LASTSURFACE = g_pCompositor->m_lastFocus.lock(); + const auto LASTSURFACE = Desktop::focusState()->surface(); //if regexp is not empty, send shortcut to current window //else, don't change focus @@ -2575,27 +2391,27 @@ 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 && g_pCompositor->m_lastWindow && !g_pCompositor->m_lastWindow->m_isX11) - g_pXWaylandManager->activateSurface(PWINDOW->m_wlSurface->resource(), true); + if (PWINDOW && PWINDOW->m_isX11 && Desktop::focusState()->window() && !Desktop::focusState()->window()->m_isX11) + g_pXWaylandManager->activateSurface(PWINDOW->wlSurface()->resource(), true); // if xwl -> xwl, send to current. Timing issues make this not work. - if (PWINDOW && PWINDOW->m_isX11 && g_pCompositor->m_lastWindow && g_pCompositor->m_lastWindow->m_isX11) + if (PWINDOW && PWINDOW->m_isX11 && Desktop::focusState()->window() && Desktop::focusState()->window()->m_isX11) PWINDOW = nullptr; g_pSeatManager->sendKeyboardMods(MOD, 0, 0, 0); @@ -2647,9 +2463,9 @@ SDispatchResult CKeybindManager::sendshortcut(std::string args) { } SDispatchResult CKeybindManager::layoutmsg(std::string msg) { - SLayoutMessageHeader hd = {g_pCompositor->m_lastWindow.lock()}; - g_pLayoutManager->getCurrentLayout()->layoutMessage(hd, msg); - + auto ret = g_layoutManager->layoutMsg(msg); + if (!ret) + return {.success = false, .error = ret.error()}; return {}; } @@ -2662,7 +2478,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; @@ -2684,14 +2502,14 @@ SDispatchResult CKeybindManager::swapnext(std::string arg) { PHLWINDOW toSwap = nullptr; - if (g_pCompositor->m_lastWindow.expired()) + if (!Desktop::focusState()->window()) return {}; - const auto PLASTWINDOW = g_pCompositor->m_lastWindow.lock(); + const auto PLASTWINDOW = Desktop::focusState()->window(); const auto PLASTCYCLED = - validMapped(g_pCompositor->m_lastWindow->m_lastCycledWindow) && g_pCompositor->m_lastWindow->m_lastCycledWindow->m_workspace == PLASTWINDOW->m_workspace ? - g_pCompositor->m_lastWindow->m_lastCycledWindow.lock() : + validMapped(Desktop::focusState()->window()->m_lastCycledWindow) && Desktop::focusState()->window()->m_lastCycledWindow->m_workspace == PLASTWINDOW->m_workspace ? + Desktop::focusState()->window()->m_lastCycledWindow.lock() : nullptr; const bool NEED_PREV = arg == "last" || arg == "l" || arg == "prev" || arg == "p"; @@ -2701,11 +2519,11 @@ SDispatchResult CKeybindManager::swapnext(std::string arg) { if (toSwap == PLASTWINDOW) toSwap = g_pCompositor->getWindowCycle(PLASTWINDOW, true, std::nullopt, false, NEED_PREV); - g_pLayoutManager->getCurrentLayout()->switchWindows(PLASTWINDOW, toSwap); + g_layoutManager->switchTargets(PLASTWINDOW->layoutTarget(), toSwap->layoutTarget(), false); PLASTWINDOW->m_lastCycledWindow = toSwap; - g_pCompositor->focusWindow(PLASTWINDOW); + Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_KEYBIND); return {}; } @@ -2735,10 +2553,10 @@ SDispatchResult CKeybindManager::pinActive(std::string args) { if (args != "active" && args.length() > 1) PWINDOW = g_pCompositor->getWindowByRegex(args); else - PWINDOW = g_pCompositor->m_lastWindow.lock(); + 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"}; } @@ -2750,21 +2568,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->moveToWorkspace(PMONITOR->m_activeWorkspace); - PWINDOW->updateDynamicRules(); - g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW); + 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 {}; } @@ -2792,38 +2612,36 @@ 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 {}; } SDispatchResult CKeybindManager::bringActiveToTop(std::string args) { - if (g_pCompositor->m_lastWindow.lock() && g_pCompositor->m_lastWindow->m_isFloating) - g_pCompositor->changeWindowZOrder(g_pCompositor->m_lastWindow.lock(), true); + if (Desktop::focusState()->window() && Desktop::focusState()->window()->m_isFloating) + g_pCompositor->changeWindowZOrder(Desktop::focusState()->window(), true); + + g_pInputManager->simulateMouseMovement(); return {}; } @@ -2833,11 +2651,11 @@ SDispatchResult CKeybindManager::alterZOrder(std::string args) { const auto POSITION = args.substr(0, args.find_first_of(',')); auto PWINDOW = g_pCompositor->getWindowByRegex(WINDOWREGEX); - if (!PWINDOW && g_pCompositor->m_lastWindow.lock() && g_pCompositor->m_lastWindow->m_isFloating) - PWINDOW = g_pCompositor->m_lastWindow.lock(); + if (!PWINDOW && Desktop::focusState()->window() && Desktop::focusState()->window()->m_isFloating) + 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"}; } @@ -2846,7 +2664,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: {}"}; } @@ -2870,48 +2688,42 @@ SDispatchResult CKeybindManager::lockGroups(std::string args) { } SDispatchResult CKeybindManager::lockActiveGroup(std::string args) { - const auto PWINDOW = g_pCompositor->m_lastWindow.lock(); + const auto PWINDOW = Desktop::focusState()->window(); 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); - g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW); + PWINDOW->updateDecorationValues(); return {}; } void CKeybindManager::moveWindowIntoGroup(PHLWINDOW pWindow, PHLWINDOW pWindowInDirection) { - if (pWindow->m_groupData.deny) + if (!pWindowInDirection->m_group || pWindowInDirection->m_group->denied()) return; updateRelativeCursorCoords(); - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow); // This removes grouped property! - if (pWindow->m_monitor != pWindowInDirection->m_monitor) { pWindow->moveToWorkspace(pWindowInDirection->m_workspace); pWindow->m_monitor = pWindowInDirection->m_monitor; } - static auto USECURRPOS = CConfigValue("group:insert_after_current"); - (*USECURRPOS ? pWindowInDirection : pWindowInDirection->getGroupTail())->insertWindowToGroup(pWindow); + pWindowInDirection->m_group->add(pWindow); - pWindowInDirection->setGroupCurrent(pWindow); + pWindowInDirection->m_group->setCurrent(pWindow); pWindow->updateWindowDecos(); - g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow); - g_pCompositor->focusWindow(pWindow); + Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND); pWindow->warpCursor(); g_pEventManager->postEvent(SHyprIPCEvent{"moveintogroup", std::format("{:x}", rc(pWindow.get()))}); @@ -2919,70 +2731,53 @@ void CKeybindManager::moveWindowIntoGroup(PHLWINDOW pWindow, PHLWINDOW pWindowIn void CKeybindManager::moveWindowOutOfGroup(PHLWINDOW pWindow, const std::string& dir) { static auto BFOCUSREMOVEDWINDOW = CConfigValue("group:focus_removed_window"); - const auto PWINDOWPREV = pWindow->getGroupPrevious(); - eDirection direction; - switch (dir[0]) { - case 't': - case 'u': direction = DIRECTION_UP; break; - case 'd': - case 'b': direction = DIRECTION_DOWN; break; - case 'l': direction = DIRECTION_LEFT; break; - case 'r': direction = DIRECTION_RIGHT; break; - default: direction = DIRECTION_DEFAULT; - } + if (!pWindow->m_group) + return; - updateRelativeCursorCoords(); + WP group = pWindow->m_group; - if (pWindow->m_groupData.pNextWindow.lock() == pWindow) { - pWindow->destroyGroup(); - } else { - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow); + const auto direction = !dir.empty() ? Math::fromChar(dir[0]) : Math::DIRECTION_DEFAULT; - const auto GROUPSLOCKEDPREV = g_pKeybindManager->m_groupsLocked; - g_pKeybindManager->m_groupsLocked = true; + pWindow->m_group->remove(pWindow, direction); - g_pLayoutManager->getCurrentLayout()->onWindowCreated(pWindow, direction); - - g_pKeybindManager->m_groupsLocked = GROUPSLOCKEDPREV; - } - - if (*BFOCUSREMOVEDWINDOW) { - g_pCompositor->focusWindow(pWindow); + if (*BFOCUSREMOVEDWINDOW || !group) { + Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND); pWindow->warpCursor(); } else { - g_pCompositor->focusWindow(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 = g_pCompositor->m_lastWindow.lock(); + 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); @@ -3001,12 +2796,12 @@ SDispatchResult CKeybindManager::moveOutOfGroup(std::string args) { if (args != "active" && args.length() > 1) PWINDOW = g_pCompositor->getWindowByRegex(args); else - PWINDOW = g_pCompositor->m_lastWindow.lock(); + PWINDOW = Desktop::focusState()->window(); 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); @@ -3015,16 +2810,15 @@ 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 = g_pCompositor->m_lastWindow.lock(); + const auto PWINDOW = Desktop::focusState()->window(); if (!PWINDOW) return {.success = false, .error = "No window found"}; @@ -3032,39 +2826,40 @@ 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(); } - g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW); + PWINDOW->updateDecorationValues(); return {}; } @@ -3083,16 +2878,16 @@ SDispatchResult CKeybindManager::setIgnoreGroupLock(std::string args) { } SDispatchResult CKeybindManager::denyWindowFromGroup(std::string args) { - const auto PWINDOW = g_pCompositor->m_lastWindow.lock(); - if (!PWINDOW || (PWINDOW && PWINDOW->m_groupData.pNextWindow.lock())) + const auto PWINDOW = Desktop::focusState()->window(); + 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"); - g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW); + PWINDOW->updateDecorationValues(); return {}; } @@ -3115,21 +2910,20 @@ SDispatchResult CKeybindManager::global(std::string args) { SDispatchResult CKeybindManager::moveGroupWindow(std::string args) { const auto BACK = args == "b" || args == "prev"; - const auto PLASTWINDOW = g_pCompositor->m_lastWindow.lock(); + const auto PLASTWINDOW = Desktop::focusState()->window(); 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 {}; } @@ -3142,13 +2936,46 @@ SDispatchResult CKeybindManager::event(std::string args) { #include #include +template +static void parsePropTrivial(Desktop::Types::COverridableVar& prop, const std::string& s) { + static_assert(std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v, + "Invalid type passed to parsePropTrivial"); + + if (s == "unset") { + prop.unset(Desktop::Types::PRIORITY_SET_PROP); + return; + } + + try { + if constexpr (std::is_same_v) { + if (s == "toggle") + prop.increment(true, Desktop::Types::PRIORITY_SET_PROP); + else + prop = Desktop::Types::COverridableVar(truthy(s), Desktop::Types::PRIORITY_SET_PROP); + } else if constexpr (std::is_same_v || std::is_same_v) { + if (s.starts_with("relative")) { + const auto VAL = std::stoi(s.substr(s.find(' ') + 1)); + prop.increment(VAL, Desktop::Types::PRIORITY_SET_PROP); + } else + prop = Desktop::Types::COverridableVar(std::stoull(s), Desktop::Types::PRIORITY_SET_PROP); + } else if constexpr (std::is_same_v) { + if (s.starts_with("relative")) { + const auto VAL = std::stof(s.substr(s.find(' ') + 1)); + prop.increment(VAL, Desktop::Types::PRIORITY_SET_PROP); + } else + 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 (...) { Log::logger->log(Log::ERR, "Hyprctl: parsePropTrivial: failed to parse setprop for {}", s); } +} + SDispatchResult CKeybindManager::setProp(std::string args) { CVarList vars(args, 3, ' '); if (vars.size() < 3) return {.success = false, .error = "Not enough args"}; - const auto PLASTWINDOW = g_pCompositor->m_lastWindow.lock(); + const auto PLASTWINDOW = Desktop::focusState()->window(); const auto PWINDOW = g_pCompositor->getWindowByRegex(vars[0]); if (!PWINDOW) @@ -3157,37 +2984,28 @@ SDispatchResult CKeybindManager::setProp(std::string args) { const auto PROP = vars[1]; const auto VAL = vars[2]; - bool noFocus = PWINDOW->m_windowData.noFocus.valueOrDefault(); + bool noFocus = PWINDOW->m_ruleApplicator->noFocus().valueOrDefault(); try { - if (PROP == "animationstyle") { - PWINDOW->m_windowData.animationStyle = CWindowOverridableVar(VAL, PRIORITY_SET_PROP); - } else if (PROP == "maxsize") { - PWINDOW->m_windowData.maxSize = CWindowOverridableVar(configStringToVector2D(VAL), PRIORITY_SET_PROP); - PWINDOW->clampWindowSize(std::nullopt, PWINDOW->m_windowData.maxSize.value()); + if (PROP == "max_size") { + const auto SIZE = PWINDOW->calculateExpression(VAL); + if (!SIZE) { + Log::logger->log(Log::ERR, "failed to parse {} as an expression", VAL); + throw "failed to parse expression"; + } + PWINDOW->m_ruleApplicator->maxSizeOverride(Desktop::Types::COverridableVar(*SIZE, Desktop::Types::PRIORITY_SET_PROP)); + PWINDOW->clampWindowSize(std::nullopt, PWINDOW->m_ruleApplicator->maxSize().value()); PWINDOW->setHidden(false); - } else if (PROP == "minsize") { - PWINDOW->m_windowData.minSize = CWindowOverridableVar(configStringToVector2D(VAL), PRIORITY_SET_PROP); - PWINDOW->clampWindowSize(PWINDOW->m_windowData.minSize.value(), std::nullopt); + } else if (PROP == "min_size") { + const auto SIZE = PWINDOW->calculateExpression(VAL); + if (!SIZE) { + Log::logger->log(Log::ERR, "failed to parse {} as an expression", VAL); + throw "failed to parse expression"; + } + PWINDOW->m_ruleApplicator->minSizeOverride(Desktop::Types::COverridableVar(*SIZE, Desktop::Types::PRIORITY_SET_PROP)); + PWINDOW->clampWindowSize(PWINDOW->m_ruleApplicator->minSize().value(), std::nullopt); PWINDOW->setHidden(false); - } else if (PROP == "alpha") { - PWINDOW->m_windowData.alpha = CWindowOverridableVar(SAlphaValue{std::stof(VAL), PWINDOW->m_windowData.alpha.valueOrDefault().overridden}, PRIORITY_SET_PROP); - } else if (PROP == "alphainactive") { - PWINDOW->m_windowData.alphaInactive = - CWindowOverridableVar(SAlphaValue{std::stof(VAL), PWINDOW->m_windowData.alphaInactive.valueOrDefault().overridden}, PRIORITY_SET_PROP); - } else if (PROP == "alphafullscreen") { - PWINDOW->m_windowData.alphaFullscreen = - CWindowOverridableVar(SAlphaValue{std::stof(VAL), PWINDOW->m_windowData.alphaFullscreen.valueOrDefault().overridden}, PRIORITY_SET_PROP); - } else if (PROP == "alphaoverride") { - PWINDOW->m_windowData.alpha = - CWindowOverridableVar(SAlphaValue{PWINDOW->m_windowData.alpha.valueOrDefault().alpha, sc(configStringToInt(VAL).value_or(0))}, PRIORITY_SET_PROP); - } else if (PROP == "alphainactiveoverride") { - PWINDOW->m_windowData.alphaInactive = - CWindowOverridableVar(SAlphaValue{PWINDOW->m_windowData.alphaInactive.valueOrDefault().alpha, sc(configStringToInt(VAL).value_or(0))}, PRIORITY_SET_PROP); - } else if (PROP == "alphafullscreenoverride") { - PWINDOW->m_windowData.alphaFullscreen = - CWindowOverridableVar(SAlphaValue{PWINDOW->m_windowData.alphaFullscreen.valueOrDefault().alpha, sc(configStringToInt(VAL).value_or(0))}, PRIORITY_SET_PROP); - } else if (PROP == "activebordercolor" || PROP == "inactivebordercolor") { + } else if (PROP == "active_border_color" || PROP == "inactive_border_color") { CGradientValueData colorData = {}; if (vars.size() > 4) { for (int i = 3; i < sc(vars.size()); ++i) { @@ -3208,53 +3026,114 @@ SDispatchResult CKeybindManager::setProp(std::string args) { colorData.updateColorsOk(); - if (PROP == "activebordercolor") - PWINDOW->m_windowData.activeBorderColor = CWindowOverridableVar(colorData, PRIORITY_SET_PROP); + if (PROP == "active_border_color") + PWINDOW->m_ruleApplicator->activeBorderColorOverride(Desktop::Types::COverridableVar(colorData, Desktop::Types::PRIORITY_SET_PROP)); else - PWINDOW->m_windowData.inactiveBorderColor = CWindowOverridableVar(colorData, PRIORITY_SET_PROP); - } else if (auto search = NWindowProperties::boolWindowProperties.find(PROP); search != NWindowProperties::boolWindowProperties.end()) { - auto pWindowDataElement = search->second(PWINDOW); - if (VAL == "toggle") - pWindowDataElement->increment(true, PRIORITY_SET_PROP); - else if (VAL == "unset") - pWindowDataElement->unset(PRIORITY_SET_PROP); - else - *pWindowDataElement = CWindowOverridableVar(sc(configStringToInt(VAL).value_or(0)), PRIORITY_SET_PROP); - } else if (auto search = NWindowProperties::intWindowProperties.find(PROP); search != NWindowProperties::intWindowProperties.end()) { - if (VAL == "unset") - search->second(PWINDOW)->unset(PRIORITY_SET_PROP); - else if (VAL.starts_with("relative")) { - const Hyprlang::INT V = std::stoi(VAL.substr(VAL.find(' '))); - search->second(PWINDOW)->increment(V, PRIORITY_SET_PROP); - } else if (const auto V = configStringToInt(VAL); V) - *(search->second(PWINDOW)) = CWindowOverridableVar(*V, PRIORITY_SET_PROP); - } else if (auto search = NWindowProperties::floatWindowProperties.find(PROP); search != NWindowProperties::floatWindowProperties.end()) { - if (VAL == "unset") - search->second(PWINDOW)->unset(PRIORITY_SET_PROP); - else if (VAL.starts_with("relative")) { - const auto V = std::stof(VAL.substr(VAL.find(' '))); - search->second(PWINDOW)->increment(V, PRIORITY_SET_PROP); - } else { - const auto V = std::stof(VAL); - *(search->second(PWINDOW)) = CWindowOverridableVar(V, PRIORITY_SET_PROP); - } - } else - return {.success = false, .error = "Prop not found"}; + PWINDOW->m_ruleApplicator->inactiveBorderColorOverride(Desktop::Types::COverridableVar(colorData, Desktop::Types::PRIORITY_SET_PROP)); + } else if (PROP == "opacity") { + PWINDOW->m_ruleApplicator->alphaOverride(Desktop::Types::COverridableVar( + Desktop::Types::SAlphaValue{std::stof(VAL), PWINDOW->m_ruleApplicator->alpha().valueOrDefault().overridden}, Desktop::Types::PRIORITY_SET_PROP)); + } else if (PROP == "opacity_inactive") { + PWINDOW->m_ruleApplicator->alphaInactiveOverride(Desktop::Types::COverridableVar( + Desktop::Types::SAlphaValue{std::stof(VAL), PWINDOW->m_ruleApplicator->alphaInactive().valueOrDefault().overridden}, Desktop::Types::PRIORITY_SET_PROP)); + } else if (PROP == "opacity_fullscreen") { + PWINDOW->m_ruleApplicator->alphaFullscreenOverride(Desktop::Types::COverridableVar( + Desktop::Types::SAlphaValue{std::stof(VAL), PWINDOW->m_ruleApplicator->alphaFullscreen().valueOrDefault().overridden}, Desktop::Types::PRIORITY_SET_PROP)); + } else if (PROP == "opacity_override") { + PWINDOW->m_ruleApplicator->alphaOverride(Desktop::Types::COverridableVar( + Desktop::Types::SAlphaValue{PWINDOW->m_ruleApplicator->alpha().valueOrDefault().alpha, sc(configStringToInt(VAL).value_or(0))}, + Desktop::Types::PRIORITY_SET_PROP)); + } else if (PROP == "opacity_inactive_override") { + PWINDOW->m_ruleApplicator->alphaInactiveOverride(Desktop::Types::COverridableVar( + Desktop::Types::SAlphaValue{PWINDOW->m_ruleApplicator->alphaInactive().valueOrDefault().alpha, sc(configStringToInt(VAL).value_or(0))}, + Desktop::Types::PRIORITY_SET_PROP)); + } else if (PROP == "opacity_fullscreen_override") { + PWINDOW->m_ruleApplicator->alphaFullscreenOverride(Desktop::Types::COverridableVar( + Desktop::Types::SAlphaValue{PWINDOW->m_ruleApplicator->alphaFullscreen().valueOrDefault().alpha, sc(configStringToInt(VAL).value_or(0))}, + Desktop::Types::PRIORITY_SET_PROP)); + } else if (PROP == "allows_input") + parsePropTrivial(PWINDOW->m_ruleApplicator->allowsInput(), VAL); + else if (PROP == "decorate") + parsePropTrivial(PWINDOW->m_ruleApplicator->decorate(), VAL); + else if (PROP == "focus_on_activate") + parsePropTrivial(PWINDOW->m_ruleApplicator->focusOnActivate(), VAL); + else if (PROP == "keep_aspect_ratio") + parsePropTrivial(PWINDOW->m_ruleApplicator->keepAspectRatio(), VAL); + else if (PROP == "nearest_neighbor") + parsePropTrivial(PWINDOW->m_ruleApplicator->nearestNeighbor(), VAL); + else if (PROP == "no_anim") + parsePropTrivial(PWINDOW->m_ruleApplicator->noAnim(), VAL); + else if (PROP == "no_blur") + parsePropTrivial(PWINDOW->m_ruleApplicator->noBlur(), VAL); + else if (PROP == "no_dim") + parsePropTrivial(PWINDOW->m_ruleApplicator->noDim(), VAL); + else if (PROP == "no_focus") + parsePropTrivial(PWINDOW->m_ruleApplicator->noFocus(), VAL); + else if (PROP == "no_max_size") + parsePropTrivial(PWINDOW->m_ruleApplicator->noMaxSize(), VAL); + else if (PROP == "no_shadow") + parsePropTrivial(PWINDOW->m_ruleApplicator->noShadow(), VAL); + else if (PROP == "no_shortcuts_inhibit") + parsePropTrivial(PWINDOW->m_ruleApplicator->noShortcutsInhibit(), VAL); + else if (PROP == "dim_around") + parsePropTrivial(PWINDOW->m_ruleApplicator->dimAround(), VAL); + else if (PROP == "opaque") + parsePropTrivial(PWINDOW->m_ruleApplicator->opaque(), VAL); + else if (PROP == "force_rgbx") + parsePropTrivial(PWINDOW->m_ruleApplicator->RGBX(), VAL); + else if (PROP == "sync_fullscreen") + parsePropTrivial(PWINDOW->m_ruleApplicator->syncFullscreen(), VAL); + else if (PROP == "immediate") + parsePropTrivial(PWINDOW->m_ruleApplicator->tearing(), VAL); + else if (PROP == "xray") + parsePropTrivial(PWINDOW->m_ruleApplicator->xray(), VAL); + else if (PROP == "render_unfocused") + parsePropTrivial(PWINDOW->m_ruleApplicator->renderUnfocused(), VAL); + else if (PROP == "no_follow_mouse") + parsePropTrivial(PWINDOW->m_ruleApplicator->noFollowMouse(), VAL); + else if (PROP == "no_screen_share") + parsePropTrivial(PWINDOW->m_ruleApplicator->noScreenShare(), VAL); + else if (PROP == "no_vrr") + parsePropTrivial(PWINDOW->m_ruleApplicator->noVRR(), VAL); + else if (PROP == "persistent_size") + parsePropTrivial(PWINDOW->m_ruleApplicator->persistentSize(), VAL); + else if (PROP == "stay_focused") + parsePropTrivial(PWINDOW->m_ruleApplicator->stayFocused(), VAL); + else if (PROP == "idle_inhibit") + parsePropTrivial(PWINDOW->m_ruleApplicator->idleInhibitMode(), VAL); + else if (PROP == "border_size") + parsePropTrivial(PWINDOW->m_ruleApplicator->borderSize(), VAL); + else if (PROP == "rounding") + parsePropTrivial(PWINDOW->m_ruleApplicator->rounding(), VAL); + else if (PROP == "rounding_power") + parsePropTrivial(PWINDOW->m_ruleApplicator->roundingPower(), VAL); + else if (PROP == "scroll_mouse") + parsePropTrivial(PWINDOW->m_ruleApplicator->scrollMouse(), VAL); + else if (PROP == "scroll_touchpad") + parsePropTrivial(PWINDOW->m_ruleApplicator->scrollTouchpad(), VAL); + else if (PROP == "animation") + parsePropTrivial(PWINDOW->m_ruleApplicator->animationStyle(), VAL); + else + return {.success = false, .error = "prop not found"}; + } catch (std::exception& e) { return {.success = false, .error = std::format("Error parsing prop value: {}", std::string(e.what()))}; } g_pCompositor->updateAllWindowsAnimatedDecorationValues(); - if (!(PWINDOW->m_windowData.noFocus.valueOrDefault() == noFocus)) { - g_pCompositor->focusWindow(nullptr); - g_pCompositor->focusWindow(PWINDOW); - g_pCompositor->focusWindow(PLASTWINDOW); + if (PWINDOW->m_ruleApplicator->noFocus().valueOrDefault() != noFocus) { + // FIXME: what the fuck is going on here? -vax + Desktop::focusState()->rawWindowFocus(nullptr, Desktop::FOCUS_REASON_KEYBIND); + Desktop::focusState()->fullWindowFocus(PWINDOW, Desktop::FOCUS_REASON_KEYBIND); + Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_KEYBIND); } - if (PROP == "novrr") + if (PROP == "no_vrr") g_pConfigManager->ensureVRR(PWINDOW->m_monitor.lock()); - for (auto const& m : g_pCompositor->m_monitors) - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id); + for (auto const& m : g_pCompositor->m_monitors) { + if (m->m_activeWorkspace) + m->m_activeWorkspace->m_space->recalculate(); + } return {}; } @@ -3263,7 +3142,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!"}; } @@ -3276,14 +3155,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 592588b5..1f013606 100644 --- a/src/managers/KeybindManager.hpp +++ b/src/managers/KeybindManager.hpp @@ -26,29 +26,30 @@ 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; + 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; // DO NOT INITIALIZE bool shadowed = false; @@ -162,9 +163,10 @@ class CKeybindManager { static bool tryMoveFocusToMonitor(PHLMONITOR monitor); static void moveWindowOutOfGroup(PHLWINDOW pWindow, const std::string& dir = ""); static void moveWindowIntoGroup(PHLWINDOW pWindow, PHLWINDOW pWindowInDirection); - static void switchToWindow(PHLWINDOW PWINDOWTOCHANGETO, bool preserveFocusHistory = false); - static uint64_t spawnRawProc(std::string, PHLWORKSPACE pInitialWorkspace); - static uint64_t spawnWithRules(std::string, PHLWORKSPACE pInitialWorkspace); + + static void switchToWindow(PHLWINDOW PWINDOWTOCHANGETO, bool forceFSCycle = false); + static uint64_t spawnRawProc(std::string, PHLWORKSPACE pInitialWorkspace, const std::string& execRuleToken = ""); + static std::optional spawnWithRules(std::string, PHLWORKSPACE pInitialWorkspace); // -------------- Dispatchers -------------- // static SDispatchResult closeActive(std::string); @@ -192,10 +194,7 @@ class CKeybindManager { static SDispatchResult swapActive(std::string); static SDispatchResult toggleGroup(std::string); static SDispatchResult changeGroupActive(std::string); - static SDispatchResult alterSplitRatio(std::string); static SDispatchResult focusMonitor(std::string); - static SDispatchResult toggleSplit(std::string); - static SDispatchResult swapSplit(std::string); static SDispatchResult moveCursorToCorner(std::string); static SDispatchResult moveCursor(std::string); static SDispatchResult workspaceOpt(std::string); diff --git a/src/managers/LayoutManager.cpp b/src/managers/LayoutManager.cpp deleted file mode 100644 index 449d1700..00000000 --- a/src/managers/LayoutManager.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include "LayoutManager.hpp" - -CLayoutManager::CLayoutManager() { - m_layouts.emplace_back(std::make_pair<>("dwindle", &m_dwindleLayout)); - m_layouts.emplace_back(std::make_pair<>("master", &m_masterLayout)); -} - -IHyprLayout* CLayoutManager::getCurrentLayout() { - return m_layouts[m_currentLayoutID].second; -} - -void CLayoutManager::switchToLayout(std::string layout) { - for (size_t i = 0; i < m_layouts.size(); ++i) { - if (m_layouts[i].first == layout) { - if (i == sc(m_currentLayoutID)) - return; - - getCurrentLayout()->onDisable(); - m_currentLayoutID = i; - getCurrentLayout()->onEnable(); - return; - } - } - - 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 80c522fb..00000000 --- a/src/managers/LayoutManager.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "../layout/DwindleLayout.hpp" -#include "../layout/MasterLayout.hpp" - -class CLayoutManager { - public: - CLayoutManager(); - - IHyprLayout* getCurrentLayout(); - - void switchToLayout(std::string); - - bool addLayout(const std::string& name, IHyprLayout* layout); - bool removeLayout(IHyprLayout* layout); - std::vector getAllLayoutNames(); - - private: - enum eHyprLayouts : uint8_t { - LAYOUT_DWINDLE = 0, - LAYOUT_MASTER - }; - - int m_currentLayoutID = LAYOUT_DWINDLE; - - CHyprDwindleLayout m_dwindleLayout; - CHyprMasterLayout m_masterLayout; - std::vector> m_layouts; -}; - -inline UP g_pLayoutManager; diff --git a/src/managers/PointerManager.cpp b/src/managers/PointerManager.cpp index aeabe412..7256e176 100644 --- a/src/managers/PointerManager.cpp +++ b/src/managers/PointerManager.cpp @@ -8,37 +8,41 @@ #include "../protocols/IdleNotify.hpp" #include "../protocols/core/Compositor.hpp" #include "../protocols/core/Seat.hpp" +#include "debug/log/Logger.hpp" #include "eventLoop/EventLoopManager.hpp" #include "../render/pass/TexPassElement.hpp" #include "../managers/input/InputManager.hpp" -#include "../managers/HookSystemManager.hpp" #include "../render/Renderer.hpp" #include "../render/OpenGL.hpp" +#include "../desktop/state/FocusState.hpp" #include "SeatManager.hpp" #include "../helpers/time/Time.hpp" +#include "../helpers/Drm.hpp" +#include "../event/EventBus.hpp" +#include #include #include #include +#include +#include #include using namespace Hyprutils::Utils; CPointerManager::CPointerManager() { - m_hooks.monitorAdded = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any data) { - auto PMONITOR = std::any_cast(data); - + m_hooks.monitorAdded = Event::bus()->m_events.monitor.added.listen([this](PHLMONITOR monitor) { onMonitorLayoutChange(); - PMONITOR->m_events.modeChanged.listenStatic([this] { g_pEventLoopManager->doLater([this]() { onMonitorLayoutChange(); }); }); - PMONITOR->m_events.disconnect.listenStatic([this] { g_pEventLoopManager->doLater([this]() { onMonitorLayoutChange(); }); }); - PMONITOR->m_events.destroy.listenStatic([this] { + monitor->m_events.modeChanged.listenStatic([this] { g_pEventLoopManager->doLater([this]() { onMonitorLayoutChange(); }); }); + monitor->m_events.disconnect.listenStatic([this] { g_pEventLoopManager->doLater([this]() { onMonitorLayoutChange(); }); }); + monitor->m_events.destroy.listenStatic([this] { if (g_pCompositor && !g_pCompositor->m_isShuttingDown) std::erase_if(m_monitorStates, [](const auto& other) { return other->monitor.expired(); }); }); }); - m_hooks.monitorPreRender = g_pHookSystem->hookDynamic("preMonitorCommit", [this](void* self, SCallbackInfo& info, std::any data) { - auto state = stateFor(std::any_cast(data)); + m_hooks.monitorPreRender = Event::bus()->m_events.monitor.preCommit.listen([this](PHLMONITOR monitor) { + auto state = stateFor(monitor); if (!state) return; @@ -71,8 +75,10 @@ void CPointerManager::lockSoftwareForMonitor(PHLMONITOR mon) { void CPointerManager::unlockSoftwareForMonitor(PHLMONITOR mon) { auto const state = stateFor(mon); state->softwareLocks--; - if (state->softwareLocks < 0) + if (state->softwareLocks < 0) { state->softwareLocks = 0; + Log::logger->log(Log::WARN, "Unlocking SW for monitor while it's not locked"); + } if (state->softwareLocks == 0) updateCursorBackend(); @@ -80,13 +86,22 @@ void CPointerManager::unlockSoftwareForMonitor(PHLMONITOR mon) { bool CPointerManager::softwareLockedFor(PHLMONITOR mon) { auto const state = stateFor(mon); - return state->softwareLocks > 0 || state->hardwareFailed; + return state->softwareLocks > 0 || (state->hardwareFailed && hasCursor() && g_pHyprRenderer->shouldRenderCursor()); +} + +bool CPointerManager::hasVisibleHWCursor(PHLMONITOR pMonitor) { + auto const state = stateFor(pMonitor); + return state->softwareLocks == 0 && !state->hardwareFailed && hasCursor() && g_pHyprRenderer->shouldRenderCursor(); } Vector2D CPointerManager::position() { return m_pointerPos; } +Vector2D CPointerManager::hotspot() { + return m_currentCursorImage.hotspot; +} + bool CPointerManager::hasCursor() { return m_currentCursorImage.pBuffer || m_currentCursorImage.surface; } @@ -106,6 +121,7 @@ void CPointerManager::setCursorBuffer(SP buf, const Vector2 m_currentCursorImage.scale = scale; updateCursorBackend(); damageIfSoftware(); + m_events.cursorChanged.emit(); } return; @@ -123,9 +139,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) { @@ -134,6 +151,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; @@ -155,6 +173,7 @@ void CPointerManager::setCursorSurface(SP surf, const Vector2D& hots recheckEnteredOutputs(); updateCursorBackend(); damageIfSoftware(); + m_events.cursorChanged.emit(); }); if (surf->resource()->m_current.texture) { @@ -168,6 +187,7 @@ void CPointerManager::setCursorSurface(SP surf, const Vector2D& hots recheckEnteredOutputs(); updateCursorBackend(); damageIfSoftware(); + m_events.cursorChanged.emit(); } void CPointerManager::recheckEnteredOutputs() { @@ -242,7 +262,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; } @@ -252,6 +272,8 @@ void CPointerManager::resetCursorImage(bool apply) { ms->cursorFrontBuffer = nullptr; } } + + m_events.cursorChanged.emit(); } void CPointerManager::updateCursorBackend() { @@ -259,7 +281,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,7 +296,7 @@ void CPointerManager::updateCursorBackend() { } if (state->softwareLocks > 0 || g_pConfigManager->shouldUseSoftwareCursors(m) || !attemptHardwareCursor(state)) { - Debug::log(TRACE, "Output {} rejected hardware cursors, falling back to sw", m->m_name); + 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; @@ -304,11 +326,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; } @@ -342,7 +364,7 @@ bool CPointerManager::attemptHardwareCursor(SP hiding"); + Log::logger->log(Log::TRACE, "[pointer] no texture for hw cursor -> hiding"); setHWCursorBuffer(state, nullptr); return true; } @@ -350,7 +372,7 @@ bool CPointerManager::attemptHardwareCursor(SPlog(Log::TRACE, "[pointer] hw cursor failed rendering"); setHWCursorBuffer(state, nullptr); return false; } @@ -358,7 +380,7 @@ bool CPointerManager::attemptHardwareCursor(SPlog(Log::TRACE, "[pointer] hw cursor failed applying, hiding"); setHWCursorBuffer(state, nullptr); return false; } else @@ -373,7 +395,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; @@ -388,7 +410,7 @@ bool CPointerManager::setHWCursorBuffer(SP state, SP CPointerManager::renderHWCursorBuffer(SP state, SP texture) { +SP CPointerManager::renderHWCursorBuffer(SP state, SP texture) { auto maxSize = state->monitor->m_output->cursorPlaneSize(); auto const& cursorSize = m_currentCursorImage.size; @@ -401,7 +423,7 @@ SP CPointerManager::renderHWCursorBuffer(SP 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 @@ -432,14 +454,14 @@ SP CPointerManager::renderHWCursorBuffer(SPmonitor->m_output->getBackend()->preferredAllocator()->drmFD() != g_pCompositor->m_drm.fd; + options.multigpu = !DRM::sameGpu(state->monitor->m_output->getBackend()->preferredAllocator()->drmFD(), g_pCompositor->m_drm.fd); // We do not set the format (unless shm). If it's unset (DRM_FORMAT_INVALID) then the swapchain will pick for us, // but if it's set, we don't wanna change it. if (shouldUseCpuBuffer) 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; } } @@ -454,7 +476,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; } @@ -469,12 +491,12 @@ 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 == 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; } } @@ -492,7 +514,7 @@ SP CPointerManager::renderHWCursorBuffer(SPlog(Log::TRACE, "Cannot use dumb copy on dmabuf cursor buffers"); return nullptr; } } @@ -521,24 +543,23 @@ SP CPointerManager::renderHWCursorBuffer(SPm_size / (m_currentCursorImage.size / m_currentCursorImage.scale * state->monitor->m_scale); - cairo_matrix_scale(&matrixPre, SCALE.x, SCALE.y); + const auto SX = SCALE.x, SY = SCALE.y; + const auto BW = sc(DMABUF.size.x), BH = sc(DMABUF.size.y); - if (TR) { - cairo_matrix_rotate(&matrixPre, M_PI_2 * sc(TR)); - - // FIXME: this is wrong, and doesn't work for 5, 6 and 7. (flipped + rot) - // cba to do it rn, does anyone fucking use that?? - if (TR >= WL_OUTPUT_TRANSFORM_FLIPPED) { - cairo_matrix_scale(&matrixPre, -1, 1); - cairo_matrix_translate(&matrixPre, -DMABUF.size.x, 0); - } - - if (TR == 3 || TR == 7) - cairo_matrix_translate(&matrixPre, -DMABUF.size.x, 0); - else if (TR == 2 || TR == 6) - cairo_matrix_translate(&matrixPre, -DMABUF.size.x, -DMABUF.size.y); - else if (TR == 1 || TR == 5) - cairo_matrix_translate(&matrixPre, 0, -DMABUF.size.y); + // Cairo pattern matrix maps destination coords to source coords (inverse of visual transform). + // x_src = xx * x_dst + xy * y_dst + x0 + // y_src = yx * x_dst + yy * y_dst + y0 + // cairo_matrix_init(&m, xx, yx, xy, yy, x0, y0) + switch (TR) { + case WL_OUTPUT_TRANSFORM_NORMAL: + default: cairo_matrix_init(&matrixPre, SX, 0, 0, SY, 0, 0); break; + case WL_OUTPUT_TRANSFORM_90: cairo_matrix_init(&matrixPre, 0, SY, -SX, 0, SX * BW, 0); break; + case WL_OUTPUT_TRANSFORM_180: cairo_matrix_init(&matrixPre, -SX, 0, 0, -SY, SX * BW, SY * BH); break; + case WL_OUTPUT_TRANSFORM_270: cairo_matrix_init(&matrixPre, 0, -SY, SX, 0, 0, SY * BH); break; + case WL_OUTPUT_TRANSFORM_FLIPPED: cairo_matrix_init(&matrixPre, -SX, 0, 0, SY, SX * BW, 0); break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: cairo_matrix_init(&matrixPre, 0, SY, SX, 0, 0, 0); break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: cairo_matrix_init(&matrixPre, SX, 0, 0, -SY, 0, SY * BH); break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: cairo_matrix_init(&matrixPre, 0, -SY, -SX, 0, SX * BW, SY * BH); break; } cairo_pattern_set_matrix(PATTERNPRE, &matrixPre); @@ -565,26 +586,24 @@ SP CPointerManager::renderHWCursorBuffer(SPgetOrCreateRenderbuffer(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}); + g_pHyprOpenGL->beginSimple(state->monitor.lock(), {0, 0, INT_MAX, INT_MAX}, RBO); + g_pHyprOpenGL->clear(CHyprColor{0.F, 0.F, 0.F, 0.F}); // ensure the RBO is zero initialized. CBox xbox = {{}, Vector2D{m_currentCursorImage.size / m_currentCursorImage.scale * state->monitor->m_scale}.round()}; - 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_pHyprOpenGL->renderTexture(texture, xbox, {.noCM = true}); g_pHyprOpenGL->end(); g_pHyprOpenGL->m_renderData.pMonitor.reset(); - g_pHyprRenderer->onRenderbufferDestroy(RBO.get()); - return buf; } @@ -636,7 +655,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; @@ -647,7 +666,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(); } @@ -737,17 +756,26 @@ Vector2D CPointerManager::closestValid(const Vector2D& pos) { } void CPointerManager::damageIfSoftware() { + if (g_pCompositor->m_unsafeState) + return; + auto b = getCursorBoxGlobal().expand(4); for (auto const& mw : m_monitorStates) { - if (mw->monitor.expired() || !mw->monitor->m_output) + auto monitor = mw->monitor.lock(); + if (!monitor || !monitor->m_output || monitor->isMirror()) continue; - if ((mw->softwareLocks > 0 || mw->hardwareFailed || g_pConfigManager->shouldUseSoftwareCursors(mw->monitor.lock())) && - b.overlaps({mw->monitor->m_position, mw->monitor->m_size})) { - g_pHyprRenderer->damageBox(b, mw->monitor->shouldSkipScheduleFrameOnMouseEvent()); - break; - } + auto usesSoftwareCursor = (mw->softwareLocks > 0 || mw->hardwareFailed || g_pConfigManager->shouldUseSoftwareCursors(monitor)); + if (!usesSoftwareCursor) + continue; + + auto shouldAddDamage = !monitor->shouldSkipScheduleFrameOnMouseEvent() && b.overlaps({monitor->m_position, monitor->m_size}); + if (!shouldAddDamage) + continue; + + CBox damageBox = b.copy().translate(-monitor->m_position).scale(monitor->m_scale).round(); + monitor->addDamage(damageBox); } } @@ -799,7 +827,7 @@ void CPointerManager::warpAbsolute(Vector2D abs, SP dev) { auto outputMappedArea = [&mappedArea](const std::string& output) { if (output == "current") { - if (const auto PLASTMONITOR = g_pCompositor->m_lastMonitor.lock(); PLASTMONITOR) + if (const auto PLASTMONITOR = Desktop::focusState()->monitor(); PLASTMONITOR) return PLASTMONITOR->logicalBox(); } else if (const auto PMONITOR = g_pCompositor->getMonitorFromString(output); PMONITOR) return PMONITOR->logicalBox(); @@ -871,13 +899,17 @@ void CPointerManager::onMonitorLayoutChange() { damageIfSoftware(); } -SP CPointerManager::getCurrentCursorTexture() { +const CPointerManager::SCursorImage& CPointerManager::currentCursorImage() { + return m_currentCursorImage; +} + +SP CPointerManager::getCurrentCursorTexture() { if (!m_currentCursorImage.pBuffer && (!m_currentCursorImage.surface || !m_currentCursorImage.surface->resource()->m_current.texture)) return nullptr; if (m_currentCursorImage.pBuffer) { if (!m_currentCursorImage.bufferTex) - m_currentCursorImage.bufferTex = makeShared(m_currentCursorImage.pBuffer, true); + m_currentCursorImage.bufferTex = g_pHyprRenderer->createTexture(m_currentCursorImage.pBuffer, true); return m_currentCursorImage.bufferTex; } @@ -919,21 +951,11 @@ void CPointerManager::attachPointer(SP pointer) { PROTO::idle->onActivity(); }); - listener->axis = pointer->m_pointerEvents.axis.listen([weak = WP(pointer)](const IPointer::SAxisEvent& event) { + listener->axis = pointer->m_pointerEvents.axis.listen([weak = WP(pointer)](const IPointer::SAxisEvent& event) { g_pInputManager->onMouseWheel(event, weak.lock()); PROTO::idle->onActivity(); }); - - listener->frame = pointer->m_pointerEvents.frame.listen([] { - bool shouldSkip = false; - if (!g_pSeatManager->m_mouse.expired() && g_pInputManager->isLocked()) { - auto PMONITOR = g_pCompositor->m_lastMonitor.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); @@ -985,7 +1007,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) { @@ -1026,7 +1048,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) { @@ -1071,7 +1093,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) { @@ -1086,7 +1108,7 @@ void CPointerManager::detachTablet(SP tablet) { std::erase_if(m_tabletListeners, [tablet](const auto& e) { return e->tablet.expired() || e->tablet == tablet; }); } -void CPointerManager::damageCursor(PHLMONITOR pMonitor) { +void CPointerManager::damageCursor(PHLMONITOR pMonitor, bool skipFrameSchedule) { for (auto const& mw : m_monitorStates) { if (mw->monitor != pMonitor) continue; @@ -1096,7 +1118,7 @@ void CPointerManager::damageCursor(PHLMONITOR pMonitor) { if (b.empty()) return; - g_pHyprRenderer->damageBox(b); + g_pHyprRenderer->damageBox(b, skipFrameSchedule); return; } @@ -1105,22 +1127,3 @@ void CPointerManager::damageCursor(PHLMONITOR pMonitor) { Vector2D CPointerManager::cursorSizeLogical() { return m_currentCursorImage.size / m_currentCursorImage.scale; } - -void CPointerManager::storeMovement(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel) { - m_storedTime = time; - m_storedDelta += delta; - m_storedUnaccel += deltaUnaccel; -} - -void CPointerManager::setStoredMovement(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel) { - m_storedTime = time; - m_storedDelta = delta; - m_storedUnaccel = deltaUnaccel; -} - -void CPointerManager::sendStoredMovement() { - PROTO::relativePointer->sendRelativeMotion(m_storedTime * 1000, m_storedDelta, m_storedUnaccel); - m_storedTime = 0; - m_storedDelta = Vector2D{}; - m_storedUnaccel = Vector2D{}; -} diff --git a/src/managers/PointerManager.hpp b/src/managers/PointerManager.hpp index 62a5d18f..a4fe1971 100644 --- a/src/managers/PointerManager.hpp +++ b/src/managers/PointerManager.hpp @@ -4,14 +4,15 @@ #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; +class ITexture; AQUAMARINE_FORWARD(IBuffer); @@ -40,7 +41,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 +49,7 @@ class CPointerManager { void lockSoftwareAll(); void unlockSoftwareAll(); bool softwareLockedFor(PHLMONITOR pMonitor); + bool hasVisibleHWCursor(PHLMONITOR pMonitor); void renderSoftwareCursorsFor(PHLMONITOR pMonitor, const Time::steady_tp& now, CRegion& damage /* logical */, std::optional overridePos = {} /* monitor-local */, bool forceRender = false); @@ -55,17 +57,38 @@ class CPointerManager { // this is needed e.g. during screensharing where // the software cursors aren't locked during the cursor move, but they // are rendered later. - void damageCursor(PHLMONITOR pMonitor); + void damageCursor(PHLMONITOR pMonitor, bool skipFrameSchedule = false); // Vector2D position(); + Vector2D hotspot(); Vector2D cursorSizeLogical(); - void storeMovement(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel); - void setStoredMovement(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel); - void sendStoredMovement(); void recheckEnteredOutputs(); + // returns the thing in global coords + CBox getCursorBoxGlobal(); + + struct SCursorImage { + SP pBuffer; + SP bufferTex; + WP surface; + + Vector2D hotspot; + Vector2D size; + float scale = 1.F; + + CHyprSignalListener destroySurface; + CHyprSignalListener commitSurface; + }; + + const SCursorImage& currentCursorImage(); + SP getCurrentCursorTexture(); + + struct { + CSignalT<> cursorChanged; + } m_events; + private: void recheckPointerPosition(); void onMonitorLayoutChange(); @@ -81,13 +104,9 @@ class CPointerManager { // returns the thing in device coordinates. Is NOT offset by the hotspot, relies on set_cursor with hotspot. Vector2D getCursorPosForMonitor(PHLMONITOR pMonitor); // returns the thing in logical coordinates of the monitor - CBox getCursorBoxLogicalForMonitor(PHLMONITOR pMonitor); - // returns the thing in global coords - CBox getCursorBoxGlobal(); + CBox getCursorBoxLogicalForMonitor(PHLMONITOR pMonitor); - Vector2D transformedHotspot(PHLMONITOR pMonitor); - - SP getCurrentCursorTexture(); + Vector2D transformedHotspot(PHLMONITOR pMonitor); struct SPointerListener { CHyprSignalListener destroy; @@ -139,24 +158,9 @@ class CPointerManager { std::vector monitorBoxes; } m_currentMonitorLayout; - struct { - SP pBuffer; - SP bufferTex; - WP surface; + SCursorImage m_currentCursorImage; // TODO: support various sizes per-output so we can have pixel-perfect cursors - Vector2D hotspot; - Vector2D size; - float scale = 1.F; - - CHyprSignalListener destroySurface; - CHyprSignalListener commitSurface; - } m_currentCursorImage; // TODO: support various sizes per-output so we can have pixel-perfect cursors - - Vector2D m_pointerPos = {0, 0}; - - uint64_t m_storedTime = 0; - Vector2D m_storedDelta = {0, 0}; - Vector2D m_storedUnaccel = {0, 0}; + Vector2D m_pointerPos = {0, 0}; struct SMonitorPointerState { SMonitorPointerState(const PHLMONITOR& m) : monitor(m) {} @@ -177,12 +181,12 @@ class CPointerManager { std::vector> m_monitorStates; SP stateFor(PHLMONITOR mon); bool attemptHardwareCursor(SP state); - SP renderHWCursorBuffer(SP state, SP texture); + SP renderHWCursorBuffer(SP state, SP texture); bool setHWCursorBuffer(SP state, SP buf); struct { - SP monitorAdded; - SP monitorPreRender; + CHyprSignalListener monitorAdded; + CHyprSignalListener monitorPreRender; } m_hooks; }; diff --git a/src/managers/ProtocolManager.cpp b/src/managers/ProtocolManager.cpp index f9f90ffe..c13e6e48 100644 --- a/src/managers/ProtocolManager.cpp +++ b/src/managers/ProtocolManager.cpp @@ -50,6 +50,8 @@ #include "../protocols/SecurityContext.hpp" #include "../protocols/CTMControl.hpp" #include "../protocols/HyprlandSurface.hpp" +#include "../protocols/ImageCaptureSource.hpp" +#include "../protocols/ImageCopyCapture.hpp" #include "../protocols/core/Seat.hpp" #include "../protocols/core/DataDevice.hpp" #include "../protocols/core/Compositor.hpp" @@ -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" @@ -69,6 +69,7 @@ #include "../protocols/CommitTiming.hpp" #include "../helpers/Monitor.hpp" +#include "../event/EventBus.hpp" #include "../render/Renderer.hpp" #include "../Compositor.hpp" #include "content-type-v1.hpp" @@ -99,21 +100,20 @@ 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 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 +130,7 @@ CProtocolManager::CProtocolManager() { m_modeChangeListeners[M->m_name] = M->m_events.modeChanged.listen([this, M] { onMonitorModeChange(M); }); }); - static auto P2 = g_pHookSystem->hookDynamic("monitorRemoved", [this](void* self, SCallbackInfo& info, std::any param) { - auto M = std::any_cast(param); + static auto P2 = Event::bus()->m_events.monitor.removed.listen([this](PHLMONITOR M) { if (!PROTO::outputs.contains(M->m_name)) return; PROTO::outputs.at(M->m_name)->remove(); @@ -143,14 +142,14 @@ 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"); PROTO::tearing = makeUnique(&wp_tearing_control_manager_v1_interface, 1, "TearingControl"); PROTO::fractional = makeUnique(&wp_fractional_scale_manager_v1_interface, 1, "FractionalScale"); PROTO::xdgOutput = makeUnique(&zxdg_output_manager_v1_interface, 3, "XDGOutput"); - PROTO::cursorShape = makeUnique(&wp_cursor_shape_manager_v1_interface, 1, "CursorShape"); + PROTO::cursorShape = makeUnique(&wp_cursor_shape_manager_v1_interface, 2, "CursorShape"); PROTO::idleInhibit = makeUnique(&zwp_idle_inhibit_manager_v1_interface, 1, "IdleInhibit"); PROTO::relativePointer = makeUnique(&zwp_relative_pointer_manager_v1_interface, 1, "RelativePointer"); PROTO::xdgDecoration = makeUnique(&zxdg_decoration_manager_v1_interface, 1, "XDGDecoration"); @@ -181,8 +180,6 @@ CProtocolManager::CProtocolManager() { PROTO::dataWlr = makeUnique(&zwlr_data_control_manager_v1_interface, 2, "DataDeviceWlr"); PROTO::primarySelection = makeUnique(&zwp_primary_selection_device_manager_v1_interface, 1, "PrimarySelection"); PROTO::xwaylandShell = makeUnique(&xwayland_shell_v1_interface, 1, "XWaylandShell"); - PROTO::screencopy = makeUnique(&zwlr_screencopy_manager_v1_interface, 3, "Screencopy"); - PROTO::toplevelExport = makeUnique(&hyprland_toplevel_export_manager_v1_interface, 2, "ToplevelExport"); PROTO::toplevelMapping = makeUnique(&hyprland_toplevel_mapping_manager_v1_interface, 1, "ToplevelMapping"); PROTO::globalShortcuts = makeUnique(&hyprland_global_shortcuts_manager_v1_interface, 1, "GlobalShortcuts"); PROTO::xdgDialog = makeUnique(&xdg_wm_dialog_v1_interface, 1, "XDGDialog"); @@ -197,16 +194,19 @@ CProtocolManager::CProtocolManager() { PROTO::extDataDevice = makeUnique(&ext_data_control_manager_v1_interface, 1, "ExtDataDevice"); PROTO::pointerWarp = makeUnique(&wp_pointer_warp_v1_interface, 1, "PointerWarp"); PROTO::fifo = makeUnique(&wp_fifo_manager_v1_interface, 1, "Fifo"); - PROTO::commitTiming = makeUnique(&wp_commit_timing_manager_v1_interface, 1, "CommitTiming"); + + if (*PENABLECT) + PROTO::commitTiming = makeUnique(&wp_commit_timing_manager_v1_interface, 1, "CommitTiming"); + + // Screensharing Protocols + PROTO::screencopy = makeUnique(&zwlr_screencopy_manager_v1_interface, 3, "Screencopy"); + PROTO::toplevelExport = makeUnique(&hyprland_toplevel_export_manager_v1_interface, 2, "ToplevelExport"); + PROTO::imageCaptureSource = makeUnique(); // ctor inits actual protos, output and toplevel + PROTO::imageCopyCapture = makeUnique(&ext_image_copy_capture_manager_v1_interface, 1, "ImageCopyCapture"); if (*PENABLECM) PROTO::colorManagement = makeUnique(&wp_color_manager_v1_interface, 1, "ColorManagement", *PDEBUGCM); - if (*PENABLEXXCM && *PENABLECM) { - PROTO::xxColorManagement = makeUnique(&xx_color_manager_v4_interface, 1, "XXColorManagement"); - PROTO::frogColorManagement = makeUnique(&frog_color_management_factory_v1_interface, 1, "FrogColorManagement"); - } - // ! please read the top of this file before adding another protocol for (auto const& b : g_pCompositor->m_aqBackend->getImplementations()) { @@ -222,9 +222,9 @@ CProtocolManager::CProtocolManager() { if (g_pHyprOpenGL->m_exts.EGL_ANDROID_native_fence_sync_ext && !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"); } } @@ -232,7 +232,7 @@ CProtocolManager::CProtocolManager() { 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,8 +295,6 @@ 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(); @@ -304,6 +302,7 @@ CProtocolManager::~CProtocolManager() { PROTO::pointerWarp.reset(); PROTO::fifo.reset(); PROTO::commitTiming.reset(); + PROTO::imageCaptureSource.reset(); for (auto& [_, lease] : PROTO::lease) { lease.reset(); @@ -346,9 +345,6 @@ bool CProtocolManager::isGlobalPrivileged(const wl_global* global) { PROTO::constraints->getGlobal(), PROTO::activation->getGlobal(), PROTO::idle->getGlobal(), - PROTO::ime->getGlobal(), - PROTO::virtualKeyboard->getGlobal(), - PROTO::virtualPointer->getGlobal(), PROTO::serverDecorationKDE->getGlobal(), PROTO::tablet->getGlobal(), PROTO::presentation->getGlobal(), diff --git a/src/managers/SeatManager.cpp b/src/managers/SeatManager.cpp index 3b3d8b04..a107dced 100644 --- a/src/managers/SeatManager.cpp +++ b/src/managers/SeatManager.cpp @@ -5,11 +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 @@ -113,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; } @@ -216,14 +217,14 @@ void CSeatManager::setPointerFocus(SP surf, const Vector2D& if (PROTO::data->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; } @@ -516,7 +517,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; @@ -544,13 +545,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; // } @@ -563,7 +564,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; } @@ -589,7 +590,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; } @@ -617,8 +618,9 @@ void CSeatManager::setGrab(SP grab) { if (m_seatGrab) { auto oldGrab = m_seatGrab; - // Try to find the parent window from the grab + // Try to find the parent window or layer surface from the grab PHLWINDOW parentWindow; + PHLLS parentLayer; if (oldGrab && oldGrab->m_surfs.size()) { // Try to find the surface that had focus when the grab ended SP focusedSurf; @@ -639,13 +641,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()); + } } } } @@ -653,35 +658,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)) { - g_pCompositor->focusWindow(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()); } } @@ -689,10 +698,10 @@ void CSeatManager::setGrab(SP grab) { refocus = layer->m_interactivity == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE; if (refocus) { - auto candidate = g_pCompositor->m_lastWindow.lock(); + auto candidate = Desktop::focusState()->window(); if (candidate) - g_pCompositor->focusWindow(candidate); + Desktop::focusState()->rawWindowFocus(candidate, Desktop::FOCUS_REASON_FFM); } if (oldGrab->m_onEnd) diff --git a/src/managers/SeatManager.hpp b/src/managers/SeatManager.hpp index fe11f930..21736e3a 100644 --- a/src/managers/SeatManager.hpp +++ b/src/managers/SeatManager.hpp @@ -127,9 +127,6 @@ class CSeatManager { void setGrab(SP grab); // nullptr removes SP m_seatGrab; - bool m_isPointerFrameSkipped = false; - bool m_isPointerFrameCommit = false; - private: struct SSeatResourceContainer { SSeatResourceContainer(SP); diff --git a/src/managers/SessionLockManager.cpp b/src/managers/SessionLockManager.cpp index 391b6dbb..e729ae95 100644 --- a/src/managers/SessionLockManager.cpp +++ b/src/managers/SessionLockManager.cpp @@ -4,6 +4,8 @@ #include "../protocols/FractionalScale.hpp" #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" @@ -25,8 +27,8 @@ SSessionLockSurface::SSessionLockSurface(SP surface_) : sur }); listeners.destroy = surface_->m_events.destroy.listen([this] { - if (pWlrSurface == g_pCompositor->m_lastFocus) - g_pCompositor->m_lastFocus.reset(); + if (pWlrSurface == Desktop::focusState()->surface()) + Desktop::focusState()->surface().reset(); g_pSessionLockManager->removeSessionLockSurface(this); }); @@ -34,7 +36,7 @@ SSessionLockSurface::SSessionLockSurface(SP surface_) : sur listeners.commit = surface_->m_events.commit.listen([this] { const auto PMONITOR = g_pCompositor->getMonitorFromID(iMonitorID); - if (mapped && !g_pCompositor->m_lastFocus) + if (mapped && !Desktop::focusState()->surface()) g_pInputManager->simulateMouseMovement(); if (PMONITOR) @@ -50,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; } @@ -58,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; @@ -67,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] { @@ -82,13 +86,13 @@ void CSessionLockManager::onNewSessionLock(SP pLock) { m_sessionLock->listeners.destroy = pLock->m_events.destroyed.listen([this] { m_sessionLock.reset(); - g_pCompositor->focusSurface(nullptr); + Desktop::focusState()->rawSurfaceFocus(nullptr); for (auto const& m : g_pCompositor->m_monitors) g_pHyprRenderer->damageMonitor(m); }); - g_pCompositor->focusSurface(nullptr); + Desktop::focusState()->rawSurfaceFocus(nullptr); g_pSeatManager->setGrab(nullptr); const bool NOACTIVEMONS = std::ranges::all_of(g_pCompositor->m_monitors, [](const auto& m) { return !m->m_enabled || !m->m_dpmsStatus; }); @@ -119,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; }, @@ -196,14 +200,14 @@ void CSessionLockManager::removeSessionLockSurface(SSessionLockSurface* pSLS) { std::erase_if(m_sessionLock->vSessionLockSurfaces, [&](const auto& other) { return pSLS == other.get(); }); - if (g_pCompositor->m_lastFocus) + if (Desktop::focusState()->surface()) return; for (auto const& sls : m_sessionLock->vSessionLockSurfaces) { if (!sls->mapped) continue; - g_pCompositor->focusSurface(sls->surface->surface()); + Desktop::focusState()->rawSurfaceFocus(sls->surface->surface()); break; } } diff --git a/src/managers/SessionLockManager.hpp b/src/managers/SessionLockManager.hpp index 2938a851..efcaf09a 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 93f820f4..b1d167fa 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 15821879..11bfb7df 100644 --- a/src/managers/VersionKeeperManager.hpp +++ b/src/managers/VersionKeeperManager.hpp @@ -10,7 +10,7 @@ class CVersionKeeperManager { bool fired(); private: - bool isVersionOlderThanRunning(const std::string& ver); + bool isMajorVersionOlderThanRunning(const std::string& ver); bool m_fired = false; }; diff --git a/src/managers/WelcomeManager.cpp b/src/managers/WelcomeManager.cpp new file mode 100644 index 00000000..6faf58c3 --- /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 00000000..1f0535eb --- /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 307cbc84..90cd2a32 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); } } @@ -396,6 +397,10 @@ std::string CXCursorManager::getLegacyShapeName(std::string const& shape) { return "left_ptr"; else if (shape == "zoom-out") return "left_ptr"; + else if (shape == "dnd-ask") + return "dnd-copy"; + else if (shape == "all-resize") + return "fleur"; return std::string(); }; @@ -492,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; } } @@ -524,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; } @@ -538,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; } } @@ -574,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; } @@ -592,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 460e9ecd..1fca293a 100644 --- a/src/managers/XWaylandManager.cpp +++ b/src/managers/XWaylandManager.cpp @@ -1,6 +1,6 @@ #include "XWaylandManager.hpp" #include "../Compositor.hpp" -#include "../events/Events.hpp" +#include "../desktop/state/FocusState.hpp" #include "../config/ConfigValue.hpp" #include "../helpers/Monitor.hpp" #include "../protocols/XDGShell.hpp" @@ -21,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; } @@ -69,8 +69,8 @@ void CHyprXWaylandManager::activateWindow(PHLWINDOW pWindow, bool activate) { pWindow->m_xdgSurface->m_toplevel->setActive(activate); if (activate) { - g_pCompositor->m_lastFocus = getWindowSurface(pWindow); - g_pCompositor->m_lastWindow = pWindow; + Desktop::focusState()->surface() = getWindowSurface(pWindow); + Desktop::focusState()->window() = pWindow; } if (!pWindow->m_pinned) @@ -88,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; } @@ -121,7 +129,8 @@ bool CHyprXWaylandManager::shouldBeFloated(PHLWINDOW pWindow, bool pending) { const auto SIZEHINTS = pWindow->m_xwaylandSurface->m_sizeHints.get(); if (pWindow->m_xwaylandSurface->m_transient || pWindow->m_xwaylandSurface->m_parent || - (SIZEHINTS && (SIZEHINTS->min_width == SIZEHINTS->max_width) && (SIZEHINTS->min_height == SIZEHINTS->max_height))) + (SIZEHINTS && SIZEHINTS->min_width > 0 && SIZEHINTS->min_height > 0 && SIZEHINTS->max_width > 0 && SIZEHINTS->max_height > 0 && + (SIZEHINTS->min_width == SIZEHINTS->max_width) && (SIZEHINTS->min_height == SIZEHINTS->max_height))) return true; } else { if (!pWindow->m_xdgSurface || !pWindow->m_xdgSurface->m_toplevel) diff --git a/src/managers/XWaylandManager.hpp b/src/managers/XWaylandManager.hpp index 59eee4c5..a26b7b68 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 { diff --git a/src/managers/animation/AnimationManager.cpp b/src/managers/animation/AnimationManager.cpp index f38f4ccf..c4b921cb 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 @@ -18,16 +18,7 @@ static int wlTick(SP self, void* data) { if (g_pAnimationManager) - g_pAnimationManager->onTicked(); - - if (g_pCompositor->m_sessionActive && g_pAnimationManager && g_pHookSystem && !g_pCompositor->m_unsafeState && - std::ranges::any_of(g_pCompositor->m_monitors, [](const auto& mon) { return mon->m_enabled && mon->m_output; })) { - g_pAnimationManager->tick(); - EMIT_HOOK_EVENT("tick", nullptr); - } - - if (g_pAnimationManager && g_pAnimationManager->shouldTickForNext()) - g_pAnimationManager->scheduleTick(); + g_pAnimationManager->frameTick(); return 0; } @@ -98,7 +89,7 @@ static void handleUpdate(CAnimatedVariable& av, bool warp) { if (!PMONITOR) return; - animationsDisabled = PWINDOW->m_windowData.noAnim.valueOr(animationsDisabled); + animationsDisabled = PWINDOW->m_ruleApplicator->noAnim().valueOr(animationsDisabled); } else if (PWORKSPACE) { PMONITOR = PWORKSPACE->m_monitor.lock(); if (!PMONITOR) @@ -142,7 +133,7 @@ static void handleUpdate(CAnimatedVariable& av, bool warp) { PMONITOR = g_pCompositor->getMonitorFromVector(PLAYER->m_realPosition->goal() + PLAYER->m_realSize->goal() / 2.F); if (!PMONITOR) return; - animationsDisabled = animationsDisabled || PLAYER->m_noAnimations; + animationsDisabled = animationsDisabled || PLAYER->m_ruleApplicator->noanim().valueOrDefault(); } const auto SPENT = av.getPercent(); @@ -207,7 +198,7 @@ static void handleUpdate(CAnimatedVariable& av, bool warp) { } // manually schedule a frame - if (PMONITOR) + if (PMONITOR && !PMONITOR->inFullscreenMode()) g_pCompositor->scheduleFrameForMonitor(PMONITOR, Aquamarine::IOutput::AQ_SCHEDULE_ANIMATION); } @@ -218,63 +209,88 @@ 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 (!PAV) - continue; + if (!m_vActiveAnimatedVariables.empty()) { + const auto CPY = m_vActiveAnimatedVariables; - // for disabled anims just warp - bool warp = !*PANIMENABLED || !PAV->enabled(); + for (const auto& PAV : CPY) { + if (!PAV) + continue; - switch (PAV->m_Type) { - case AVARTYPE_FLOAT: { - auto pTypedAV = dc*>(PAV.get()); - RASSERT(pTypedAV, "Failed to upcast animated float"); - handleUpdate(*pTypedAV, warp); - } break; - case AVARTYPE_VECTOR: { - auto pTypedAV = dc*>(PAV.get()); - RASSERT(pTypedAV, "Failed to upcast animated Vector2D"); - handleUpdate(*pTypedAV, warp); - } break; - case AVARTYPE_COLOR: { - auto pTypedAV = dc*>(PAV.get()); - RASSERT(pTypedAV, "Failed to upcast animated CHyprColor"); - handleUpdate(*pTypedAV, warp); - } break; - default: UNREACHABLE(); + // lock this value while we are doing handleUpdate to avoid a UAF if an update callback destroys it + const auto LOCK = PAV.lock(); + + // for disabled anims just warp + bool warp = !*PANIMENABLED || !PAV->enabled(); + + switch (PAV->m_Type) { + case AVARTYPE_FLOAT: { + auto pTypedAV = dc*>(PAV.get()); + RASSERT(pTypedAV, "Failed to upcast animated float"); + handleUpdate(*pTypedAV, warp); + } break; + case AVARTYPE_VECTOR: { + auto pTypedAV = dc*>(PAV.get()); + RASSERT(pTypedAV, "Failed to upcast animated Vector2D"); + handleUpdate(*pTypedAV, warp); + } break; + case AVARTYPE_COLOR: { + auto pTypedAV = dc*>(PAV.get()); + RASSERT(pTypedAV, "Failed to upcast animated CHyprColor"); + handleUpdate(*pTypedAV, warp); + } break; + default: UNREACHABLE(); + } } } tickDone(); } +void CHyprAnimationManager::frameTick() { + onTicked(); + + if (!shouldTickForNext()) + return; + + if UNLIKELY (!g_pCompositor->m_sessionActive || g_pCompositor->m_unsafeState || + !std::ranges::any_of(g_pCompositor->m_monitors, [](const auto& mon) { return mon->m_enabled && mon->m_output; })) + return; + + if (!m_lastTickValid || m_lastTickTimer.getMillis() >= 1.0f) { + m_lastTickTimer.reset(); + m_lastTickValid = true; + + tick(); + Event::bus()->m_events.tick.emit(); + } + + if (shouldTickForNext()) + scheduleTick(); +} + void CHyprAnimationManager::scheduleTick() { if (m_tickScheduled) return; m_tickScheduled = true; - const auto PMOSTHZ = g_pHyprRenderer->m_mostHzMonitor; - - if (!PMOSTHZ) { - m_animationTimer->updateTimeout(std::chrono::milliseconds(16)); + if (!m_animationTimer || !g_pEventLoopManager) { + m_tickScheduled = false; return; } - float refreshDelayMs = std::floor(1000.f / PMOSTHZ->m_refreshRate); - - const float SINCEPRES = std::chrono::duration_cast(Time::steadyNow() - PMOSTHZ->m_lastPresentationTimer.chrono()).count() / 1000.F; - - const auto TOPRES = std::clamp(refreshDelayMs - SINCEPRES, 1.1f, 1000.f); // we can't send 0, that will disarm it - - m_animationTimer->updateTimeout(std::chrono::milliseconds(sc(std::floor(TOPRES)))); + m_animationTimer->updateTimeout(std::chrono::milliseconds(1)); } 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 0631e7c7..35bb1e8a 100644 --- a/src/managers/animation/AnimationManager.hpp +++ b/src/managers/animation/AnimationManager.hpp @@ -6,6 +6,7 @@ #include "../../defines.hpp" #include "../../helpers/AnimatedVariable.hpp" #include "../../desktop/DesktopTypes.hpp" +#include "../../helpers/time/Timer.hpp" #include "../eventLoop/EventLoopTimer.hpp" class CHyprAnimationManager : public Hyprutils::Animation::CAnimationManager { @@ -13,20 +14,22 @@ class CHyprAnimationManager : public Hyprutils::Animation::CAnimationManager { CHyprAnimationManager(); void tick(); + void frameTick(); 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 @@ -52,7 +55,9 @@ class CHyprAnimationManager : public Hyprutils::Animation::CAnimationManager { float m_lastTickTimeMs; private: - bool m_tickScheduled = false; + bool m_tickScheduled = false; + bool m_lastTickValid = false; + CTimer m_lastTickTimer; }; inline UP g_pAnimationManager; diff --git a/src/managers/animation/DesktopAnimationManager.cpp b/src/managers/animation/DesktopAnimationManager.cpp index f156dfa9..2c450add 100644 --- a/src/managers/animation/DesktopAnimationManager.cpp +++ b/src/managers/animation/DesktopAnimationManager.cpp @@ -1,11 +1,15 @@ #include "DesktopAnimationManager.hpp" -#include "../../desktop/LayerSurface.hpp" -#include "../../desktop/Window.hpp" +#include + +#include "../../desktop/view/LayerSurface.hpp" +#include "../../desktop/view/Window.hpp" +#include "../../desktop/view/Group.hpp" #include "../../desktop/Workspace.hpp" #include "../../config/ConfigManager.hpp" #include "../../Compositor.hpp" +#include "desktop/DesktopTypes.hpp" #include "wlr-layer-shell-unstable-v1.hpp" void CDesktopAnimationManager::startAnimation(PHLWINDOW pWindow, eAnimationType type, bool force) { @@ -41,8 +45,8 @@ void CDesktopAnimationManager::startAnimation(PHLWINDOW pWindow, eAnimationType if (!pWindow->m_realPosition->enabled() && !force) return; - if (pWindow->m_windowData.animationStyle.hasValue()) { - const auto STYLE = pWindow->m_windowData.animationStyle.value(); + if (pWindow->m_ruleApplicator->animationStyle().hasValue()) { + const auto STYLE = pWindow->m_ruleApplicator->animationStyle().value(); // the window has config'd special anim if (STYLE.starts_with("slide")) { CVarList animList2(STYLE, 0, 's'); @@ -106,7 +110,7 @@ void CDesktopAnimationManager::startAnimation(PHLLS ls, eAnimationType type, boo ls->m_alpha->setConfig(g_pConfigManager->getAnimationPropertyConfig("fadeLayersOut")); } - const auto ANIMSTYLE = ls->m_animationStyle.value_or(ls->m_realPosition->getStyle()); + const auto ANIMSTYLE = ls->m_ruleApplicator->animationStyle().valueOr(ls->m_realPosition->getStyle()); if (ANIMSTYLE.starts_with("slide")) { // get closest edge const auto MIDDLE = ls->m_geometry.middle(); @@ -279,7 +283,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 +410,26 @@ void CDesktopAnimationManager::animationSlide(PHLWINDOW pWindow, std::string for } const auto MIDPOINT = GOALPOS + GOALSIZE / 2.f; + const auto MONBOX = PMONITOR->logicalBox(); - // check sides it touches - const 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 +470,7 @@ void CDesktopAnimationManager::setFullscreenFadeAnimation(PHLWORKSPACE ws, eAnim *w->m_alpha = 1.F; else if (!w->isFullscreen()) { const bool CREATED_OVER_FS = w->m_createdOverFullscreen; - const bool IS_IN_GROUP_OF_FS = FSWINDOW && FSWINDOW->hasInGroup(w); + const bool IS_IN_GROUP_OF_FS = FSWINDOW && FSWINDOW->m_group && FSWINDOW->m_group->has(w); *w->m_alpha = !CREATED_OVER_FS && !IS_IN_GROUP_OF_FS ? 0.f : 1.f; } } @@ -481,12 +480,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 f27f09d2..fa86425e 100644 --- a/src/managers/animation/DesktopAnimationManager.hpp +++ b/src/managers/animation/DesktopAnimationManager.hpp @@ -16,6 +16,7 @@ class CDesktopAnimationManager { void startAnimation(PHLWORKSPACE ws, eAnimationType type, bool left = true, bool instant = false); void setFullscreenFadeAnimation(PHLWORKSPACE ws, eAnimationType type); + void setFullscreenFloatingFade(PHLWINDOW pWindow, float fade); void overrideFullscreenFadeAmount(PHLWORKSPACE ws, float fade, PHLWINDOW exclude = nullptr); private: diff --git a/src/managers/eventLoop/EventLoopManager.cpp b/src/managers/eventLoop/EventLoopManager.cpp index 1426e424..e38474aa 100644 --- a/src/managers/eventLoop/EventLoopManager.cpp +++ b/src/managers/eventLoop/EventLoopManager.cpp @@ -1,5 +1,5 @@ #include "EventLoopManager.hpp" -#include "../../debug/Log.hpp" +#include "../../debug/log/Logger.hpp" #include "../../Compositor.hpp" #include "../../config/ConfigWatcher.hpp" @@ -26,10 +26,7 @@ CEventLoopManager::~CEventLoopManager() { wl_event_source_remove(eventSourceData.eventSource); } - for (auto const& w : m_readableWaiters) { - if (w->source != nullptr) - wl_event_source_remove(w->source); - } + m_readableWaiters.clear(); if (m_wayland.eventSource) wl_event_source_remove(m_wayland.eventSource); @@ -40,14 +37,21 @@ CEventLoopManager::~CEventLoopManager() { } static int timerWrite(int fd, uint32_t mask, void* data) { + if (!CFileDescriptor::isReadable(fd)) + Log::logger->log(Log::ERR, "timerWrite: triggered a non readable event on fd : {}", fd); + else { + uint64_t expirations; + read(fd, &expirations, sizeof(expirations)); + } + g_pEventLoopManager->onTimerFire(); - return 1; + return 0; } static int aquamarineFDWrite(int fd, uint32_t mask, void* data) { auto POLLFD = sc(data); POLLFD->onSignal(); - return 1; + return 0; } static int configWatcherWrite(int fd, uint32_t mask, void* data) { @@ -56,13 +60,21 @@ static int configWatcherWrite(int fd, uint32_t mask, void* data) { } static int handleWaiterFD(int fd, uint32_t mask, void* data) { + auto waiter = sc(data); + + if (!waiter) { + Log::logger->log(Log::ERR, "handleWaiterFD: failed casting waiter"); + return 0; + } + if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { - 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 +86,11 @@ void CEventLoopManager::onFdReadable(SReadableWaiter* waiter) { if (it == m_readableWaiters.end()) return; + if (waiter->source) { // remove even_source if fn() somehow causes a reentry + wl_event_source_remove(waiter->source); + waiter->source = nullptr; + } + UP taken = std::move(*it); m_readableWaiters.erase(it); @@ -81,6 +98,16 @@ void CEventLoopManager::onFdReadable(SReadableWaiter* waiter) { taken->fn(); } +void CEventLoopManager::onFdReadableFail(SReadableWaiter* waiter) { + auto it = std::ranges::find_if(m_readableWaiters, [waiter](const UP& w) { return waiter == w.get() && w->fd == waiter->fd && w->source == waiter->source; }); + + // ??? + if (it == m_readableWaiters.end()) + return; + + m_readableWaiters.erase(it); +} + void CEventLoopManager::enterLoop() { m_wayland.eventSource = wl_event_loop_add_fd(m_wayland.loop, m_timers.timerfd.get(), WL_EVENT_READABLE, timerWrite, nullptr); @@ -96,7 +123,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 +209,12 @@ void CEventLoopManager::doLater(const std::function& fn) { m_wayland.loop, [](void* data) { auto IDLE = sc(data); - auto cpy = IDLE->fns; + auto fns = std::move(IDLE->fns); IDLE->fns.clear(); IDLE->eventSource = nullptr; - for (auto const& c : cpy) { - if (c) - c(); + for (auto& f : fns) { + if (f) + f(); } }, &m_idle); diff --git a/src/managers/eventLoop/EventLoopManager.hpp b/src/managers/eventLoop/EventLoopManager.hpp index 7a3b4314..7999dc59 100644 --- a/src/managers/eventLoop/EventLoopManager.hpp +++ b/src/managers/eventLoop/EventLoopManager.hpp @@ -65,6 +65,7 @@ class CEventLoopManager { // takes ownership of fd void doOnReadable(Hyprutils::OS::CFileDescriptor fd, std::function&& fn); void onFdReadable(SReadableWaiter* waiter); + void onFdReadableFail(SReadableWaiter* waiter); private: // Manages the event sources after AQ pollFDs change. diff --git a/src/managers/input/IdleInhibitor.cpp b/src/managers/input/IdleInhibitor.cpp index 82f43f47..02eefc1d 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; } @@ -61,13 +61,13 @@ void CInputManager::recheckIdleInhibitorStatus() { } bool CInputManager::isWindowInhibiting(const PHLWINDOW& w, bool onlyHl) { - if (w->m_idleInhibitMode == IDLEINHIBIT_ALWAYS) + if (w->m_ruleApplicator->idleInhibitMode().valueOrDefault() == Desktop::Rule::IDLEINHIBIT_ALWAYS) return true; - if (w->m_idleInhibitMode == IDLEINHIBIT_FOCUS && g_pCompositor->isWindowActive(w)) + if (w->m_ruleApplicator->idleInhibitMode().valueOrDefault() == Desktop::Rule::IDLEINHIBIT_FOCUS && g_pCompositor->isWindowActive(w)) return true; - if (w->m_idleInhibitMode == IDLEINHIBIT_FULLSCREEN && w->isFullscreen() && w->m_workspace && w->m_workspace->isVisible()) + if (w->m_ruleApplicator->idleInhibitMode().valueOrDefault() == Desktop::Rule::IDLEINHIBIT_FULLSCREEN && w->isFullscreen() && w->m_workspace && w->m_workspace->isVisible()) return true; if (onlyHl) @@ -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 fe93a83b..9195536f 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -7,8 +7,8 @@ #include #include "../../config/ConfigValue.hpp" #include "../../config/ConfigManager.hpp" -#include "../../desktop/Window.hpp" -#include "../../desktop/LayerSurface.hpp" +#include "../../desktop/view/WLSurface.hpp" +#include "../../desktop/state/FocusState.hpp" #include "../../protocols/CursorShape.hpp" #include "../../protocols/IdleInhibit.hpp" #include "../../protocols/RelativePointer.hpp" @@ -35,14 +35,16 @@ #include "../../managers/SeatManager.hpp" #include "../../managers/KeybindManager.hpp" #include "../../render/Renderer.hpp" -#include "../../managers/HookSystemManager.hpp" #include "../../managers/EventManager.hpp" -#include "../../managers/LayoutManager.hpp" #include "../../managers/permissions/DynamicPermissionManager.hpp" #include "../../helpers/time/Time.hpp" #include "../../helpers/MiscFunctions.hpp" +#include "../../layout/LayoutManager.hpp" + +#include "../../event/EventBus.hpp" + #include "trackpad/TrackpadGestures.hpp" #include "../cursor/CursorShapeOverrideController.hpp" @@ -50,22 +52,22 @@ CInputManager::CInputManager() { m_listeners.setCursorShape = PROTO::cursorShape->m_events.setShape.listen([this](const CCursorShapeProtocol::SSetShapeEvent& event) { - if (!cursorImageUnlocked()) - return; - if (!g_pSeatManager->m_state.pointerFocusResource) return; 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 = {}; m_cursorSurfaceInfo.name = event.shapeName; m_cursorSurfaceInfo.hidden = false; + if (!cursorImageUnlocked()) + return; + g_pHyprRenderer->setCursorFromName(m_cursorSurfaceInfo.name); }); @@ -94,7 +96,7 @@ CInputManager::CInputManager() { g_pHyprRenderer->setCursorFromName(shape); }); - m_cursorSurfaceInfo.wlSurface = CWLSurface::create(); + m_cursorSurfaceInfo.wlSurface = Desktop::View::CWLSurface::create(); } CInputManager::~CInputManager() { @@ -130,26 +132,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) { @@ -159,7 +158,8 @@ void CInputManager::onMouseWarp(IPointer::SMotionAbsoluteEvent e) { m_lastCursorMovement.reset(); - m_lastInputTouch = false; + m_lastInputTouch = false; + m_lastInputTablet = false; } void CInputManager::simulateMouseMovement() { @@ -168,18 +168,32 @@ void CInputManager::simulateMouseMovement() { } void CInputManager::sendMotionEventsToFocused() { - if (!g_pCompositor->m_lastFocus || isConstrained()) + if (!Desktop::focusState()->surface() || isConstrained()) return; - // todo: this sucks ass - const auto PWINDOW = g_pCompositor->getWindowFromSurface(g_pCompositor->m_lastFocus.lock()); - const auto PLS = g_pCompositor->getLayerSurfaceFromSurface(g_pCompositor->m_lastFocus.lock()); + 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(g_pCompositor->m_lastFocus.lock(), LOCAL); + g_pSeatManager->setPointerFocus(Desktop::focusState()->surface(), getMouseCoordsInternal().floor() - BOX->pos()); } void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, std::optional overridePos) { @@ -218,12 +232,16 @@ 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() && g_pCompositor->m_lastMonitor ? g_pCompositor->m_lastMonitor.lock() : g_pCompositor->getMonitorFromCursor(); + const auto PMONITOR = isLocked() && Desktop::focusState()->monitor() ? Desktop::focusState()->monitor() : g_pCompositor->getMonitorFromCursor(); // this can happen if there are no displays hooked up to Hyprland if (PMONITOR == nullptr) @@ -239,7 +257,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st // constraints if (!g_pSeatManager->m_mouse.expired() && isConstrained()) { - const auto SURF = CWLSurface::fromResource(g_pCompositor->m_lastFocus.lock()); + const auto SURF = Desktop::View::CWLSurface::fromResource(Desktop::focusState()->surface()); const auto CONSTRAINT = SURF ? SURF->constraint() : nullptr; if (CONSTRAINT) { @@ -250,7 +268,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); @@ -260,14 +279,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 != g_pCompositor->m_lastMonitor && (*PMOUSEFOCUSMON || refocus) && m_forcedFocus.expired()) - g_pCompositor->setActiveMonitor(PMONITOR); + 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); @@ -277,7 +297,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st const auto PSESSIONLOCKSURFACE = g_pSessionLockManager->getSessionLockSurfaceForMonitor(PMONITOR->m_id); const auto foundLockSurface = PSESSIONLOCKSURFACE ? PSESSIONLOCKSURFACE->surface->surface() : nullptr; - g_pCompositor->focusSurface(foundLockSurface); + Desktop::focusState()->rawSurfaceFocus(foundLockSurface); // search for interactable abovelock surfaces for pointer focus, or use session lock surface if not found for (auto& lsl : PMONITOR->m_layerSurfaceLayers | std::views::reverse) { @@ -312,12 +332,12 @@ 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() && g_pCompositor->m_lastFocus && g_pCompositor->m_lastFocus->m_mapped && + if (!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(); @@ -329,17 +349,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() ? g_pCompositor->m_lastWindow.lock() : PWINDOW; + + if (PWINDOW) + pFoundWindow = PWINDOW; + else if (LS) + pFoundLayerSurface = LS; + } else // reset foundSurface, find one normally foundSurface = nullptr; } else // reset foundSurface, find one normally @@ -347,7 +371,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st } } - g_pLayoutManager->getCurrentLayout()->onMouseMove(getMouseCoordsInternal()); + g_layoutManager->moveMouse(getMouseCoordsInternal()); // forced above all if (!g_pInputManager->m_exclusiveLSes.empty()) { @@ -355,7 +379,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,27 +406,36 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st // 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 PWINDOWIDEAL = g_pCompositor->vectorToWindowUnified(mouseCoords, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING); + 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 (!pFoundWindow) { - // what the fuck, somehow happens occasionally?? - PWORKSPACE->m_hasFullscreenWindow = false; - return; - } + if (IS_LS_UNFOCUSABLE) { + foundSurface = nullptr; + pFoundLayerSurface = nullptr; - 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; + pFoundWindow = PWORKSPACE->getFullscreenWindow(); - 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 (!pFoundWindow) { + // what the fuck, somehow happens occasionally?? + PWORKSPACE->m_hasFullscreenWindow = false; + return; + } + + 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,7 +445,8 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st if (!foundSurface) { if (PMONITOR->m_activeSpecialWorkspace) { if (pFoundWindow != PWINDOWIDEAL) - pFoundWindow = g_pCompositor->vectorToWindowUnified(mouseCoords, RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING); + pFoundWindow = + g_pCompositor->vectorToWindowUnified(mouseCoords, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING); if (pFoundWindow && !pFoundWindow->onSpecialWorkspace()) { pFoundWindow = PWORKSPACE->getFullscreenWindow(); @@ -426,7 +460,8 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st if (!foundSurface) { if (pFoundWindow != PWINDOWIDEAL) - pFoundWindow = g_pCompositor->vectorToWindowUnified(mouseCoords, RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING); + pFoundWindow = + g_pCompositor->vectorToWindowUnified(mouseCoords, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING); if (!(pFoundWindow && (pFoundWindow->m_isFloating && (pFoundWindow->m_createdOverFullscreen || pFoundWindow->m_pinned)))) pFoundWindow = PWORKSPACE->getFullscreenWindow(); @@ -436,18 +471,18 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st } else { if (pFoundWindow != PWINDOWIDEAL) - pFoundWindow = g_pCompositor->vectorToWindowUnified(mouseCoords, RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING); + pFoundWindow = g_pCompositor->vectorToWindowUnified(mouseCoords, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING); } 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 +496,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st 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) - g_pCompositor->scheduleFrameForMonitor(g_pCompositor->m_lastMonitor.lock(), Aquamarine::IOutput::AQ_SCHEDULE_CURSOR_MOVE); + 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 // xdg-popup grabs should be keyboard-only, while they are absolute in our case... @@ -473,7 +508,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(); @@ -492,8 +527,8 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st g_pSeatManager->setPointerFocus(nullptr, {}); - if (refocus || g_pCompositor->m_lastWindow.expired()) // if we are forcing a refocus, and we don't find a surface, clear the kb focus too! - g_pCompositor->focusWindow(nullptr); + if (refocus || !Desktop::focusState()->window()) // if we are forcing a refocus, and we don't find a surface, clear the kb focus too! + Desktop::focusState()->rawWindowFocus(nullptr, FOCUS_REASON); return; } @@ -502,20 +537,13 @@ 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; bool allowKeyboardRefocus = true; - if (!refocus && g_pCompositor->m_lastFocus) { - const auto PLS = g_pCompositor->getLayerSurfaceFromSurface(g_pCompositor->m_lastFocus.lock()); + if (!refocus && Desktop::focusState()->surface()) { + const auto PLS = g_pCompositor->getLayerSurfaceFromSurface(Desktop::focusState()->surface()); if (PLS && PLS->m_layerSurface->m_current.interactivity == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) allowKeyboardRefocus = false; @@ -528,12 +556,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"); @@ -546,22 +574,29 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st if (*PRESIZEONBORDER && *PRESIZECURSORICON) { if (!pFoundWindow->isFullscreen() && !pFoundWindow->hasPopupAt(mouseCoords)) setCursorIconOnBorder(pFoundWindow); + else if (m_borderIconDirection != BORDERICON_NONE) { + m_borderIconDirection = BORDERICON_NONE; + Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_WINDOW_EDGE); + } + } else if (m_borderIconDirection != BORDERICON_NONE) { + m_borderIconDirection = BORDERICON_NONE; + Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_WINDOW_EDGE); } if (FOLLOWMOUSE != 1 && !refocus) { - if (pFoundWindow != g_pCompositor->m_lastWindow.lock() && g_pCompositor->m_lastWindow.lock() && - ((pFoundWindow->m_isFloating && *PFLOATBEHAVIOR == 2) || (g_pCompositor->m_lastWindow->m_isFloating != pFoundWindow->m_isFloating && *PFLOATBEHAVIOR != 0))) { + if (pFoundWindow != Desktop::focusState()->window() && Desktop::focusState()->window() && + ((pFoundWindow->m_isFloating && *PFLOATBEHAVIOR == 2) || (Desktop::focusState()->window()->m_isFloating != pFoundWindow->m_isFloating && *PFLOATBEHAVIOR != 0))) { // enter if change floating style if (FOLLOWMOUSE != 3 && allowKeyboardRefocus) - g_pCompositor->focusWindow(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); - if (pFoundWindow == g_pCompositor->m_lastWindow) + if (pFoundWindow == Desktop::focusState()->window()) g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal); - if (FOLLOWMOUSE != 0 || pFoundWindow == g_pCompositor->m_lastWindow) + if (FOLLOWMOUSE != 0 || pFoundWindow == Desktop::focusState()->window()) g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal); if (g_pSeatManager->m_state.pointerFocus == foundSurface) @@ -571,26 +606,26 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st return; // don't enter any new surfaces } else { if (allowKeyboardRefocus && ((FOLLOWMOUSE != 3 && (*PMOUSEREFOCUS || m_lastMouseFocus.lock() != pFoundWindow)) || refocus)) { - if (m_lastMouseFocus.lock() != pFoundWindow || g_pCompositor->m_lastWindow.lock() != pFoundWindow || g_pCompositor->m_lastFocus != foundSurface || refocus) { + if (m_lastMouseFocus.lock() != 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. // Temp fix until that's figured out. Otherwise spams windowrule lookups and other shit. - if (m_lastMouseFocus.lock() != pFoundWindow || g_pCompositor->m_lastWindow.lock() != pFoundWindow) { + if (m_lastMouseFocus.lock() != pFoundWindow || Desktop::focusState()->window() != pFoundWindow) { if (m_mousePosDelta > *PFOLLOWMOUSETHRESHOLD || refocus) { - const bool hasNoFollowMouse = pFoundWindow && pFoundWindow->m_windowData.noFollowMouse.valueOrDefault(); + const bool hasNoFollowMouse = pFoundWindow && pFoundWindow->m_ruleApplicator->noFollowMouse().valueOrDefault(); if (refocus || !hasNoFollowMouse) - g_pCompositor->focusWindow(pFoundWindow, foundSurface); + Desktop::focusState()->rawWindowFocus(pFoundWindow, FOCUS_REASON, foundSurface); } } else - g_pCompositor->focusSurface(foundSurface, pFoundWindow); + Desktop::focusState()->rawSurfaceFocus(foundSurface, pFoundWindow); } } } if (g_pSeatManager->m_state.keyboardFocus == nullptr) - g_pCompositor->focusWindow(pFoundWindow, foundSurface); + Desktop::focusState()->rawWindowFocus(pFoundWindow, FOCUS_REASON, foundSurface); m_lastFocusOnLS = false; } else { @@ -601,7 +636,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st if (pFoundLayerSurface && (pFoundLayerSurface->m_layerSurface->m_current.interactivity != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE) && FOLLOWMOUSE != 3 && (allowKeyboardRefocus || pFoundLayerSurface->m_layerSurface->m_current.interactivity == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE)) { - g_pCompositor->focusSurface(foundSurface); + Desktop::focusState()->rawSurfaceFocus(foundSurface); } if (pFoundLayerSurface) @@ -613,7 +648,10 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st } void CInputManager::onMouseButton(IPointer::SButtonEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("mouseButton", e); + Event::SCallbackInfo info; + Event::bus()->m_events.input.mouse.button.emit(e, info); + if (info.cancelled) + return; if (e.mouse) recheckMouseWarpOnMouseInput(); @@ -643,13 +681,12 @@ void CInputManager::onMouseButton(IPointer::SButtonEvent e) { m_focusHeldByButtons = false; m_refocusHeldByButtons = false; } + + g_pSeatManager->sendPointerFrame(); } void CInputManager::processMouseRequest(const CSeatManager::SSetCursorEvent& event) { - if (!cursorImageUnlocked()) - return; - - 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(); @@ -668,6 +705,9 @@ void CInputManager::processMouseRequest(const CSeatManager::SSetCursorEvent& eve m_cursorSurfaceInfo.name = ""; + if (!cursorImageUnlocked()) + return; + g_pHyprRenderer->setCursorSurface(m_cursorSurfaceInfo.wlSurface, event.hotspot.x, event.hotspot.y); } @@ -695,13 +735,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 @@ -730,7 +770,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; @@ -755,7 +795,7 @@ void CInputManager::processMouseDownNormal(const IPointer::SButtonEvent& e) { break; if ((g_pSeatManager->m_mouse.expired() || !isConstrained()) /* No constraints */ - && (w && g_pCompositor->m_lastWindow.lock() != w) /* window should change */) { + && (w && Desktop::focusState()->window() != w) /* window should change */) { // a bit hacky // if we only pressed one button, allow us to refocus. m_lCurrentlyHeldButtons.size() > 0 will stick the focus if (m_currentlyHeldButtons.size() == 1) { @@ -771,10 +811,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; } @@ -784,8 +826,8 @@ void CInputManager::processMouseDownNormal(const IPointer::SButtonEvent& e) { // notify app if we didn't handle it g_pSeatManager->sendPointerButton(e.timeMs, e.button, e.state); - if (const auto PMON = g_pCompositor->getMonitorFromVector(mouseCoords); PMON != g_pCompositor->m_lastMonitor && PMON) - g_pCompositor->setActiveMonitor(PMON); + if (const auto PMON = g_pCompositor->getMonitorFromVector(mouseCoords); PMON != Desktop::focusState()->monitor() && PMON) + Desktop::focusState()->rawMonitorFocus(PMON); if (g_pSeatManager->m_seatGrab && e.state == WL_POINTER_BUTTON_STATE_PRESSED) { m_hardInput = true; @@ -797,13 +839,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; @@ -830,8 +876,10 @@ void CInputManager::onMouseWheel(IPointer::SAxisEvent e, SP pointer) { if (pointer && pointer->m_scrollFactor.has_value()) factor = *pointer->m_scrollFactor; - const auto EMAP = std::unordered_map{{"event", e}}; - EMIT_HOOK_EVENT_CANCELLABLE("mouseAxis", EMAP); + Event::SCallbackInfo info; + Event::bus()->m_events.input.mouse.axis.emit(e, info); + if (info.cancelled) + return; if (e.mouse) recheckMouseWarpOnMouseInput(); @@ -843,7 +891,7 @@ void CInputManager::onMouseWheel(IPointer::SAxisEvent e, SP pointer) { 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)) @@ -918,6 +966,23 @@ void CInputManager::onMouseWheel(IPointer::SAxisEvent e, SP pointer) { int32_t deltaDiscrete = std::abs(discrete) != 0 && std::abs(discrete) < 1 ? std::copysign(1, discrete) : std::round(discrete); g_pSeatManager->sendPointerAxis(e.timeMs, e.axis, delta, deltaDiscrete, value120, e.source, WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL); + + const bool deferPointerFrame = e.source == WL_POINTER_AXIS_SOURCE_FINGER || e.source == WL_POINTER_AXIS_SOURCE_CONTINUOUS; + if (deferPointerFrame) { + m_pointerAxisFramePending = true; + return; + } + + m_pointerAxisFramePending = false; + g_pSeatManager->sendPointerFrame(); +} + +void CInputManager::onPointerFrame() { + if (!m_pointerAxisFramePending) + return; + + m_pointerAxisFramePending = false; + g_pSeatManager->sendPointerFrame(); } Vector2D CInputManager::getMouseCoordsInternal() { @@ -929,7 +994,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) { @@ -937,7 +1002,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) { @@ -945,7 +1010,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) { @@ -956,7 +1021,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()] { @@ -966,7 +1031,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) { @@ -1003,7 +1068,7 @@ void CInputManager::setupKeyboard(SP keeb) { } g_pEventManager->postEvent(SHyprIPCEvent{"activelayout", PKEEB->m_hlName + "," + LAYOUT}); - EMIT_HOOK_EVENT("activeLayout", (std::vector{PKEEB, LAYOUT})); + Event::bus()->m_events.input.keyboard.layout.emit(PKEEB, LAYOUT); }); disableAllKeyboards(false); @@ -1015,8 +1080,8 @@ void CInputManager::setupKeyboard(SP keeb) { keeb->updateLEDs(); // in case m_lastFocus was set without a keyboard - if (m_keyboards.size() == 1 && g_pCompositor->m_lastFocus) - g_pSeatManager->setKeyboardFocus(g_pCompositor->m_lastFocus.lock()); + if (m_keyboards.size() == 1 && Desktop::focusState()->surface()) + g_pSeatManager->setKeyboardFocus(Desktop::focusState()->surface()); } void CInputManager::setKeyboardLayout() { @@ -1031,7 +1096,7 @@ void CInputManager::applyConfigToKeyboard(SP pKeyboard) { const auto HASCONFIG = g_pConfigManager->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"); @@ -1054,14 +1119,19 @@ void CInputManager::applyConfigToKeyboard(SP pKeyboard) { 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; } @@ -1078,7 +1148,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) { @@ -1095,10 +1165,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) { @@ -1106,7 +1176,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) { @@ -1114,7 +1184,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) { @@ -1122,7 +1192,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) { @@ -1131,15 +1201,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); @@ -1206,7 +1276,7 @@ void CInputManager::setPointerConfigs() { 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"); @@ -1221,7 +1291,7 @@ 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) @@ -1300,15 +1370,15 @@ 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"); @@ -1320,7 +1390,7 @@ void CInputManager::setPointerConfigs() { 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); } } } @@ -1331,7 +1401,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; }); @@ -1355,7 +1425,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; }); @@ -1368,7 +1438,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; }); @@ -1376,7 +1446,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; }); @@ -1384,7 +1454,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; }); @@ -1392,7 +1462,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; }); @@ -1417,14 +1487,16 @@ void CInputManager::onKeyboardKey(const IKeyboard::SKeyEvent& event, SPm_enabled || !pKeyboard->m_allowed) return; - const bool DISALLOWACTION = pKeyboard->isVirtual() && shouldIgnoreVirtualKeyboard(pKeyboard); + const bool DISALLOWACTION = pKeyboard->isVirtual() && shouldIgnoreVirtualKeyboard(pKeyboard); - const auto IME = m_relay.m_inputMethod.lock(); - const bool HASIME = IME && IME->hasGrab(); - const bool USEIME = HASIME && !DISALLOWACTION; + const auto IME = m_relay.m_inputMethod.lock(); + const bool HASIME = IME && IME->hasGrab(); + const bool USEIME = HASIME && !DISALLOWACTION; - const auto EMAP = std::unordered_map{{"keyboard", pKeyboard}, {"event", event}}; - EMIT_HOOK_EVENT_CANCELLABLE("keyPress", EMAP); + Event::SCallbackInfo info; + Event::bus()->m_events.input.keyboard.key.emit(event, info); + if (info.cancelled) + return; bool passEvent = DISALLOWACTION; @@ -1510,10 +1582,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); } } @@ -1540,7 +1612,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; } @@ -1570,16 +1642,16 @@ bool CInputManager::refocusLastWindow(PHLMONITOR pMonitor) { foundSurface = nullptr; } - if (!foundSurface && g_pCompositor->m_lastWindow.lock() && g_pCompositor->m_lastWindow->m_workspace && g_pCompositor->m_lastWindow->m_workspace->isVisibleNotCovered()) { + 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 = g_pCompositor->m_lastWindow.lock(); - g_pCompositor->focusWindow(PLASTWINDOW); + const auto PLASTWINDOW = Desktop::focusState()->window(); + 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 = g_pCompositor->m_lastWindow.lock(); - g_pCompositor->focusWindow(PLASTWINDOW); + const auto PLASTWINDOW = Desktop::focusState()->window(); + Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_FFM); } refocus(); @@ -1608,7 +1680,7 @@ void CInputManager::unconstrainMouse() { bool CInputManager::isConstrained() { return std::ranges::any_of(m_constraints, [](auto const& c) { const auto constraint = c.lock(); - return constraint && constraint->isActive() && constraint->owner()->resource() == g_pCompositor->m_lastFocus; + return constraint && constraint->isActive() && constraint->owner()->resource() == Desktop::focusState()->surface(); }); } @@ -1616,7 +1688,7 @@ bool CInputManager::isLocked() { if (!isConstrained()) return false; - const auto SURF = CWLSurface::fromResource(g_pCompositor->m_lastFocus.lock()); + const auto SURF = Desktop::View::CWLSurface::fromResource(Desktop::focusState()->surface()); const auto CONSTRAINT = SURF ? SURF->constraint() : nullptr; return CONSTRAINT && CONSTRAINT->isLocked(); @@ -1704,7 +1776,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); @@ -1719,7 +1791,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) { @@ -1733,7 +1805,7 @@ void CInputManager::setTouchDeviceConfigs(SP dev) { 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); if (ROTATION > -1) @@ -1754,10 +1826,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); } }; @@ -1781,7 +1853,7 @@ void CInputManager::setTabletConfigs() { 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); + Log::logger->log(Log::DEBUG, "Setting calibration matrix for device {}", NAME); if (ROTATION > -1) libinput_device_config_calibration_set_matrix(LIBINPUTDEV, MATRICES[ROTATION]); @@ -1792,7 +1864,7 @@ void CInputManager::setTabletConfigs() { const auto OUTPUT = g_pConfigManager->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 = ""; @@ -1823,22 +1895,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); } }); @@ -1895,7 +1967,7 @@ void CInputManager::setCursorIconOnBorder(PHLWINDOW w) { if (w->hasPopupAt(mouseCoords)) direction = BORDERICON_NONE; - else if (!boxFullGrabInput.containsPoint(mouseCoords) || (!m_currentlyHeldButtons.empty() && m_currentlyDraggedWindow.expired())) + else if (!boxFullGrabInput.containsPoint(mouseCoords) || (!m_currentlyHeldButtons.empty() && !g_layoutManager->dragController()->target())) direction = BORDERICON_NONE; else { @@ -1981,7 +2053,10 @@ void CInputManager::recheckMouseWarpOnMouseInput() { } void CInputManager::onSwipeBegin(IPointer::SSwipeBeginEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("swipeBegin", e); + Event::SCallbackInfo info; + Event::bus()->m_events.gesture.swipe.begin.emit(e, info); + if (info.cancelled) + return; g_pTrackpadGestures->gestureBegin(e); @@ -1989,7 +2064,10 @@ void CInputManager::onSwipeBegin(IPointer::SSwipeBeginEvent e) { } void CInputManager::onSwipeUpdate(IPointer::SSwipeUpdateEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("swipeUpdate", e); + Event::SCallbackInfo info; + Event::bus()->m_events.gesture.swipe.update.emit(e, info); + if (info.cancelled) + return; g_pTrackpadGestures->gestureUpdate(e); @@ -1997,7 +2075,10 @@ void CInputManager::onSwipeUpdate(IPointer::SSwipeUpdateEvent e) { } void CInputManager::onSwipeEnd(IPointer::SSwipeEndEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("swipeEnd", e); + Event::SCallbackInfo info; + Event::bus()->m_events.gesture.swipe.end.emit(e, info); + if (info.cancelled) + return; g_pTrackpadGestures->gestureEnd(e); @@ -2005,7 +2086,10 @@ void CInputManager::onSwipeEnd(IPointer::SSwipeEndEvent e) { } void CInputManager::onPinchBegin(IPointer::SPinchBeginEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("pinchBegin", e); + Event::SCallbackInfo info; + Event::bus()->m_events.gesture.pinch.begin.emit(e, info); + if (info.cancelled) + return; g_pTrackpadGestures->gestureBegin(e); @@ -2013,7 +2097,10 @@ void CInputManager::onPinchBegin(IPointer::SPinchBeginEvent e) { } void CInputManager::onPinchUpdate(IPointer::SPinchUpdateEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("pinchUpdate", e); + Event::SCallbackInfo info; + Event::bus()->m_events.gesture.pinch.update.emit(e, info); + if (info.cancelled) + return; g_pTrackpadGestures->gestureUpdate(e); @@ -2021,7 +2108,10 @@ void CInputManager::onPinchUpdate(IPointer::SPinchUpdateEvent e) { } void CInputManager::onPinchEnd(IPointer::SPinchEndEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("pinchEnd", e); + Event::SCallbackInfo info; + Event::bus()->m_events.gesture.pinch.end.emit(e, info); + if (info.cancelled) + return; g_pTrackpadGestures->gestureEnd(e); diff --git a/src/managers/input/InputManager.hpp b/src/managers/input/InputManager.hpp index 9fbf68b4..ca235a23 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; @@ -91,6 +91,7 @@ class CInputManager { void onMouseWarp(IPointer::SMotionAbsoluteEvent); void onMouseButton(IPointer::SButtonEvent); void onMouseWheel(IPointer::SAxisEvent, SP pointer = nullptr); + void onPointerFrame(); void onKeyboardKey(const IKeyboard::SKeyEvent&, SP); void onKeyboardMod(SP); @@ -153,12 +154,6 @@ class CInputManager { STouchData m_touchData; - // for dragging floating windows - PHLWINDOWREF m_currentlyDraggedWindow; - eMouseBindMode m_dragMode = MBIND_INVALID; - bool m_wasDraggingWindow = false; - bool m_dragThresholdReached = false; - // for refocus to be forced PHLWINDOWREF m_forcedFocus; @@ -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; @@ -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 27acd08c..9a891213 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; } @@ -120,11 +120,18 @@ void CInputPopup::updateBox() { CBox cursorBoxLocal({-popupOffset.x, -popupOffset.y}, cursorBoxParent.size()); m_popup->sendInputRectangle(cursorBoxLocal); - CBox popupBoxParent(cursorBoxParent.pos() + popupOffset, currentPopupSize); - if (popupBoxParent != m_lastBoxLocal) { + CBox popupBoxParent(cursorBoxParent.pos() + popupOffset, currentPopupSize); + const bool boxChanged = popupBoxParent != m_lastBoxLocal; + if (boxChanged) + damageEntire(); // damage the old location before updating + + m_lastBoxLocal = popupBoxParent; + + // Since a redraw request is not always sent when only the position is updated, + // a manual redraw may be required in some cases. + if (boxChanged) damageEntire(); - m_lastBoxLocal = popupBoxParent; - } + damageSurface(); if (const auto PM = g_pCompositor->getMonitorFromCursor(); PM && PM->m_id != m_lastMonitor) { @@ -143,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 20767963..a7014973 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 c09d0624..27fd80b6 100644 --- a/src/managers/input/InputMethodRelay.cpp +++ b/src/managers/input/InputMethodRelay.cpp @@ -1,15 +1,13 @@ #include "InputMethodRelay.hpp" -#include "InputManager.hpp" -#include "../../Compositor.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); }); @@ -18,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(); @@ -31,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; } @@ -41,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(); @@ -51,20 +49,20 @@ 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 (!g_pCompositor->m_lastFocus) + if (!Desktop::focusState()->surface()) return; for (auto const& ti : m_textInputs) { - if (ti->client() != g_pCompositor->m_lastFocus->client()) + if (ti->client() != Desktop::focusState()->surface()->client()) continue; if (ti->isV3()) - ti->enter(g_pCompositor->m_lastFocus.lock()); + ti->enter(Desktop::focusState()->surface()); else - ti->onEnabled(g_pCompositor->m_lastFocus.lock()); + ti->onEnabled(Desktop::focusState()->surface()); } } @@ -73,11 +71,16 @@ void CInputMethodRelay::removePopup(CInputPopup* pPopup) { } CTextInput* CInputMethodRelay::getFocusedTextInput() { - if (!g_pCompositor->m_lastFocus) + if (!Desktop::focusState()->surface()) return nullptr; for (auto const& ti : m_textInputs) { - if (ti->focusedSurface() == g_pCompositor->m_lastFocus) + if (ti->focusedSurface() == Desktop::focusState()->surface() && ti->isEnabled()) + return ti.get(); + } + + for (auto const& ti : m_textInputs) { + if (ti->focusedSurface() == Desktop::focusState()->surface()) return ti.get(); } diff --git a/src/managers/input/Tablets.cpp b/src/managers/input/Tablets.cpp index 7a9c359a..a2fec15c 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 5da5e47a..be9a5d29 100644 --- a/src/managers/input/TextInput.cpp +++ b/src/managers/input/TextInput.cpp @@ -1,8 +1,7 @@ #include "TextInput.hpp" -#include "../../defines.hpp" #include "InputManager.hpp" #include "../../protocols/TextInputV1.hpp" -#include "../../Compositor.hpp" +#include "../../desktop/state/FocusState.hpp" #include "../../protocols/TextInputV3.hpp" #include "../../protocols/InputMethodV2.hpp" #include "../../protocols/core/Compositor.hpp" @@ -23,16 +22,10 @@ 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 (!g_pCompositor->m_lastFocus.expired() && g_pCompositor->m_lastFocus->client() == INPUT->client()) - enter(g_pCompositor->m_lastFocus.lock()); + if (Desktop::focusState()->surface() && Desktop::focusState()->surface()->client() == INPUT->client()) + enter(Desktop::focusState()->surface()); } else { const auto INPUT = m_v1Input.lock(); @@ -40,27 +33,31 @@ 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; } // v1 only, map surface to PTI if (!isV3()) { - if (g_pCompositor->m_lastFocus != surfV1 || !m_v1Input->m_active) + if (Desktop::focusState()->surface() != surfV1 || !m_v1Input->m_active) return; enter(surfV1); @@ -71,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; } @@ -108,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; } @@ -133,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--; @@ -153,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--; @@ -189,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; } @@ -209,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; } @@ -306,3 +303,7 @@ bool CTextInput::hasCursorRectangle() { CBox CTextInput::cursorBox() { return CBox{isV3() ? m_v3Input->m_current.box.cursorBox : m_v1Input->m_cursorRectangle}; } + +bool CTextInput::isEnabled() { + return isV3() ? m_v3Input->m_current.enabled.value : true; +} diff --git a/src/managers/input/TextInput.hpp b/src/managers/input/TextInput.hpp index fd24dbfa..798f31e9 100644 --- a/src/managers/input/TextInput.hpp +++ b/src/managers/input/TextInput.hpp @@ -29,6 +29,7 @@ class CTextInput { void onCommit(); void onReset(); + bool isEnabled(); bool hasCursorRectangle(); CBox cursorBox(); @@ -38,6 +39,8 @@ class CTextInput { void setFocusedSurface(SP pSurface); void initCallbacks(); + void destroy(); + WP m_focusedSurface; int m_enterLocks = 0; WP m_v3Input; diff --git a/src/managers/input/Touch.cpp b/src/managers/input/Touch.cpp index 631bd569..e45bfd28 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 "managers/animation/AnimationManager.hpp" -#include "../HookSystemManager.hpp" -#include "debug/Log.hpp" +#include "debug/log/Logger.hpp" #include "UnifiedWorkspaceSwipeGesture.hpp" void CInputManager::onTouchDown(ITouch::SDownEvent e) { @@ -19,14 +19,18 @@ void CInputManager::onTouchDown(ITouch::SDownEvent e) { static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); auto* const PGAPSOUT = sc((PGAPSOUTDATA.ptr())->getData()); // TODO: WORKSPACERULE.gapsOut.value_or() - auto gapsOut = *PGAPSOUT; - static auto PBORDERSIZE = CConfigValue("general:border_size"); - static auto PSWIPEINVR = CConfigValue("gestures:workspace_swipe_touch_invert"); - EMIT_HOOK_EVENT_CANCELLABLE("touchDown", e); + auto gapsOut = *PGAPSOUT; + static auto PBORDERSIZE = CConfigValue("general:border_size"); + static auto PSWIPEINVR = CConfigValue("gestures:workspace_swipe_touch_invert"); + + Event::SCallbackInfo info; + Event::bus()->m_events.input.touch.down.emit(e, info); + if (info.cancelled) + return; auto PMONITOR = g_pCompositor->getMonitorFromName(!e.device->m_boundOutput.empty() ? e.device->m_boundOutput : ""); - PMONITOR = PMONITOR ? PMONITOR : g_pCompositor->m_lastMonitor.lock(); + PMONITOR = PMONITOR ? PMONITOR : Desktop::focusState()->monitor(); const auto TOUCH_COORDS = PMONITOR->m_position + (e.pos * PMONITOR->m_size); @@ -66,7 +70,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 +113,11 @@ void CInputManager::onTouchDown(ITouch::SDownEvent e) { void CInputManager::onTouchUp(ITouch::SUpEvent e) { m_lastInputTouch = true; - EMIT_HOOK_EVENT_CANCELLABLE("touchUp", e); + Event::SCallbackInfo info; + Event::bus()->m_events.input.touch.up.emit(e, info); + if (info.cancelled) + return; + if (g_pUnifiedWorkspaceSwipe->isGestureInProgress()) { // If there was a swipe from this finger, end it. if (e.touchID == g_pUnifiedWorkspaceSwipe->m_touchID) @@ -126,7 +134,11 @@ void CInputManager::onTouchMove(ITouch::SMotionEvent e) { m_lastCursorMovement.reset(); - EMIT_HOOK_EVENT_CANCELLABLE("touchMove", e); + Event::SCallbackInfo info; + Event::bus()->m_events.input.touch.motion.emit(e, info); + if (info.cancelled) + return; + if (g_pUnifiedWorkspaceSwipe->isGestureInProgress()) { // Do nothing if this is using a different finger. if (e.touchID != g_pUnifiedWorkspaceSwipe->m_touchID) diff --git a/src/managers/input/UnifiedWorkspaceSwipeGesture.cpp b/src/managers/input/UnifiedWorkspaceSwipeGesture.cpp index 01b92b48..c2c8a72a 100644 --- a/src/managers/input/UnifiedWorkspaceSwipeGesture.cpp +++ b/src/managers/input/UnifiedWorkspaceSwipeGesture.cpp @@ -1,6 +1,7 @@ #include "UnifiedWorkspaceSwipeGesture.hpp" #include "../../Compositor.hpp" +#include "../../desktop/state/FocusState.hpp" #include "../../render/Renderer.hpp" #include "InputManager.hpp" @@ -12,18 +13,18 @@ void CUnifiedWorkspaceSwipeGesture::begin() { if (isGestureInProgress()) return; - const auto PWORKSPACE = g_pCompositor->m_lastMonitor->m_activeWorkspace; + 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; - m_monitor = g_pCompositor->m_lastMonitor; + m_monitor = Desktop::focusState()->monitor(); m_avgSpeed = 0; m_speedPoints = 0; if (PWORKSPACE->m_hasFullscreenWindow) { - for (auto const& ls : g_pCompositor->m_lastMonitor->m_layerSurfaceLayers[2]) { + for (auto const& ls : Desktop::focusState()->monitor()->m_layerSurfaceLayers[2]) { *ls->m_alpha = 1.f; } } @@ -245,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); @@ -260,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 { @@ -272,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); @@ -287,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()); @@ -307,7 +305,7 @@ void CUnifiedWorkspaceSwipeGesture::end() { g_pInputManager->refocus(); // apply alpha - for (auto const& ls : g_pCompositor->m_lastMonitor->m_layerSurfaceLayers[2]) { + 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 3595f5ba..e054c2f9 100644 --- a/src/managers/input/trackpad/TrackpadGestures.cpp +++ b/src/managers/input/trackpad/TrackpadGestures.cpp @@ -1,6 +1,8 @@ #include "TrackpadGestures.hpp" #include "../InputManager.hpp" +#include "../../../config/ConfigValue.hpp" +#include "../../../protocols/ShortcutsInhibit.hpp" #include @@ -54,7 +56,7 @@ const char* CTrackpadGestures::stringForDir(eTrackpadGestureDirection dir) { } std::expected CTrackpadGestures::addGesture(UP&& gesture, size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, - float deltaScale) { + float deltaScale, bool disableInhibit) { for (const auto& g : m_gestures) { if (g->fingerCount != fingerCount) continue; @@ -84,14 +86,16 @@ std::expected CTrackpadGestures::addGesture(UP(std::move(gesture), fingerCount, modMask, direction, deltaScale)); + m_gestures.emplace_back(makeShared(std::move(gesture), fingerCount, modMask, direction, deltaScale, disableInhibit)); return {}; } -std::expected CTrackpadGestures::removeGesture(size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale) { - const auto IT = std::ranges::find_if( - m_gestures, [&](const auto& g) { return g->fingerCount == fingerCount && g->direction == direction && g->modMask == modMask && g->deltaScale == deltaScale; }); +std::expected CTrackpadGestures::removeGesture(size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale, + bool disableInhibit) { + const auto IT = std::ranges::find_if(m_gestures, [&](const auto& g) { + return g->fingerCount == fingerCount && g->direction == direction && g->modMask == modMask && g->deltaScale == deltaScale && g->disableInhibit == disableInhibit; + }); if (IT == m_gestures.end()) return std::unexpected("Can't remove a non-existent gesture"); @@ -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 7f96761f..ecf11c40 100644 --- a/src/managers/input/trackpad/TrackpadGestures.hpp +++ b/src/managers/input/trackpad/TrackpadGestures.hpp @@ -11,8 +11,9 @@ class CTrackpadGestures { public: void clearGestures(); - std::expected addGesture(UP&& gesture, size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale); - std::expected removeGesture(size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale); + std::expected addGesture(UP&& gesture, size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale, + bool disableInhibit); + std::expected removeGesture(size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale, bool disableInhibit); void gestureBegin(const IPointer::SSwipeBeginEvent& e); void gestureUpdate(const IPointer::SSwipeUpdateEvent& e); @@ -32,6 +33,7 @@ class CTrackpadGestures { uint32_t modMask = 0; eTrackpadGestureDirection direction = TRACKPAD_GESTURE_DIR_NONE; // configured dir float deltaScale = 1.F; + bool disableInhibit = false; eTrackpadGestureDirection currentDirection = TRACKPAD_GESTURE_DIR_NONE; // actual dir of that select swipe }; diff --git a/src/managers/input/trackpad/gestures/CloseGesture.cpp b/src/managers/input/trackpad/gestures/CloseGesture.cpp index 59beb712..0c37ee36 100644 --- a/src/managers/input/trackpad/gestures/CloseGesture.cpp +++ b/src/managers/input/trackpad/gestures/CloseGesture.cpp @@ -1,12 +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; @@ -27,7 +28,7 @@ static float lerpVal(const float& from, const float& to, const float& t) { void CCloseTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) { ITrackpadGesture::begin(e); - m_window = g_pCompositor->m_lastWindow; + m_window = Desktop::focusState()->window(); if (!m_window) return; @@ -132,8 +133,8 @@ void CCloseTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) if (!window->m_isMapped) return; - g_pLayoutManager->getCurrentLayout()->recalculateWindow(window.lock()); - g_pCompositor->updateWindowAnimatedDecorationValues(window.lock()); + window->layoutTarget()->recalc(); + window->updateDecorationValues(); window->sendWindowSize(true); *window->m_alpha = 1.F; }, diff --git a/src/managers/input/trackpad/gestures/CursorZoomGesture.cpp b/src/managers/input/trackpad/gestures/CursorZoomGesture.cpp new file mode 100644 index 00000000..97dfe158 --- /dev/null +++ b/src/managers/input/trackpad/gestures/CursorZoomGesture.cpp @@ -0,0 +1,33 @@ +#include "CursorZoomGesture.hpp" + +#include "../../../../Compositor.hpp" +#include "../../../../helpers/Monitor.hpp" + +CCursorZoomTrackpadGesture::CCursorZoomTrackpadGesture(const std::string& first, const std::string& second) { + try { + m_zoomValue = std::stof(first); + } catch (...) { ; } + + if (second == "mult") + m_mode = MODE_MULT; +} + +void CCursorZoomTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) { + ITrackpadGesture::begin(e); + + if (m_mode == MODE_TOGGLE) + m_zoomed = !m_zoomed; + + for (auto const& m : g_pCompositor->m_monitors) { + switch (m_mode) { + case MODE_TOGGLE: + static auto PZOOMFACTOR = CConfigValue("cursor:zoom_factor"); + *m->m_cursorZoom = m_zoomed ? m_zoomValue : *PZOOMFACTOR; + break; + case MODE_MULT: *m->m_cursorZoom = std::clamp(m->m_cursorZoom->goal() * m_zoomValue, 1.0F, 100.0F); break; + } + } +} + +void CCursorZoomTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) {} +void CCursorZoomTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) {} diff --git a/src/managers/input/trackpad/gestures/CursorZoomGesture.hpp b/src/managers/input/trackpad/gestures/CursorZoomGesture.hpp new file mode 100644 index 00000000..b53c81e9 --- /dev/null +++ b/src/managers/input/trackpad/gestures/CursorZoomGesture.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "ITrackpadGesture.hpp" + +class CCursorZoomTrackpadGesture : public ITrackpadGesture { + public: + CCursorZoomTrackpadGesture(const std::string& zoomLevel, const std::string& mode); + virtual ~CCursorZoomTrackpadGesture() = default; + + virtual void begin(const ITrackpadGesture::STrackpadGestureBegin& e); + virtual void update(const ITrackpadGesture::STrackpadGestureUpdate& e); + virtual void end(const ITrackpadGesture::STrackpadGestureEnd& e); + + private: + float m_zoomValue = 1.0; + inline static bool m_zoomed = false; + + enum eMode : uint8_t { + MODE_TOGGLE = 0, + MODE_MULT, + }; + + eMode m_mode = MODE_TOGGLE; +}; diff --git a/src/managers/input/trackpad/gestures/FloatGesture.cpp b/src/managers/input/trackpad/gestures/FloatGesture.cpp index bd8d65ea..0849bfac 100644 --- a/src/managers/input/trackpad/gestures/FloatGesture.cpp +++ b/src/managers/input/trackpad/gestures/FloatGesture.cpp @@ -1,8 +1,10 @@ #include "FloatGesture.hpp" -#include "../../../../Compositor.hpp" -#include "../../../../managers/LayoutManager.hpp" #include "../../../../render/Renderer.hpp" +#include "../../../../desktop/state/FocusState.hpp" +#include "../../../../desktop/view/Window.hpp" +#include "../../../../layout/LayoutManager.hpp" +#include "../../../../layout/target/WindowTarget.hpp" constexpr const float MAX_DISTANCE = 250.F; @@ -29,7 +31,7 @@ CFloatTrackpadGesture::CFloatTrackpadGesture(const std::string_view& data) { void CFloatTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) { ITrackpadGesture::begin(e); - m_window = g_pCompositor->m_lastWindow; + m_window = Desktop::focusState()->window(); if (!m_window) return; @@ -39,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(); @@ -78,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 66d86e8b..a219b685 100644 --- a/src/managers/input/trackpad/gestures/FullscreenGesture.cpp +++ b/src/managers/input/trackpad/gestures/FullscreenGesture.cpp @@ -1,6 +1,7 @@ #include "FullscreenGesture.hpp" #include "../../../../Compositor.hpp" +#include "../../../../desktop/state/FocusState.hpp" #include "../../../../render/Renderer.hpp" #include "../../../animation/DesktopAnimationManager.hpp" @@ -29,7 +30,7 @@ CFullscreenTrackpadGesture::CFullscreenTrackpadGesture(const std::string_view& m void CFullscreenTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) { ITrackpadGesture::begin(e); - m_window = g_pCompositor->m_lastWindow; + m_window = Desktop::focusState()->window(); if (!m_window) return; @@ -76,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 6ceb02ec..0dcc310f 100644 --- a/src/managers/input/trackpad/gestures/MoveGesture.cpp +++ b/src/managers/input/trackpad/gestures/MoveGesture.cpp @@ -1,13 +1,14 @@ #include "MoveGesture.hpp" -#include "../../../../Compositor.hpp" -#include "../../../../managers/LayoutManager.hpp" +#include "../../../../desktop/state/FocusState.hpp" +#include "../../../../desktop/view/Window.hpp" #include "../../../../render/Renderer.hpp" +#include "../../../../layout/LayoutManager.hpp" void CMoveTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) { ITrackpadGesture::begin(e); - m_window = g_pCompositor->m_lastWindow; + m_window = Desktop::focusState()->window(); m_lastDelta = {}; } @@ -18,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; @@ -51,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 d788257a..ffc7704b 100644 --- a/src/managers/input/trackpad/gestures/ResizeGesture.cpp +++ b/src/managers/input/trackpad/gestures/ResizeGesture.cpp @@ -1,13 +1,14 @@ #include "ResizeGesture.hpp" -#include "../../../../Compositor.hpp" -#include "../../../../managers/LayoutManager.hpp" +#include "../../../../desktop/state/FocusState.hpp" +#include "../../../../desktop/view/Window.hpp" #include "../../../../render/Renderer.hpp" +#include "../../../../layout/LayoutManager.hpp" void CResizeTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) { ITrackpadGesture::begin(e); - m_window = g_pCompositor->m_lastWindow; + m_window = Desktop::focusState()->window(); } void CResizeTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) { @@ -16,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/input/trackpad/gestures/SpecialWorkspaceGesture.cpp b/src/managers/input/trackpad/gestures/SpecialWorkspaceGesture.cpp index 06a18502..b3643c05 100644 --- a/src/managers/input/trackpad/gestures/SpecialWorkspaceGesture.cpp +++ b/src/managers/input/trackpad/gestures/SpecialWorkspaceGesture.cpp @@ -1,7 +1,7 @@ #include "SpecialWorkspaceGesture.hpp" #include "../../../../Compositor.hpp" -#include "../../../../managers/LayoutManager.hpp" +#include "../../../../desktop/state/FocusState.hpp" #include "../../../../render/Renderer.hpp" #include @@ -36,7 +36,7 @@ void CSpecialWorkspaceGesture::begin(const ITrackpadGesture::STrackpadGestureBeg if (m_specialWorkspace) { m_animatingOut = m_specialWorkspace->isVisible(); - m_monitor = m_animatingOut ? m_specialWorkspace->m_monitor : g_pCompositor->m_lastMonitor; + m_monitor = m_animatingOut ? m_specialWorkspace->m_monitor : Desktop::focusState()->monitor(); if (!m_monitor) return; @@ -44,7 +44,7 @@ void CSpecialWorkspaceGesture::begin(const ITrackpadGesture::STrackpadGestureBeg if (!m_animatingOut) m_monitor->setSpecialWorkspace(m_specialWorkspace); } else { - m_monitor = g_pCompositor->m_lastMonitor; + m_monitor = Desktop::focusState()->monitor(); if (!m_monitor) return; diff --git a/src/managers/input/trackpad/gestures/WorkspaceSwipeGesture.cpp b/src/managers/input/trackpad/gestures/WorkspaceSwipeGesture.cpp index 793bfd77..0ccd2462 100644 --- a/src/managers/input/trackpad/gestures/WorkspaceSwipeGesture.cpp +++ b/src/managers/input/trackpad/gestures/WorkspaceSwipeGesture.cpp @@ -1,7 +1,7 @@ #include "WorkspaceSwipeGesture.hpp" #include "../../../../Compositor.hpp" -#include "../../../../managers/input/InputManager.hpp" +#include "../../../../desktop/state/FocusState.hpp" #include "../../../../render/Renderer.hpp" #include "../../UnifiedWorkspaceSwipeGesture.hpp" @@ -16,7 +16,7 @@ void CWorkspaceSwipeGesture::begin(const ITrackpadGesture::STrackpadGestureBegin int onMonitor = 0; for (auto const& w : g_pCompositor->getWorkspaces()) { - if (w->m_monitor == g_pCompositor->m_lastMonitor && !g_pCompositor->isWorkspaceSpecial(w->m_id)) + if (w->m_monitor == Desktop::focusState()->monitor() && !g_pCompositor->isWorkspaceSpecial(w->m_id)) onMonitor++; } diff --git a/src/managers/permissions/DynamicPermissionManager.cpp b/src/managers/permissions/DynamicPermissionManager.cpp index af1de990..d63a72a0 100644 --- a/src/managers/permissions/DynamicPermissionManager.cpp +++ b/src/managers/permissions/DynamicPermissionManager.cpp @@ -5,6 +5,7 @@ #include "../../Compositor.hpp" #include "../../config/ConfigValue.hpp" #include "../../helpers/MiscFunctions.hpp" +#include "../../i18n/Engine.hpp" #include using namespace Hyprutils::String; @@ -52,17 +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"; - } - - return "error"; -} - -static const char* permissionToHumanString(eDynamicPermissionType type) { - switch (type) { - case PERMISSION_TYPE_UNKNOWN: return "An application {} is requesting an unknown permission."; - case PERMISSION_TYPE_SCREENCOPY: return "An application {} is trying to capture your screen.

Do you want to allow it to do so?"; - case PERMISSION_TYPE_PLUGIN: return "An application {} is trying to load a plugin: {}.

Do you want to load it?"; - case PERMISSION_TYPE_KEYBOARD: return "A new keyboard has been plugged in: {}.

Do you want to allow it to operate?"; + case PERMISSION_TYPE_CURSOR_POS: return "PERMISSION_TYPE_CURSOR_POS"; } return "error"; @@ -92,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) @@ -124,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; } @@ -168,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; @@ -179,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) @@ -196,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; } @@ -244,55 +235,58 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s rule->m_pid = pid; - std::string description = ""; + std::string appName = ""; if (binaryPath.empty()) - description = std::format(std::runtime_format(permissionToHumanString(type)), std::format("unknown application (wayland client ID 0x{:x})", rc(client))); + appName = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, {{"wayland_id", std::format("{:x}", rc(client))}}); else if (client) { - std::string binaryName = binaryPath.contains("/") ? binaryPath.substr(binaryPath.find_last_of('/') + 1) : binaryPath; - description = std::format(std::runtime_format(permissionToHumanString(type)), std::format("{}
({})", binaryName, binaryPath)); + appName = binaryPath.contains("/") ? binaryPath.substr(binaryPath.find_last_of('/') + 1) : binaryPath; } else { - std::string lookup = ""; if (pid < 0) - lookup = specialPidToString(sc(pid)); + appName = specialPidToString(sc(pid)); else { const auto LOOKUP = binaryNameForPid(pid); - lookup = LOOKUP.value_or("Unknown"); - } - - if (type == PERMISSION_TYPE_PLUGIN) { - const auto LOOKUP = binaryNameForPid(pid); - description = std::format(std::runtime_format(permissionToHumanString(type)), lookup, binaryPath); - } else { - const auto LOOKUP = binaryNameForPid(pid); - description = std::format(std::runtime_format(permissionToHumanString(type)), lookup, binaryPath); + appName = LOOKUP.value_or(I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_UNKNOWN_NAME)); } } + std::string description = ""; + switch (rule->m_type) { + case PERMISSION_TYPE_SCREENCOPY: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, {{"app", appName}}); break; + case PERMISSION_TYPE_CURSOR_POS: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_CURSOR_POS, {{"app", appName}}); break; + case PERMISSION_TYPE_PLUGIN: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_PLUGIN, {{"app", appName}, {"plugin", binaryPath}}); break; + case PERMISSION_TYPE_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; + } + std::vector options; + const auto ALLOW = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_ALLOW); + const auto ALLOW_AND_REMEMBER = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER); + const auto ALLOW_ONCE = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_ALLOW_ONCE); + const auto DENY = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_DENY); if (!binaryPath.empty() && client) { - description += "

Hint: you can set persistent rules for these in the Hyprland config file."; - options = {"Deny", "Allow and remember app", "Allow once"}; + description += std::format("

{}", I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_PERSISTENCE_HINT)); + options = {DENY, ALLOW_AND_REMEMBER, ALLOW_ONCE}; } else - options = {"Deny", "Allow"}; + options = {DENY, ALLOW}; - rule->m_dialogBox = CAsyncDialogBox::create("Permission request", description, options); + rule->m_dialogBox = CAsyncDialogBox::create(I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_TITLE), description, options); 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; } rule->m_promise = rule->m_dialogBox->open(); - rule->m_promise->then([r = WP(rule), binaryPath](SP> pr) { + rule->m_promise->then([r = WP(rule), binaryPath, ALLOW, ALLOW_AND_REMEMBER, ALLOW_ONCE, DENY](SP> pr) { if (!r) return; 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(); @@ -301,17 +295,17 @@ 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")) + if (result.starts_with(ALLOW_ONCE)) r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_ALLOW; - else if (result.starts_with("Deny")) { + else if (result.starts_with(DENY)) { r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_DENY; r->m_binaryPath = binaryPath; - } else if (result.starts_with("Allow and remember")) { + } else if (result.starts_with(ALLOW_AND_REMEMBER)) { r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_ALLOW; r->m_binaryPath = binaryPath; - } else if (result.starts_with("Allow")) + } else if (result.starts_with(ALLOW)) r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_ALLOW; if (r->m_promiseResolverForExternal) diff --git a/src/managers/permissions/DynamicPermissionManager.hpp b/src/managers/permissions/DynamicPermissionManager.hpp index 4de1eb32..423596c3 100644 --- a/src/managers/permissions/DynamicPermissionManager.hpp +++ b/src/managers/permissions/DynamicPermissionManager.hpp @@ -18,6 +18,7 @@ enum eDynamicPermissionType : uint8_t { PERMISSION_TYPE_SCREENCOPY, PERMISSION_TYPE_PLUGIN, PERMISSION_TYPE_KEYBOARD, + PERMISSION_TYPE_CURSOR_POS, }; enum eDynamicPermissionRuleSource : uint8_t { @@ -104,4 +105,4 @@ class CDynamicPermissionManager { std::vector> m_rules; }; -inline UP g_pDynamicPermissionManager; \ No newline at end of file +inline UP g_pDynamicPermissionManager; diff --git a/src/managers/screenshare/CursorshareSession.cpp b/src/managers/screenshare/CursorshareSession.cpp new file mode 100644 index 00000000..703832ab --- /dev/null +++ b/src/managers/screenshare/CursorshareSession.cpp @@ -0,0 +1,240 @@ +#include "ScreenshareManager.hpp" +#include "../PointerManager.hpp" +#include "../../protocols/core/Seat.hpp" +#include "../permissions/DynamicPermissionManager.hpp" +#include "../../render/Renderer.hpp" + +using namespace Screenshare; + +CCursorshareSession::CCursorshareSession(wl_client* client, WP pointer) : m_client(client), m_pointer(pointer) { + m_listeners.pointerDestroyed = m_pointer->m_events.destroyed.listen([this] { stop(); }); + m_listeners.cursorChanged = g_pPointerManager->m_events.cursorChanged.listen([this] { + calculateConstraints(); + m_events.constraintsChanged.emit(); + + if (m_pendingFrame.pending) { + if (copy()) + return; + + LOGM(Log::ERR, "Failed to copy cursor image for cursor share"); + if (m_pendingFrame.callback) + m_pendingFrame.callback(RESULT_NOT_COPIED); + m_pendingFrame.pending = false; + return; + } + }); + + calculateConstraints(); +} + +CCursorshareSession::~CCursorshareSession() { + stop(); +} + +void CCursorshareSession::stop() { + if (m_stopped) + return; + m_stopped = true; + m_events.stopped.emit(); +} + +void CCursorshareSession::calculateConstraints() { + const auto& cursorImage = g_pPointerManager->currentCursorImage(); + m_constraintsChanged = true; + + // cursor is hidden, keep the previous constraints and render 0 alpha + if (!cursorImage.pBuffer) + return; + + // TODO: should cursor share have a format bit flip for RGBA? + if (auto attrs = cursorImage.pBuffer->shm(); attrs.success) { + m_format = attrs.format; + } else { + // we only have shm cursors + return; + } + + m_hotspot = cursorImage.hotspot; + m_bufferSize = cursorImage.size; +} + +// TODO: allow render to buffer without monitor and remove monitor param +eScreenshareError CCursorshareSession::share(PHLMONITOR monitor, SP buffer, FSourceBoxCallback sourceBoxCallback, FScreenshareCallback callback) { + if (m_stopped || m_pointer.expired() || m_bufferSize == Vector2D(0, 0)) + return ERROR_STOPPED; + + if UNLIKELY (!buffer || !buffer->m_resource || !buffer->m_resource->good()) { + LOGM(Log::ERR, "Client requested sharing to an invalid buffer"); + return ERROR_NO_BUFFER; + } + + if UNLIKELY (buffer->size != m_bufferSize) { + LOGM(Log::ERR, "Client requested sharing to an invalid buffer size"); + return ERROR_BUFFER_SIZE; + } + + uint32_t bufFormat; + if (buffer->dmabuf().success) + bufFormat = buffer->dmabuf().format; + else if (buffer->shm().success) + bufFormat = buffer->shm().format; + else { + LOGM(Log::ERR, "Client requested sharing to an invalid buffer"); + return ERROR_NO_BUFFER; + } + + if (bufFormat != m_format) { + LOGM(Log::ERR, "Invalid format {} in {:x}", bufFormat, (uintptr_t)this); + return ERROR_BUFFER_FORMAT; + } + + m_pendingFrame.pending = true; + m_pendingFrame.monitor = monitor; + m_pendingFrame.buffer = buffer; + m_pendingFrame.sourceBoxCallback = sourceBoxCallback; + m_pendingFrame.callback = callback; + + // nothing changed, then delay copy until contraints changed + if (!m_constraintsChanged) + return ERROR_NONE; + + if (!copy()) { + LOGM(Log::ERR, "Failed to copy cursor image for cursor share"); + callback(RESULT_NOT_COPIED); + m_pendingFrame.pending = false; + return ERROR_UNKNOWN; + } + + return ERROR_NONE; +} + +void CCursorshareSession::render() { + const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_client, PERMISSION_TYPE_CURSOR_POS); + + const auto& cursorImage = g_pPointerManager->currentCursorImage(); + + // TODO: implement a monitor independent render mode to buffer that does this in CHyprRenderer::begin() or something like that + g_pHyprOpenGL->m_renderData.transformDamage = false; + g_pHyprOpenGL->setViewport(0, 0, m_bufferSize.x, m_bufferSize.y); + + bool overlaps = g_pPointerManager->getCursorBoxGlobal().overlaps(m_pendingFrame.sourceBoxCallback()); + if (PERM != PERMISSION_RULE_ALLOW_MODE_ALLOW || !overlaps) { + // render black when not allowed + g_pHyprOpenGL->clear(Colors::BLACK); + } else if (!cursorImage.pBuffer || !cursorImage.surface || !cursorImage.bufferTex) { + // render clear when cursor is probably hidden + g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 0)); + } else { + // render cursor + CBox texbox = {{}, cursorImage.bufferTex->m_size}; + g_pHyprOpenGL->renderTexture(cursorImage.bufferTex, texbox, {}); + } + + g_pHyprOpenGL->m_renderData.blockScreenShader = true; +} + +bool CCursorshareSession::copy() { + if (!m_pendingFrame.callback || !m_pendingFrame.monitor || !m_pendingFrame.callback || !m_pendingFrame.sourceBoxCallback) + return false; + + // FIXME: this doesn't really make sense but just to be safe + m_pendingFrame.callback(RESULT_TIMESTAMP); + + g_pHyprRenderer->makeEGLCurrent(); + + CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX}; + if (auto attrs = m_pendingFrame.buffer->dmabuf(); attrs.success) { + if (attrs.format != m_format) { + LOGM(Log::ERR, "Can't copy: invalid format"); + return false; + } + + if (!g_pHyprRenderer->beginRender(m_pendingFrame.monitor, fakeDamage, RENDER_MODE_TO_BUFFER, m_pendingFrame.buffer, nullptr, true)) { + LOGM(Log::ERR, "Can't copy: failed to begin rendering to dmabuf"); + return false; + } + + render(); + + g_pHyprRenderer->endRender([callback = m_pendingFrame.callback]() { + if (callback) + callback(RESULT_COPIED); + }); + } else if (auto attrs = m_pendingFrame.buffer->shm(); attrs.success) { + auto [bufData, fmt, bufLen] = m_pendingFrame.buffer->beginDataPtr(0); + const auto PFORMAT = NFormatUtils::getPixelFormatFromDRM(m_format); + + if (attrs.format != m_format || !PFORMAT) { + LOGM(Log::ERR, "Can't copy: invalid format"); + return false; + } + + auto outFB = g_pHyprRenderer->createFB(); + outFB->alloc(m_bufferSize.x, m_bufferSize.y, m_format); + + if (!g_pHyprRenderer->beginRender(m_pendingFrame.monitor, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, outFB, true)) { + LOGM(Log::ERR, "Can't copy: failed to begin rendering to shm"); + return false; + } + + render(); + + g_pHyprRenderer->endRender(); + + g_pHyprOpenGL->m_renderData.pMonitor = m_pendingFrame.monitor; + outFB->bind(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, GLFB(outFB)->getFBID()); + + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + int glFormat = PFORMAT->glFormat; + + if (glFormat == GL_RGBA) + glFormat = GL_BGRA_EXT; + + if (glFormat != GL_BGRA_EXT && glFormat != GL_RGB) { + if (PFORMAT->swizzle.has_value()) { + std::array RGBA = SWIZZLE_RGBA; + std::array BGRA = SWIZZLE_BGRA; + if (PFORMAT->swizzle == RGBA) + glFormat = GL_RGBA; + else if (PFORMAT->swizzle == BGRA) + glFormat = GL_BGRA_EXT; + else { + LOGM(Log::ERR, "Copied frame via shm might be broken or color flipped"); + glFormat = GL_RGBA; + } + } + } + + glReadPixels(0, 0, m_bufferSize.x, m_bufferSize.y, glFormat, PFORMAT->glType, bufData); + + g_pHyprOpenGL->m_renderData.pMonitor.reset(); + + m_pendingFrame.buffer->endDataPtr(); + GLFB(outFB)->unbind(); + glPixelStorei(GL_PACK_ALIGNMENT, 4); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + + m_pendingFrame.callback(RESULT_COPIED); + } else { + LOGM(Log::ERR, "Can't copy: invalid buffer type"); + return false; + } + + m_pendingFrame.pending = false; + m_constraintsChanged = false; + return true; +} + +DRMFormat CCursorshareSession::format() const { + return m_format; +} + +Vector2D CCursorshareSession::bufferSize() const { + return m_bufferSize; +} + +Vector2D CCursorshareSession::hotspot() const { + return m_hotspot; +} diff --git a/src/managers/screenshare/ScreenshareFrame.cpp b/src/managers/screenshare/ScreenshareFrame.cpp new file mode 100644 index 00000000..d747ecee --- /dev/null +++ b/src/managers/screenshare/ScreenshareFrame.cpp @@ -0,0 +1,497 @@ +#include "ScreenshareManager.hpp" +#include "../PointerManager.hpp" +#include "../input/InputManager.hpp" +#include "../permissions/DynamicPermissionManager.hpp" +#include "../../protocols/ColorManagement.hpp" +#include "../../protocols/XDGShell.hpp" +#include "../../Compositor.hpp" +#include "../../render/Renderer.hpp" +#include "../../render/OpenGL.hpp" +#include "../../helpers/Monitor.hpp" +#include "../../desktop/view/Window.hpp" +#include "../../desktop/state/FocusState.hpp" +#include + +using namespace Screenshare; + +CScreenshareFrame::CScreenshareFrame(WP session, bool overlayCursor, bool isFirst) : + m_session(session), m_bufferSize(m_session->bufferSize()), m_overlayCursor(overlayCursor), m_isFirst(isFirst) { + ; +} + +CScreenshareFrame::~CScreenshareFrame() { + if (m_failed || !m_shared) + return; + + if (!m_copied && m_callback) + m_callback(RESULT_NOT_COPIED); +} + +bool CScreenshareFrame::done() const { + if (m_session.expired() || m_session->m_stopped) + return true; + + if (m_session->m_type == SHARE_NONE || m_bufferSize == Vector2D(0, 0)) + return true; + + if (m_failed || m_copied) + return true; + + if (m_session->m_type == SHARE_MONITOR && !m_session->monitor()) + return true; + + if (m_session->m_type == SHARE_REGION && !m_session->monitor()) + return true; + + if (m_session->m_type == SHARE_WINDOW && (!m_session->monitor() || !validMapped(m_session->m_window))) + return true; + + if (!m_shared) + return false; + + if (!m_buffer || !m_buffer->m_resource || !m_buffer->m_resource->good()) + return true; + + if (!m_callback) + return true; + + return false; +} + +eScreenshareError CScreenshareFrame::share(SP buffer, const CRegion& clientDamage, FScreenshareCallback callback) { + if UNLIKELY (done()) + return ERROR_STOPPED; + + if UNLIKELY (!m_session->monitor() || !g_pCompositor->monitorExists(m_session->monitor())) { + LOGM(Log::ERR, "Client requested sharing of a monitor that is gone"); + m_failed = true; + return ERROR_STOPPED; + } + + if UNLIKELY (m_session->m_type == SHARE_WINDOW && !validMapped(m_session->m_window)) { + LOGM(Log::ERR, "Client requested sharing of window that is gone or not shareable!"); + m_failed = true; + return ERROR_STOPPED; + } + + if UNLIKELY (!buffer || !buffer->m_resource || !buffer->m_resource->good()) { + LOGM(Log::ERR, "Client requested sharing to an invalid buffer"); + return ERROR_NO_BUFFER; + } + + if UNLIKELY (buffer->size != m_bufferSize) { + LOGM(Log::ERR, "Client requested sharing to an invalid buffer size"); + return ERROR_BUFFER_SIZE; + } + + uint32_t bufFormat; + if (buffer->dmabuf().success) + bufFormat = buffer->dmabuf().format; + else if (buffer->shm().success) + bufFormat = buffer->shm().format; + else { + LOGM(Log::ERR, "Client requested sharing to an invalid buffer"); + return ERROR_NO_BUFFER; + } + + if (std::ranges::count_if(m_session->allowedFormats(), [&](const DRMFormat& format) { return format == bufFormat; }) == 0) { + LOGM(Log::ERR, "Invalid format {} in {:x}", bufFormat, (uintptr_t)this); + return ERROR_BUFFER_FORMAT; + } + + m_buffer = buffer; + m_callback = callback; + m_shared = true; + + // schedule a frame so that when a screenshare starts it isn't black until the output is updated + if (m_isFirst) { + g_pCompositor->scheduleFrameForMonitor(m_session->monitor(), Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); + g_pHyprRenderer->damageMonitor(m_session->monitor()); + } + + // TODO: add a damage ring for output damage since last shared frame + CRegion frameDamage = CRegion(0, 0, m_bufferSize.x, m_bufferSize.y); + + // copy everything on the first frame + if (m_isFirst) + m_damage = CRegion(0, 0, m_bufferSize.x, m_bufferSize.y); + else + m_damage = frameDamage.add(clientDamage); + + m_damage.intersect(0, 0, m_bufferSize.x, m_bufferSize.y); + + return ERROR_NONE; +} + +void CScreenshareFrame::copy() { + if (done()) + return; + + // tell client to send presented timestamp + // TODO: is this right? this is right after we commit to aq, not when page flip happens.. + m_callback(RESULT_TIMESTAMP); + + // store a snapshot before the permission popup so we don't break screenshots + const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_session->m_client, PERMISSION_TYPE_SCREENCOPY); + if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) { + if (!m_session->m_tempFB || !m_session->m_tempFB->isAllocated()) + storeTempFB(); + + // don't copy a frame while allow is pending because screenshot tools will only take the first frame we give, which is empty + return; + } + + if (m_buffer->shm().success) + m_failed = !copyShm(); + else if (m_buffer->dmabuf().success) + m_failed = !copyDmabuf(); + + if (!m_failed) { + // screensharing has started again + m_session->screenshareEvents(true); + m_session->m_shareStopTimer->updateTimeout(std::chrono::milliseconds(500)); // check in half second + } else + m_callback(RESULT_NOT_COPIED); +} + +void CScreenshareFrame::renderMonitor() { + if ((m_session->m_type != SHARE_MONITOR && m_session->m_type != SHARE_REGION) || done()) + return; + + const auto PMONITOR = m_session->monitor(); + + auto TEXTURE = g_pHyprRenderer->createTexture(PMONITOR->m_output->state->state().buffer); + + const bool IS_CM_AWARE = PROTO::colorManagement && PROTO::colorManagement->isClientCMAware(m_session->m_client); + g_pHyprOpenGL->m_renderData.transformDamage = false; + g_pHyprOpenGL->m_renderData.noSimplify = true; + + // render monitor texture + CBox monbox = CBox{{}, PMONITOR->m_pixelSize} + .transform(Math::wlTransformToHyprutils(Math::invertTransform(PMONITOR->m_transform)), PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y) + .translate(-m_session->m_captureBox.pos()); // vvvv kinda ass-backwards but that's how I designed the renderer... sigh. + g_pHyprOpenGL->pushMonitorTransformEnabled(true); + g_pHyprOpenGL->setRenderModifEnabled(false); + g_pHyprOpenGL->renderTexture(TEXTURE, monbox, + { + .cmBackToSRGB = !IS_CM_AWARE, + .cmBackToSRGBSource = !IS_CM_AWARE ? PMONITOR : nullptr, + }); + g_pHyprOpenGL->setRenderModifEnabled(true); + g_pHyprOpenGL->popMonitorTransformEnabled(); + + // render black boxes for noscreenshare + auto hidePopups = [&](Vector2D popupBaseOffset) { + return [&, popupBaseOffset](WP popup, void*) { + if (!popup->wlSurface() || !popup->wlSurface()->resource() || !popup->visible()) + return; + + const auto popRel = popup->coordsRelativeToParent(); + popup->wlSurface()->resource()->breadthfirst( + [&](SP surf, const Vector2D& localOff, void*) { + const auto size = surf->m_current.size; + const auto surfBox = + CBox{popupBaseOffset + popRel + localOff, size}.translate(PMONITOR->m_position).scale(PMONITOR->m_scale).translate(-m_session->m_captureBox.pos()); + + if LIKELY (surfBox.w > 0 && surfBox.h > 0) + g_pHyprOpenGL->renderRect(surfBox, Colors::BLACK, {}); + }, + nullptr); + }; + }; + + for (auto const& l : g_pCompositor->m_layers) { + if (!l->m_ruleApplicator->noScreenShare().valueOrDefault()) + continue; + + if UNLIKELY (!l->visible()) + continue; + + const auto REALPOS = l->m_realPosition->value(); + const auto REALSIZE = l->m_realSize->value(); + + const auto noScreenShareBox = CBox{REALPOS.x, REALPOS.y, std::max(REALSIZE.x, 5.0), std::max(REALSIZE.y, 5.0)} + .translate(-PMONITOR->m_position) + .scale(PMONITOR->m_scale) + .translate(-m_session->m_captureBox.pos()); + + g_pHyprOpenGL->renderRect(noScreenShareBox, Colors::BLACK, {}); + + const auto geom = l->m_geometry; + const Vector2D popupBaseOffset = REALPOS - Vector2D{geom.pos().x, geom.pos().y}; + if (l->m_popupHead) + l->m_popupHead->breadthfirst(hidePopups(popupBaseOffset), nullptr); + } + + for (auto const& w : g_pCompositor->m_windows) { + if (!w->m_ruleApplicator->noScreenShare().valueOrDefault()) + continue; + + if (!g_pHyprRenderer->shouldRenderWindow(w, PMONITOR)) + continue; + + if (w->isHidden()) + continue; + + const auto PWORKSPACE = w->m_workspace; + + if UNLIKELY (!PWORKSPACE && !w->m_fadingOut && w->m_alpha->value() != 0.f) + continue; + + const auto renderOffset = PWORKSPACE && !w->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D{}; + const auto REALPOS = w->m_realPosition->value() + renderOffset; + const auto noScreenShareBox = CBox{REALPOS.x, REALPOS.y, std::max(w->m_realSize->value().x, 5.0), std::max(w->m_realSize->value().y, 5.0)} + .translate(-PMONITOR->m_position) + .scale(PMONITOR->m_scale) + .translate(-m_session->m_captureBox.pos()); + + // seems like rounding doesn't play well with how we manipulate the box position to render regions causing the window to leak through + const auto dontRound = m_session->m_captureBox.pos() != Vector2D() || w->isEffectiveInternalFSMode(FSMODE_FULLSCREEN); + const auto rounding = dontRound ? 0 : w->rounding() * PMONITOR->m_scale; + const auto roundingPower = dontRound ? 2.0f : w->roundingPower(); + + g_pHyprOpenGL->renderRect(noScreenShareBox, Colors::BLACK, {.round = rounding, .roundingPower = roundingPower}); + + if (w->m_isX11 || !w->m_popupHead) + continue; + + const auto geom = w->m_xdgSurface->m_current.geometry; + const Vector2D popupBaseOffset = REALPOS - Vector2D{geom.pos().x, geom.pos().y}; + + w->m_popupHead->breadthfirst(hidePopups(popupBaseOffset), nullptr); + } + + if (m_overlayCursor) { + CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX}; + Vector2D cursorPos = g_pInputManager->getMouseCoordsInternal() - PMONITOR->m_position - m_session->m_captureBox.pos() / PMONITOR->m_scale; + g_pPointerManager->renderSoftwareCursorsFor(PMONITOR, Time::steadyNow(), fakeDamage, cursorPos, true); + } +} + +void CScreenshareFrame::renderWindow() { + if (m_session->m_type != SHARE_WINDOW || done()) + return; + + const auto PWINDOW = m_session->m_window.lock(); + const auto PMONITOR = m_session->monitor(); + + const auto NOW = Time::steadyNow(); + + // TODO: implement a monitor independent render mode to buffer that does this in CHyprRenderer::begin() or something like that + g_pHyprOpenGL->m_renderData.monitorProjection = Mat3x3::identity(); + g_pHyprOpenGL->m_renderData.projection = Mat3x3::outputProjection(m_bufferSize, HYPRUTILS_TRANSFORM_NORMAL); + g_pHyprOpenGL->m_renderData.transformDamage = false; + g_pHyprOpenGL->setViewport(0, 0, m_bufferSize.x, m_bufferSize.y); + + g_pHyprRenderer->m_bBlockSurfaceFeedback = g_pHyprRenderer->shouldRenderWindow(PWINDOW); // block the feedback to avoid spamming the surface if it's visible + g_pHyprRenderer->renderWindow(PWINDOW, PMONITOR, NOW, false, RENDER_PASS_ALL, true, true); + g_pHyprRenderer->m_bBlockSurfaceFeedback = false; + + if (!m_overlayCursor) + return; + + auto pointerSurfaceResource = g_pSeatManager->m_state.pointerFocus.lock(); + + if (!pointerSurfaceResource) + return; + + auto pointerSurface = Desktop::View::CWLSurface::fromResource(pointerSurfaceResource); + if (!pointerSurface) + return; + + auto box = pointerSurface->getSurfaceBoxGlobal(); + if (!box.has_value() || box->intersection(m_session->m_window->getFullWindowBoundingBox()).empty()) + return; + + if (Desktop::focusState()->window() != m_session->m_window) + return; + + CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX}; + g_pPointerManager->renderSoftwareCursorsFor(PMONITOR->m_self.lock(), NOW, fakeDamage, g_pInputManager->getMouseCoordsInternal() - PWINDOW->m_realPosition->value(), true); +} + +void CScreenshareFrame::render() { + const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_session->m_client, PERMISSION_TYPE_SCREENCOPY); + + if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) { + g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 0)); + return; + } + + bool windowShareDenied = m_session->m_type == SHARE_WINDOW && m_session->m_window->m_ruleApplicator && m_session->m_window->m_ruleApplicator->noScreenShare().valueOrDefault(); + if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY || windowShareDenied) { + g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 0)); + CBox texbox = CBox{m_bufferSize / 2.F, g_pHyprOpenGL->m_screencopyDeniedTexture->m_size}.translate(-g_pHyprOpenGL->m_screencopyDeniedTexture->m_size / 2.F); + g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_screencopyDeniedTexture, texbox, {}); + return; + } + + if (m_session->m_tempFB && m_session->m_tempFB->isAllocated()) { + CBox texbox = {{}, m_bufferSize}; + g_pHyprOpenGL->renderTexture(m_session->m_tempFB->getTexture(), texbox, {}); + m_session->m_tempFB->release(); + return; + } + + switch (m_session->m_type) { + case SHARE_REGION: // TODO: could this be better? this is how screencopy works + case SHARE_MONITOR: renderMonitor(); break; + case SHARE_WINDOW: renderWindow(); break; + case SHARE_NONE: + default: return; + } +} + +bool CScreenshareFrame::copyDmabuf() { + if (done()) + return false; + + if (!g_pHyprRenderer->beginRender(m_session->monitor(), m_damage, RENDER_MODE_TO_BUFFER, m_buffer, nullptr, true)) { + LOGM(Log::ERR, "Can't copy: failed to begin rendering to dma frame"); + return false; + } + + render(); + + g_pHyprOpenGL->m_renderData.blockScreenShader = true; + + g_pHyprRenderer->endRender([self = m_self]() { + if (!self || self.expired() || self->m_copied) + return; + + LOGM(Log::TRACE, "Copied frame via dma"); + self->m_callback(RESULT_COPIED); + self->m_copied = true; + }); + + return true; +} + +bool CScreenshareFrame::copyShm() { + if (done()) + return false; + + g_pHyprRenderer->makeEGLCurrent(); + + auto shm = m_buffer->shm(); + auto [pixelData, fmt, bufLen] = m_buffer->beginDataPtr(0); // no need for end, cuz it's shm + + const auto PFORMAT = NFormatUtils::getPixelFormatFromDRM(shm.format); + if (!PFORMAT) { + LOGM(Log::ERR, "Can't copy: failed to find a pixel format"); + return false; + } + + const auto PMONITOR = m_session->monitor(); + + auto outFB = g_pHyprRenderer->createFB(); + outFB->alloc(m_bufferSize.x, m_bufferSize.y, shm.format); + + if (!g_pHyprRenderer->beginRender(PMONITOR, m_damage, RENDER_MODE_FULL_FAKE, nullptr, outFB, true)) { + LOGM(Log::ERR, "Can't copy: failed to begin rendering"); + return false; + } + + render(); + + g_pHyprOpenGL->m_renderData.blockScreenShader = true; + + g_pHyprRenderer->endRender(); + + g_pHyprOpenGL->m_renderData.pMonitor = PMONITOR; + outFB->bind(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, GLFB(outFB)->getFBID()); + + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + uint32_t packStride = NFormatUtils::minStride(PFORMAT, m_bufferSize.x); + int glFormat = PFORMAT->glFormat; + + if (glFormat == GL_RGBA) + glFormat = GL_BGRA_EXT; + + if (glFormat != GL_BGRA_EXT && glFormat != GL_RGB) { + if (PFORMAT->swizzle.has_value()) { + std::array RGBA = SWIZZLE_RGBA; + std::array BGRA = SWIZZLE_BGRA; + if (PFORMAT->swizzle == RGBA) + glFormat = GL_RGBA; + else if (PFORMAT->swizzle == BGRA) + glFormat = GL_BGRA_EXT; + else { + LOGM(Log::ERR, "Copied frame via shm might be broken or color flipped"); + glFormat = GL_RGBA; + } + } + } + + // TODO: use pixel buffer object to not block cpu + if (packStride == sc(shm.stride)) { + m_damage.forEachRect([&](const auto& rect) { + int width = rect.x2 - rect.x1; + int height = rect.y2 - rect.y1; + glReadPixels(rect.x1, rect.y1, width, height, glFormat, PFORMAT->glType, pixelData); + }); + } else { + m_damage.forEachRect([&](const auto& rect) { + size_t width = rect.x2 - rect.x1; + size_t height = rect.y2 - rect.y1; + for (size_t i = rect.y1; i < height; ++i) { + glReadPixels(rect.x1, i, width, 1, glFormat, PFORMAT->glType, pixelData + (rect.x1 * PFORMAT->bytesPerBlock) + (i * shm.stride)); + } + }); + } + + GLFB(outFB)->unbind(); + glPixelStorei(GL_PACK_ALIGNMENT, 4); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + + g_pHyprOpenGL->m_renderData.pMonitor.reset(); + + if (!m_copied) { + LOGM(Log::TRACE, "Copied frame via shm"); + m_callback(RESULT_COPIED); + } + + return true; +} + +void CScreenshareFrame::storeTempFB() { + if (!m_session->m_tempFB) + m_session->m_tempFB = g_pHyprRenderer->createFB(); + m_session->m_tempFB->alloc(m_bufferSize.x, m_bufferSize.y); + + CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX}; + + if (!g_pHyprRenderer->beginRender(m_session->monitor(), fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, m_session->m_tempFB, true)) { + LOGM(Log::ERR, "Can't copy: failed to begin rendering to temp fb"); + return; + } + + switch (m_session->m_type) { + case SHARE_REGION: // TODO: could this be better? this is how screencopy works + case SHARE_MONITOR: renderMonitor(); break; + case SHARE_WINDOW: renderWindow(); break; + case SHARE_NONE: + default: return; + } + + g_pHyprRenderer->endRender(); +} + +Vector2D CScreenshareFrame::bufferSize() const { + return m_bufferSize; +} + +wl_output_transform CScreenshareFrame::transform() const { + switch (m_session->m_type) { + case SHARE_REGION: + case SHARE_MONITOR: return m_session->monitor()->m_transform; + default: + case SHARE_WINDOW: return WL_OUTPUT_TRANSFORM_NORMAL; + } +} + +const CRegion& CScreenshareFrame::damage() const { + return m_damage; +} diff --git a/src/managers/screenshare/ScreenshareManager.cpp b/src/managers/screenshare/ScreenshareManager.cpp new file mode 100644 index 00000000..63f2bbbc --- /dev/null +++ b/src/managers/screenshare/ScreenshareManager.cpp @@ -0,0 +1,165 @@ +#include "ScreenshareManager.hpp" +#include "../../render/Renderer.hpp" +#include "../../Compositor.hpp" +#include "../../desktop/view/Window.hpp" +#include "../../protocols/core/Seat.hpp" + +using namespace Screenshare; + +CScreenshareManager::CScreenshareManager() { + ; +} + +void CScreenshareManager::onOutputCommit(PHLMONITOR monitor) { + std::erase_if(m_sessions, [&](const WP& session) { return session.expired(); }); + + // if no pending frames, and no sessions are sharing, then unblock ds + if (m_pendingFrames.empty()) { + for (const auto& session : m_sessions) { + if (!session->m_stopped && session->m_sharing) + return; + } + + g_pHyprRenderer->m_directScanoutBlocked = false; + return; // nothing to share + } + + std::ranges::for_each(m_pendingFrames, [&](WP& frame) { + if (frame.expired() || !frame->m_shared || frame->done()) + return; + + if (frame->m_session->monitor() != monitor) + return; + + if (frame->m_session->m_type == SHARE_WINDOW) { + CBox geometry = {frame->m_session->m_window->m_realPosition->value(), frame->m_session->m_window->m_realSize->value()}; + if (geometry.intersection({monitor->m_position, monitor->m_size}).empty()) + return; + } + + frame->copy(); + }); + + std::erase_if(m_pendingFrames, [&](const WP& frame) { return frame.expired(); }); +} + +UP CScreenshareManager::newSession(wl_client* client, PHLMONITOR monitor) { + if UNLIKELY (!monitor || !g_pCompositor->monitorExists(monitor)) { + LOGM(Log::ERR, "Client requested sharing of a monitor that is gone"); + return nullptr; + } + + UP session = UP(new CScreenshareSession(monitor, client)); + + session->m_self = session; + m_sessions.emplace_back(session); + + return session; +} + +UP CScreenshareManager::newSession(wl_client* client, PHLMONITOR monitor, CBox captureRegion) { + if UNLIKELY (!monitor || !g_pCompositor->monitorExists(monitor)) { + LOGM(Log::ERR, "Client requested sharing of a monitor that is gone"); + return nullptr; + } + + UP session = UP(new CScreenshareSession(monitor, captureRegion, client)); + + session->m_self = session; + m_sessions.emplace_back(session); + + return session; +} + +UP CScreenshareManager::newSession(wl_client* client, PHLWINDOW window) { + if UNLIKELY (!window || !window->m_isMapped) { + LOGM(Log::ERR, "Client requested sharing of window that is gone or not shareable!"); + return nullptr; + } + + UP session = UP(new CScreenshareSession(window, client)); + + session->m_self = session; + m_sessions.emplace_back(session); + + return session; +} + +UP CScreenshareManager::newCursorSession(wl_client* client, WP pointer) { + UP session = UP(new CCursorshareSession(client, pointer)); + + session->m_self = session; + m_cursorSessions.emplace_back(session); + + return session; +} + +WP CScreenshareManager::getManagedSession(wl_client* client, PHLMONITOR monitor) { + return getManagedSession(SHARE_MONITOR, client, monitor, nullptr, {}); +} + +WP CScreenshareManager::getManagedSession(wl_client* client, PHLMONITOR monitor, CBox captureBox) { + + return getManagedSession(SHARE_REGION, client, monitor, nullptr, captureBox); +} + +WP CScreenshareManager::getManagedSession(wl_client* client, PHLWINDOW window) { + return getManagedSession(SHARE_WINDOW, client, nullptr, window, {}); +} + +WP CScreenshareManager::getManagedSession(eScreenshareType type, wl_client* client, PHLMONITOR monitor, PHLWINDOW window, CBox captureBox) { + if (type == SHARE_NONE) + return {}; + + auto it = std::ranges::find_if(m_managedSessions, [&](const auto& session) { + if (session->m_session->m_client != client || session->m_session->m_type != type) + return false; + + switch (type) { + case SHARE_MONITOR: return session->m_session->m_monitor == monitor; + case SHARE_WINDOW: return session->m_session->m_window == window; + case SHARE_REGION: return session->m_session->m_monitor == monitor && session->m_session->m_captureBox == captureBox; + case SHARE_NONE: + default: return false; + } + + return false; + }); + + if (it == m_managedSessions.end()) { + UP session; + switch (type) { + case SHARE_MONITOR: session = UP(new CScreenshareSession(monitor, client)); break; + case SHARE_WINDOW: session = UP(new CScreenshareSession(window, client)); break; + case SHARE_REGION: session = UP(new CScreenshareSession(monitor, captureBox, client)); break; + case SHARE_NONE: + default: return {}; + } + + session->m_self = session; + m_sessions.emplace_back(session); + + it = m_managedSessions.emplace(m_managedSessions.end(), makeUnique(std::move(session))); + } + + auto& session = *it; + + session->stoppedListener = session->m_session->m_events.stopped.listen([session = WP(session)]() { + if (!session.expired()) + std::erase_if(Screenshare::mgr()->m_managedSessions, [&](const auto& s) { return s && s->m_session.get() == session->m_session.get(); }); + }); + + return session->m_session; +} + +bool CScreenshareManager::isOutputBeingSSd(PHLMONITOR monitor) { + return std::ranges::any_of(m_sessions, [monitor](const auto& s) { + if (!s) + return false; + return (s->m_type == SHARE_MONITOR || s->m_type == SHARE_REGION) && s->m_monitor == monitor; + }); +} + +CScreenshareManager::SManagedSession::SManagedSession(UP&& session) : m_session(std::move(session)) { + ; +} diff --git a/src/managers/screenshare/ScreenshareManager.hpp b/src/managers/screenshare/ScreenshareManager.hpp new file mode 100644 index 00000000..5a4ada5e --- /dev/null +++ b/src/managers/screenshare/ScreenshareManager.hpp @@ -0,0 +1,251 @@ +#pragma once + +#include +#include "../../helpers/memory/Memory.hpp" +#include "../../protocols/types/Buffer.hpp" +#include "../../render/Framebuffer.hpp" +#include "../eventLoop/EventLoopTimer.hpp" +#include "../../render/Renderer.hpp" + +// TODO: do screenshare damage + +class CWLPointerResource; + +namespace Screenshare { + enum eScreenshareType : uint8_t { + SHARE_MONITOR, + SHARE_WINDOW, + SHARE_REGION, + SHARE_NONE + }; + + enum eScreenshareError : uint8_t { + ERROR_NONE, + ERROR_UNKNOWN, + ERROR_STOPPED, + ERROR_NO_BUFFER, + ERROR_BUFFER_SIZE, + ERROR_BUFFER_FORMAT + }; + + enum eScreenshareResult : uint8_t { + RESULT_COPIED, + RESULT_NOT_COPIED, + RESULT_TIMESTAMP, + }; + + using FScreenshareCallback = std::function; + using FSourceBoxCallback = std::function; + + class CScreenshareSession { + public: + CScreenshareSession(const CScreenshareSession&) = delete; + CScreenshareSession(CScreenshareSession&&) = delete; + ~CScreenshareSession(); + + UP nextFrame(bool overlayCursor); + void stop(); + + // constraints + const std::vector& allowedFormats() const; + Vector2D bufferSize() const; + PHLMONITOR monitor() const; // this will return the correct monitor based on type + + struct { + CSignalT<> stopped; + CSignalT<> constraintsChanged; + } m_events; + + private: + CScreenshareSession(PHLMONITOR monitor, wl_client* client); + CScreenshareSession(PHLMONITOR monitor, CBox captureRegion, wl_client* client); + CScreenshareSession(PHLWINDOW window, wl_client* client); + + WP m_self; + bool m_stopped = false; + + eScreenshareType m_type = SHARE_NONE; + PHLMONITORREF m_monitor; + PHLWINDOWREF m_window; + CBox m_captureBox = {}; // given capture area in logical coordinates (see xdg_output) + + wl_client* m_client = nullptr; + std::string m_name = ""; + + std::vector m_formats; + Vector2D m_bufferSize = Vector2D(0, 0); + + SP m_tempFB; + + SP m_shareStopTimer; + bool m_sharing = false; + + struct { + CHyprSignalListener monitorDestroyed; + CHyprSignalListener monitorModeChanged; + CHyprSignalListener windowDestroyed; + CHyprSignalListener windowSizeChanged; + CHyprSignalListener windowMonitorChanged; + } m_listeners; + + void screenshareEvents(bool started); + void calculateConstraints(); + void init(); + + friend class CScreenshareFrame; + friend class CScreenshareManager; + }; + + class CCursorshareSession { + public: + CCursorshareSession(const CCursorshareSession&) = delete; + CCursorshareSession(CCursorshareSession&&) = delete; + ~CCursorshareSession(); + + eScreenshareError share(PHLMONITOR monitor, SP buffer, FSourceBoxCallback sourceBoxCallback, FScreenshareCallback callback); + void stop(); + + // constraints + DRMFormat format() const; + Vector2D bufferSize() const; + Vector2D hotspot() const; + + struct { + CSignalT<> stopped; + CSignalT<> constraintsChanged; + } m_events; + + private: + CCursorshareSession(wl_client* client, WP pointer); + + WP m_self; + bool m_stopped = false; + bool m_constraintsChanged = true; + + wl_client* m_client = nullptr; + WP m_pointer; + + // constraints + DRMFormat m_format = 0 /* DRM_FORMAT_INVALID */; + Vector2D m_hotspot = Vector2D(0, 0); + Vector2D m_bufferSize = Vector2D(0, 0); + + struct { + bool pending = false; + PHLMONITOR monitor; + SP buffer; + FSourceBoxCallback sourceBoxCallback; + FScreenshareCallback callback; + } m_pendingFrame; + + struct { + CHyprSignalListener pointerDestroyed; + CHyprSignalListener cursorChanged; + } m_listeners; + + bool copy(); + void render(); + void calculateConstraints(); + + friend class CScreenshareFrame; + friend class CScreenshareManager; + }; + + class CScreenshareFrame { + public: + CScreenshareFrame(const CScreenshareFrame&) = delete; + CScreenshareFrame(CScreenshareFrame&&) = delete; + CScreenshareFrame(WP session, bool overlayCursor, bool isFirst); + ~CScreenshareFrame(); + + bool done() const; + eScreenshareError share(SP buffer, const CRegion& damage, FScreenshareCallback callback); + + Vector2D bufferSize() const; + wl_output_transform transform() const; // returns the transform applied by compositor on the buffer + const CRegion& damage() const; + + private: + WP m_self; + WP m_session; + FScreenshareCallback m_callback; + SP m_buffer; + Vector2D m_bufferSize = Vector2D(0, 0); + CRegion m_damage; // damage in buffer coords + bool m_shared = false, m_copied = false, m_failed = false; + bool m_overlayCursor = true; + bool m_isFirst = false; + + // + void copy(); + bool copyDmabuf(); + bool copyShm(); + + void render(); + void renderMonitor(); + void renderMonitorRegion(); + void renderWindow(); + + void storeTempFB(); + + friend class CScreenshareManager; + friend class CScreenshareSession; + }; + + class CScreenshareManager { + public: + CScreenshareManager(); + + UP newSession(wl_client* client, PHLMONITOR monitor); + UP newSession(wl_client* client, PHLMONITOR monitor, CBox captureRegion); + UP newSession(wl_client* client, PHLWINDOW window); + + WP getManagedSession(wl_client* client, PHLMONITOR monitor); + WP getManagedSession(wl_client* client, PHLMONITOR monitor, CBox captureBox); + WP getManagedSession(wl_client* client, PHLWINDOW window); + + UP newCursorSession(wl_client* client, WP pointer); + + void onOutputCommit(PHLMONITOR monitor); + bool isOutputBeingSSd(PHLMONITOR monitor); + + private: + std::vector> m_sessions; + std::vector> m_cursorSessions; + std::vector> m_pendingFrames; + + struct SManagedSession { + SManagedSession(UP&& session); + + UP m_session; + CHyprSignalListener stoppedListener; + }; + + std::vector> m_managedSessions; + WP getManagedSession(eScreenshareType type, wl_client* client, PHLMONITOR monitor, PHLWINDOW window, CBox captureBox); + + friend class CScreenshareSession; + }; + + inline UP& mgr() { + static UP manager = nullptr; + if (!manager && g_pHyprRenderer) { + Log::logger->log(Log::DEBUG, "Starting ScreenshareManager"); + manager = makeUnique(); + } + return manager; + } +} + +template <> +struct std::formatter : std::formatter { + auto format(const Screenshare::eScreenshareType& res, std::format_context& ctx) const { + switch (res) { + case Screenshare::SHARE_MONITOR: return formatter::format("monitor", ctx); + case Screenshare::SHARE_WINDOW: return formatter::format("window", ctx); + case Screenshare::SHARE_REGION: return formatter::format("region", ctx); + case Screenshare::SHARE_NONE: return formatter::format("ERR NONE", ctx); + } + return formatter::format("error", ctx); + } +}; diff --git a/src/managers/screenshare/ScreenshareSession.cpp b/src/managers/screenshare/ScreenshareSession.cpp new file mode 100644 index 00000000..2fddc431 --- /dev/null +++ b/src/managers/screenshare/ScreenshareSession.cpp @@ -0,0 +1,166 @@ +#include "ScreenshareManager.hpp" +#include "../../render/OpenGL.hpp" +#include "../../Compositor.hpp" +#include "../../render/Renderer.hpp" +#include "../EventManager.hpp" +#include "../eventLoop/EventLoopManager.hpp" +#include "../../event/EventBus.hpp" + +using namespace Screenshare; + +CScreenshareSession::CScreenshareSession(PHLMONITOR monitor, wl_client* client) : m_type(SHARE_MONITOR), m_monitor(monitor), m_client(client) { + init(); +} + +CScreenshareSession::CScreenshareSession(PHLWINDOW window, wl_client* client) : m_type(SHARE_WINDOW), m_window(window), m_client(client) { + m_listeners.windowDestroyed = m_window->m_events.unmap.listen([this]() { stop(); }); + m_listeners.windowSizeChanged = m_window->m_events.resize.listen([this]() { + calculateConstraints(); + m_events.constraintsChanged.emit(); + }); + m_listeners.windowMonitorChanged = m_window->m_events.monitorChanged.listen([this]() { + m_listeners.monitorDestroyed = monitor()->m_events.disconnect.listen([this]() { stop(); }); + m_listeners.monitorModeChanged = monitor()->m_events.modeChanged.listen([this]() { + calculateConstraints(); + m_events.constraintsChanged.emit(); + }); + + calculateConstraints(); + m_events.constraintsChanged.emit(); + }); + + init(); +} + +CScreenshareSession::CScreenshareSession(PHLMONITOR monitor, CBox captureRegion, wl_client* client) : + m_type(SHARE_REGION), m_monitor(monitor), m_captureBox(captureRegion), m_client(client) { + init(); +} + +CScreenshareSession::~CScreenshareSession() { + stop(); + uintptr_t ptr = m_type == SHARE_WINDOW && !m_window.expired() ? (uintptr_t)m_window.get() : (m_monitor.expired() ? (uintptr_t)nullptr : (uintptr_t)m_monitor.get()); + LOGM(Log::TRACE, "Destroyed screenshare session for ({}): {}, {:x}", m_type, m_name, ptr); +} + +void CScreenshareSession::stop() { + if (m_stopped) + return; + m_stopped = true; + m_events.stopped.emit(); + + screenshareEvents(false); +} + +void CScreenshareSession::init() { + uintptr_t ptr = m_type == SHARE_WINDOW && !m_window.expired() ? (uintptr_t)m_window.get() : (m_monitor.expired() ? (uintptr_t)nullptr : (uintptr_t)m_monitor.get()); + LOGM(Log::TRACE, "Created screenshare session for ({}): {}, {:x}", m_type, m_name, ptr); + + m_shareStopTimer = makeShared( + std::chrono::milliseconds(500), + [this](SP self, void* data) { + // if this fires, then it's been half a second since the last frame, so we aren't sharing + screenshareEvents(false); + }, + nullptr); + + if (g_pEventLoopManager) + g_pEventLoopManager->addTimer(m_shareStopTimer); + + // scale capture box since it's in logical coords + m_captureBox.scale(monitor()->m_scale); + + m_listeners.monitorDestroyed = monitor()->m_events.disconnect.listen([this]() { stop(); }); + m_listeners.monitorModeChanged = monitor()->m_events.modeChanged.listen([this]() { + calculateConstraints(); + m_events.constraintsChanged.emit(); + }); + + calculateConstraints(); +} + +void CScreenshareSession::calculateConstraints() { + const auto PMONITOR = monitor(); + if (!PMONITOR) { + stop(); + return; + } + + // TODO: maybe support more that just monitor format in the future? + m_formats.clear(); + m_formats.push_back(NFormatUtils::alphaFormat(g_pHyprOpenGL->getPreferredReadFormat(PMONITOR))); + m_formats.push_back(g_pHyprOpenGL->getPreferredReadFormat(PMONITOR)); // some clients don't like alpha formats + + // TODO: hack, we can't bit flip so we'll format flip heh, GL_BGRA_EXT won't work here + for (auto& format : m_formats) { + if (format == DRM_FORMAT_XRGB2101010 || format == DRM_FORMAT_ARGB2101010) + format = DRM_FORMAT_XBGR2101010; + } + + switch (m_type) { + case SHARE_MONITOR: + m_bufferSize = PMONITOR->m_pixelSize; + m_name = PMONITOR->m_name; + break; + case SHARE_WINDOW: + m_bufferSize = (m_window->m_realSize->value() * PMONITOR->m_scale).round(); + m_name = m_window->m_title; + break; + case SHARE_REGION: + m_bufferSize = PMONITOR->m_transform % 2 == 0 ? m_captureBox.size() : Vector2D{m_captureBox.h, m_captureBox.w}; + m_name = PMONITOR->m_name; + break; + case SHARE_NONE: + default: + LOGM(Log::ERR, "Invalid share type?? This shouldn't happen"); + stop(); + return; + } + + LOGM(Log::TRACE, "constraints changed for {}", m_name); +} + +void CScreenshareSession::screenshareEvents(bool startSharing) { + if (startSharing && !m_sharing) { + m_sharing = true; + g_pEventManager->postEvent(SHyprIPCEvent{.event = "screencast", .data = std::format("1,{}", m_type)}); + g_pEventManager->postEvent(SHyprIPCEvent{.event = "screencastv2", .data = std::format("1,{},{}", m_type, m_name)}); + LOGM(Log::INFO, "Started screenshare session for ({}): {}", m_type, m_name); + + Event::bus()->m_events.screenshare.state.emit(true, m_type, m_name); + } else if (!startSharing && m_sharing) { + m_sharing = false; + g_pEventManager->postEvent(SHyprIPCEvent{.event = "screencast", .data = std::format("0,{}", m_type)}); + g_pEventManager->postEvent(SHyprIPCEvent{.event = "screencastv2", .data = std::format("0,{},{}", m_type, m_name)}); + LOGM(Log::INFO, "Stopped screenshare session for ({}): {}", m_type, m_name); + + Event::bus()->m_events.screenshare.state.emit(false, m_type, m_name); + } +} + +const std::vector& CScreenshareSession::allowedFormats() const { + return m_formats; +} + +Vector2D CScreenshareSession::bufferSize() const { + return m_bufferSize; +} + +PHLMONITOR CScreenshareSession::monitor() const { + if (m_type == SHARE_WINDOW && m_window.expired()) + return nullptr; + PHLMONITORREF mon = m_type == SHARE_WINDOW ? m_window->m_monitor : m_monitor; + return mon.expired() ? nullptr : mon.lock(); +} + +UP CScreenshareSession::nextFrame(bool overlayCursor) { + UP frame = makeUnique(m_self, overlayCursor, !m_sharing); + frame->m_self = frame; + + Screenshare::mgr()->m_pendingFrames.emplace_back(frame); + + // there is now a pending frame, so block ds + g_pHyprRenderer->m_directScanoutBlocked = true; + + return frame; +} diff --git a/src/meson.build b/src/meson.build deleted file mode 100644 index d0a1590e..00000000 --- a/src/meson.build +++ /dev/null @@ -1,58 +0,0 @@ -globber = run_command('sh', '-c', 'find . -name "*.cpp" | sort', check: true) -src = globber.stdout().strip().split('\n') - -executable( - 'Hyprland', - src, - link_args: '-rdynamic', - cpp_pch: 'pch/pch.hpp', - dependencies: [ - server_protos, - aquamarine, - hyprcursor, - hyprgraphics, - hyprlang, - hyprutils, - dependency('gbm'), - dependency('xcursor'), - dependency('wayland-server'), - dependency('wayland-client'), - dependency('cairo'), - dependency('libdrm'), - dependency('egl'), - dependency('xkbcommon'), - dependency('libinput', version: '>=1.28'), - dependency('re2'), - xcb_dep, - xcb_composite_dep, - xcb_errors_dep, - xcb_icccm_dep, - xcb_render_dep, - xcb_res_dep, - xcb_xfixes_dep, - backtrace_dep, - epoll_dep, - inotify_dep, - gio_dep, - tracy, - - # Try to find canihavesomecoffee's udis86 using pkgconfig - # vmt/udis86 does not provide a .pc file and won't be detected this way - # Falls back to using the subproject through udis86.wrap - dependency('udis86'), - - dependency('pixman-1'), - dependency('gl', 'opengl'), - dependency('threads'), - dependency('pango'), - dependency('pangocairo'), - dependency('uuid'), - ], - install: true, -) - -install_symlink( - 'hyprland', - install_dir: get_option('bindir'), - pointing_to: 'Hyprland', -) diff --git a/src/plugins/HookSystem.cpp b/src/plugins/HookSystem.cpp index c5def6a5..f17a4556 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" @@ -144,6 +144,12 @@ bool CFunctionHook::hook() { return false; #endif + if (g_pFunctionHookSystem->m_activeHooks.contains(rc(m_source))) { + // TODO: return actual error codes... + Log::logger->log(Log::ERR, "[functionhook] failed, function is already hooked"); + return false; + } + // jmp rel32 // offset for relative addr: 1 static constexpr uint8_t RELATIVE_JMP_ADDRESS[] = {0xE9, 0x00, 0x00, 0x00, 0x00}; @@ -168,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; } @@ -183,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; } @@ -231,6 +237,8 @@ bool CFunctionHook::hook() { m_active = true; m_hookLen = ORIGSIZE; + g_pFunctionHookSystem->m_activeHooks.emplace(rc(m_source)); + return true; } @@ -243,6 +251,8 @@ bool CFunctionHook::unhook() { if (!m_active) return false; + g_pFunctionHookSystem->m_activeHooks.erase(rc(m_source)); + // allow write to src mprotect(sc(m_source) - rc(m_source) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_WRITE | PROT_EXEC); @@ -290,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; } @@ -300,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; @@ -313,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; } @@ -355,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) { @@ -366,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; @@ -388,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/HookSystem.hpp b/src/plugins/HookSystem.hpp index cf098dd1..3431e8c8 100644 --- a/src/plugins/HookSystem.hpp +++ b/src/plugins/HookSystem.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "../helpers/memory/Memory.hpp" #define HANDLE void* @@ -70,7 +71,8 @@ class CHookSystem { uint64_t used = 0; }; - std::vector m_pages; + std::vector m_pages; + std::unordered_set m_activeHooks; friend class CFunctionHook; }; diff --git a/src/plugins/PluginAPI.cpp b/src/plugins/PluginAPI.cpp index 66b0c74e..5f89da53 100644 --- a/src/plugins/PluginAPI.cpp +++ b/src/plugins/PluginAPI.cpp @@ -2,11 +2,11 @@ #include "../Compositor.hpp" #include "../debug/HyprCtl.hpp" #include "../plugins/PluginSystem.hpp" -#include "../managers/HookSystemManager.hpp" -#include "../managers/LayoutManager.hpp" #include "../managers/eventLoop/EventLoopManager.hpp" #include "../config/ConfigManager.hpp" #include "../debug/HyprNotificationOverlay.hpp" +#include "../layout/target/Target.hpp" +#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp" #include #include @@ -37,9 +37,9 @@ APICALL SP HyprlandAPI::registerCallbackDynamic(HANDLE handle, if (!PLUGIN) return nullptr; - auto PFN = g_pHookSystem->hookDynamic(event, fn, handle); - PLUGIN->m_registeredCallbacks.emplace_back(std::make_pair<>(event, WP(PFN))); - return PFN; + //auto PFN = g_pHookSystem->hookDynamic(event, fn, handle); + //PLUGIN->m_registeredCallbacks.emplace_back(std::make_pair<>(event, WP(PFN))); + return nullptr; } APICALL bool HyprlandAPI::unregisterCallback(HANDLE handle, SP fn) { @@ -48,8 +48,8 @@ APICALL bool HyprlandAPI::unregisterCallback(HANDLE handle, SP if (!PLUGIN) return false; - g_pHookSystem->unhook(fn); - std::erase_if(PLUGIN->m_registeredCallbacks, [&](const auto& other) { return other.second.lock() == fn; }); + //g_pHookSystem->unhook(fn); + // std::erase_if(PLUGIN->m_registeredCallbacks, [&](const auto& other) { return other.second.lock() == fn; }); return true; } @@ -62,25 +62,44 @@ APICALL std::string HyprlandAPI::invokeHyprctlCommand(const std::string& call, c } APICALL bool HyprlandAPI::addLayout(HANDLE handle, const std::string& name, IHyprLayout* layout) { - auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle); - - if (!PLUGIN) - return false; - - PLUGIN->m_registeredLayouts.push_back(layout); - - return g_pLayoutManager->addLayout(name, layout); + return false; } APICALL bool HyprlandAPI::removeLayout(HANDLE handle, IHyprLayout* layout) { + return false; +} + +APICALL bool HyprlandAPI::addTiledAlgo(HANDLE handle, const std::string& name, const std::type_info* typeInfo, std::function()>&& factory) { auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle); if (!PLUGIN) return false; - std::erase(PLUGIN->m_registeredLayouts, layout); + PLUGIN->m_registeredAlgos.emplace_back(name); - return g_pLayoutManager->removeLayout(layout); + return Layout::Supplementary::algoMatcher()->registerTiledAlgo(name, typeInfo, std::move(factory)); +} + +APICALL bool HyprlandAPI::addFloatingAlgo(HANDLE handle, const std::string& name, const std::type_info* typeInfo, std::function()>&& factory) { + auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle); + + if (!PLUGIN) + return false; + + PLUGIN->m_registeredAlgos.emplace_back(name); + + return Layout::Supplementary::algoMatcher()->registerFloatingAlgo(name, typeInfo, std::move(factory)); +} + +APICALL bool HyprlandAPI::removeAlgo(HANDLE handle, const std::string& name) { + auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle); + + if (!PLUGIN) + return false; + + std::erase(PLUGIN->m_registeredAlgos, name); + + return Layout::Supplementary::algoMatcher()->unregisterAlgo(name); } APICALL bool HyprlandAPI::reloadConfig() { @@ -130,7 +149,7 @@ APICALL bool HyprlandAPI::addWindowDecoration(HANDLE handle, PHLWINDOW pWindow, pWindow->addWindowDeco(std::move(pDecoration)); - g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow); + pWindow->layoutTarget()->recalc(); return true; } @@ -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 c88fe586..77bb9926 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 740b2cce..3bd8f473 100644 --- a/src/plugins/PluginSystem.cpp +++ b/src/plugins/PluginSystem.cpp @@ -4,11 +4,11 @@ #include #include "../config/ConfigManager.hpp" #include "../debug/HyprCtl.hpp" -#include "../managers/LayoutManager.hpp" -#include "../managers/HookSystemManager.hpp" #include "../managers/eventLoop/EventLoopManager.hpp" #include "../managers/permissions/DynamicPermissionManager.hpp" #include "../debug/HyprNotificationOverlay.hpp" +#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp" +#include "../i18n/Engine.hpp" CPluginSystem::CPluginSystem() { g_pFunctionHookSystem = makeUnique(); @@ -24,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? @@ -34,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()) @@ -55,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; } @@ -70,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!"); } @@ -82,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)); } @@ -93,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")); @@ -102,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")); @@ -120,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())); } @@ -134,8 +134,8 @@ std::expected CPluginSystem::loadPluginInternal(const std g_pEventLoopManager->doLater([] { g_pConfigManager->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; } @@ -150,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); @@ -170,8 +170,10 @@ void CPluginSystem::unloadPlugin(const CPlugin* plugin, bool eject) { HyprlandAPI::removeDispatcher(plugin->m_handle, d); const auto rhc = plugin->m_registeredHyprctlCommands; - for (auto const& c : rhc) - HyprlandAPI::unregisterHyprCtlCommand(plugin->m_handle, c); + for (auto const& c : rhc) { + if (const auto sp = c.lock()) + HyprlandAPI::unregisterHyprCtlCommand(plugin->m_handle, sp); + } g_pConfigManager->removePluginConfig(plugin->m_handle); @@ -184,7 +186,7 @@ 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(); }); @@ -206,7 +208,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; } @@ -216,21 +218,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(std::format("Failed to load plugin {}: {}", NAME, result->error()), CHyprColor{0, 0, 0, 0}, 5000, ICON_ERROR); + Log::logger->log(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); 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 7d062a9b..85afaefa 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 a9c09d97..a4ebc635 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 9c241942..f94792dc 100644 --- a/src/protocols/CTMControl.cpp +++ b/src/protocols/CTMControl.cpp @@ -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) { diff --git a/src/protocols/ColorManagement.cpp b/src/protocols/ColorManagement.cpp index 408ade4d..b9c3143b 100644 --- a/src/protocols/ColorManagement.cpp +++ b/src/protocols/ColorManagement.cpp @@ -3,7 +3,7 @@ #include "color-management-v1.hpp" #include "../helpers/Monitor.hpp" #include "core/Output.hpp" -#include "types/ColorManagement.hpp" +#include "../helpers/cm/ColorManagement.hpp" #include using namespace NColorManagement; @@ -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,10 +35,7 @@ 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); - - if (PROTO::colorManagement->m_debug) { - m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_CIE1931_XYZ); - } + m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_CIE1931_XYZ); m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB); m_resource->sendSupportedTfNamed(WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22); @@ -62,9 +59,9 @@ CColorManager::CColorManager(SP resource) : m_resource(resour m_resource->sendSupportedIntent(WP_COLOR_MANAGER_V1_RENDER_INTENT_RELATIVE_BPC); } - 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 +77,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 +104,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 +125,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 +143,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 +157,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 +168,10 @@ 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->resource()->sendReady(RESOURCE->m_settings->id()); }); m_resource->setOnDestroy([this](CWpColorManagerV1* r) { PROTO::colorManagement->destroyResource(this); }); @@ -204,7 +197,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 +216,7 @@ 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->m_resource->sendReady(RESOURCE->m_settings->id()); } }); } @@ -236,27 +229,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 = DEFAULT_IMAGE_DESCRIPTION; 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 +269,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 = DEFAULT_IMAGE_DESCRIPTION; setHasImageDescription(false); }); } @@ -295,8 +285,9 @@ 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; + LOGM(Log::WARN, "Reading imageDescription while none set. Returns default or empty values"); + + return m_imageDescription->value(); } bool CColorManagementSurface::hasImageDescription() { @@ -327,13 +318,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 +336,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 +364,11 @@ CColorManagementFeedbackSurface::CColorManagementFeedbackSurface(SPm_self = RESOURCE; RESOURCE->m_settings = m_surface->getPreferredImageDescription(); - RESOURCE->resource()->sendReady(RESOURCE->m_settings.updateId()); + RESOURCE->resource()->sendReady(RESOURCE->m_settings->id()); }); 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,13 +386,7 @@ CColorManagementFeedbackSurface::CColorManagementFeedbackSurface(SPm_self = RESOURCE; RESOURCE->m_settings = m_surface->getPreferredImageDescription(); - m_currentPreferredId = RESOURCE->m_settings.updateId(); - - 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; - } + m_currentPreferredId = RESOURCE->m_settings->id(); RESOURCE->resource()->sendReady(m_currentPreferredId); }); @@ -411,7 +397,7 @@ CColorManagementFeedbackSurface::CColorManagementFeedbackSurface(SPm_enteredOutputs.size() == 1) { - const auto newId = m_surface->getPreferredImageDescription().updateId(); + const auto newId = m_surface->getPreferredImageDescription()->id(); if (m_currentPreferredId != newId) m_resource->sendPreferredChanged(newId); } @@ -434,10 +420,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 +437,25 @@ 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->resource()->sendReady(RESOURCE->m_settings->id()); 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 +476,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 +499,22 @@ CColorManagementParametricCreator::CColorManagementParametricCreator(SPm_self = RESOURCE; - RESOURCE->m_settings = m_settings; - RESOURCE->resource()->sendReady(m_settings.updateId()); + RESOURCE->m_settings = CImageDescription::from(m_settings); + RESOURCE->resource()->sendReady(RESOURCE->m_settings->id()); 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; @@ -547,7 +541,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 +558,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 +571,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 +603,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 +617,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 +636,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 +645,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 +659,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 +688,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(); @@ -736,26 +725,47 @@ CColorManagementImageDescriptionInfo::CColorManagementImageDescriptionInfo(SP(std::round(value * PRIMARIES_SCALE)); }; - if (m_settings.icc.fd >= 0) - m_resource->sendIccFile(m_settings.icc.fd, m_settings.icc.length); + // FIXME: + // if (m_icc.fd >= 0) + // m_resource->sendIccFile(m_icc.fd, m_icc.length); // send preferred client paramateres m_resource->sendPrimaries(toProto(m_settings.primaries.red.x), toProto(m_settings.primaries.red.y), toProto(m_settings.primaries.green.x), toProto(m_settings.primaries.green.y), toProto(m_settings.primaries.blue.x), toProto(m_settings.primaries.blue.y), toProto(m_settings.primaries.white.x), toProto(m_settings.primaries.white.y)); + if (m_settings.primariesNameSet) m_resource->sendPrimariesNamed(m_settings.primariesNamed); - m_resource->sendTfPower(std::round(m_settings.transferFunctionPower * 10000)); + m_resource->sendTfNamed(m_settings.transferFunction); + + if (m_settings.transferFunctionPower != 1.0f) + m_resource->sendTfPower(std::round(m_settings.transferFunctionPower * 10000)); + m_resource->sendLuminances(std::round(m_settings.luminances.min * 10000), m_settings.luminances.max, m_settings.luminances.reference); - // send expected display paramateres - m_resource->sendTargetPrimaries(toProto(m_settings.masteringPrimaries.red.x), toProto(m_settings.masteringPrimaries.red.y), toProto(m_settings.masteringPrimaries.green.x), - toProto(m_settings.masteringPrimaries.green.y), toProto(m_settings.masteringPrimaries.blue.x), toProto(m_settings.masteringPrimaries.blue.y), - toProto(m_settings.masteringPrimaries.white.x), toProto(m_settings.masteringPrimaries.white.y)); - m_resource->sendTargetLuminance(std::round(m_settings.masteringLuminances.min * 10000), m_settings.masteringLuminances.max); - m_resource->sendTargetMaxCll(m_settings.maxCLL); - m_resource->sendTargetMaxFall(m_settings.maxFALL); + const auto& targetPrimaries = ( // + m_settings.masteringPrimaries.red.x != 0 || m_settings.masteringPrimaries.red.y != 0 || // + m_settings.masteringPrimaries.green.x != 0 || m_settings.masteringPrimaries.green.y != 0 || // + m_settings.masteringPrimaries.blue.x != 0 || m_settings.masteringPrimaries.blue.y != 0) ? + m_settings.masteringPrimaries : + m_settings.primaries; + + m_resource->sendTargetPrimaries( // + toProto(targetPrimaries.red.x), toProto(targetPrimaries.red.y), // + toProto(targetPrimaries.green.x), toProto(targetPrimaries.green.y), // + toProto(targetPrimaries.blue.x), toProto(targetPrimaries.blue.y), // + toProto(targetPrimaries.white.x), toProto(targetPrimaries.white.y)); + + if (m_settings.masteringLuminances.max > 0) + m_resource->sendTargetLuminance(std::round(m_settings.masteringLuminances.min * 10000), m_settings.masteringLuminances.max); + else + m_resource->sendTargetLuminance(std::round(m_settings.luminances.min * 10000), m_settings.luminances.max); + + if (m_settings.maxCLL > 0 || m_settings.maxFALL > 0) { + m_resource->sendTargetMaxCll(m_settings.maxCLL); + m_resource->sendTargetMaxFall(m_settings.maxFALL); + } m_resource->sendDone(); } @@ -782,7 +792,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 dea9f74c..7cdab37d 100644 --- a/src/protocols/ColorManagement.hpp +++ b/src/protocols/ColorManagement.hpp @@ -7,7 +7,7 @@ #include "../helpers/Monitor.hpp" #include "core/Compositor.hpp" #include "color-management-v1.hpp" -#include "types/ColorManagement.hpp" +#include "../helpers/cm/ColorManagement.hpp" class CColorManager; class CColorManagementOutput; @@ -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 { @@ -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; @@ -157,7 +161,7 @@ class CColorManagementImageDescription { WP m_self; - NColorManagement::SImageDescription m_settings; + NColorManagement::PImageDescription m_settings; private: SP m_resource; @@ -216,9 +220,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 2fcd8e9c..9cc2da83 100644 --- a/src/protocols/CommitTiming.cpp +++ b/src/protocols/CommitTiming.cpp @@ -12,61 +12,48 @@ CCommitTimerResource::CCommitTimerResource(UP&& resource_, SP< m_resource->setOnDestroy([this](CWpCommitTimerV1* r) { PROTO::commitTiming->destroyResource(this); }); m_resource->setSetTimestamp([this](CWpCommitTimerV1* r, uint32_t tvHi, uint32_t tvLo, uint32_t tvNsec) { - return; - if (!m_surface) { r->error(WP_COMMIT_TIMER_V1_ERROR_SURFACE_DESTROYED, "Surface was gone"); return; } - if (m_pendingTimeout.has_value()) { + if (m_surface->m_pending.pendingTimeout.has_value()) { r->error(WP_COMMIT_TIMER_V1_ERROR_TIMESTAMP_EXISTS, "Timestamp is already set"); return; } - timespec ts; - ts.tv_sec = (((uint64_t)tvHi) << 32) | (uint64_t)tvLo; - ts.tv_nsec = tvNsec; + const auto delay = Time::till({.tv_sec = (((uint64_t)tvHi) << 32) | (uint64_t)tvLo, .tv_nsec = tvNsec}); - const auto TIME = Time::fromTimespec(&ts); - const auto TIME_NOW = Time::steadyNow(); - - if (TIME_NOW > TIME) { - // TODO: should we err here? - // for now just do nothing I guess, thats some lag. - m_pendingTimeout = Time::steady_dur::min(); + if (delay.count() <= 0) { + m_surface->m_pending.pendingTimeout.reset(); } else - m_pendingTimeout = TIME - TIME_NOW; + m_surface->m_pending.pendingTimeout = delay; }); m_listeners.surfaceStateCommit = m_surface->m_events.stateCommit2.listen([this](auto state) { - if (!m_pendingTimeout.has_value()) + if (!state || !state->pendingTimeout.has_value() || !m_surface || m_surface->isTearing()) return; m_surface->m_stateQueue.lock(state, LOCK_REASON_TIMER); - if (!m_timerPresent) { - m_timerPresent = true; - timer = makeShared( - m_pendingTimeout, - [this](SP self, void* data) { - if (!m_surface) + if (!state->timer) { + state->timer = makeShared( + state->pendingTimeout, + [surface = m_surface, state](SP self, void* data) { + if (!surface || !state) return; - m_surface->m_stateQueue.unlockFirst(LOCK_REASON_TIMER); + surface->m_stateQueue.unlock(state, LOCK_REASON_TIMER); }, nullptr); + g_pEventLoopManager->addTimer(state->timer); } else - timer->updateTimeout(m_pendingTimeout); + state->timer->updateTimeout(state->pendingTimeout); - m_pendingTimeout.reset(); + state->pendingTimeout.reset(); }); } -CCommitTimerResource::~CCommitTimerResource() { - ; -} - bool CCommitTimerResource::good() { return m_resource->resource(); } diff --git a/src/protocols/CommitTiming.hpp b/src/protocols/CommitTiming.hpp index e79face8..b5a1de93 100644 --- a/src/protocols/CommitTiming.hpp +++ b/src/protocols/CommitTiming.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include "WaylandProtocol.hpp" #include "commit-timing-v1.hpp" @@ -14,16 +13,12 @@ class CEventLoopTimer; class CCommitTimerResource { public: CCommitTimerResource(UP&& resource_, SP surface); - ~CCommitTimerResource(); bool good(); private: - UP m_resource; - WP m_surface; - bool m_timerPresent = false; - std::optional m_pendingTimeout; - SP timer; + UP m_resource; + WP m_surface; struct { CHyprSignalListener surfaceStateCommit; diff --git a/src/protocols/ContentType.cpp b/src/protocols/ContentType.cpp index c32f54a2..acae218c 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 260497d3..2da7b04a 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 40869c0d..821796d0 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 835fbc01..d29106e0 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 c2d4c497..6ab83ab4 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 8b6c010d..4fa3152d 100644 --- a/src/protocols/ExtWorkspace.cpp +++ b/src/protocols/ExtWorkspace.cpp @@ -1,9 +1,8 @@ #include "ExtWorkspace.hpp" #include "../Compositor.hpp" -#include "../managers/HookSystemManager.hpp" #include "../managers/eventLoop/EventLoopManager.hpp" +#include "../event/EventBus.hpp" #include -#include #include #include "core/Output.hpp" @@ -22,18 +21,21 @@ CExtWorkspaceGroupResource::CExtWorkspaceGroupResource(WPsendCapabilities(sc(0)); + if (!PROTO::outputs.contains(m_monitor->m_name)) + return; + const auto& output = PROTO::outputs.at(m_monitor->m_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()); }); - - m_manager->sendGroupToWorkspaces(m_self); - m_manager->scheduleDone(); } bool CExtWorkspaceGroupResource::good() const { @@ -46,6 +48,11 @@ WP CExtWorkspaceGroupResource::fromResource(wl_resou return data ? data->m_self : WP(); } +void CExtWorkspaceGroupResource::sendToWorkspaces() { + m_manager->sendGroupToWorkspaces(m_self); + m_manager->scheduleDone(); +} + void CExtWorkspaceGroupResource::workspaceEnter(const WP& handle) { m_resource->sendWorkspaceEnter(handle.get()); } @@ -265,9 +272,10 @@ void CExtWorkspaceManagerResource::onMonitorCreated(const PHLMONITOR& monitor) { auto& group = PROTO::extWorkspace->m_groups.emplace_back( makeUnique(m_self, makeUnique(m_resource->client(), m_resource->version(), 0), monitor)); group->m_self = group; + 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; } @@ -281,24 +289,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); } @@ -310,7 +314,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/ExtWorkspace.hpp b/src/protocols/ExtWorkspace.hpp index efaac3fe..ba4aeeae 100644 --- a/src/protocols/ExtWorkspace.hpp +++ b/src/protocols/ExtWorkspace.hpp @@ -21,6 +21,8 @@ class CExtWorkspaceGroupResource { void workspaceEnter(const WP&); void workspaceLeave(const WP&); + void sendToWorkspaces(); + PHLMONITORREF m_monitor; private: diff --git a/src/protocols/Fifo.cpp b/src/protocols/Fifo.cpp index 63ce8579..355644d9 100644 --- a/src/protocols/Fifo.cpp +++ b/src/protocols/Fifo.cpp @@ -1,8 +1,8 @@ #include "Fifo.hpp" #include "Compositor.hpp" #include "core/Compositor.hpp" -#include "../managers/HookSystemManager.hpp" #include "../helpers/Monitor.hpp" +#include "../event/EventBus.hpp" CFifoResource::CFifoResource(UP&& resource_, SP surface) : m_resource(std::move(resource_)), m_surface(surface) { if UNLIKELY (!m_resource->resource()) @@ -18,7 +18,8 @@ CFifoResource::CFifoResource(UP&& resource_, SP s return; } - m_pending.barrierSet = true; + m_surface->m_pending.barrierSet = true; + m_surface->m_pending.updated.bits.fifo = true; }); m_resource->setWaitBarrier([this](CWpFifoV1* r) { @@ -27,49 +28,37 @@ CFifoResource::CFifoResource(UP&& resource_, SP s return; } - if (!m_pending.barrierSet) - return; + if (!m_surface->m_current.barrierSet) { + // that might mean an empty commit with a barrier_set alone + static const auto PPEND = CConfigValue("debug:fifo_pending_workaround"); + if (!m_surface->m_pending.fifoScheduled) + m_surface->m_pending.fifoScheduled = checkMonitors(*PPEND); - m_pending.surfaceLocked = true; + return; + } + + m_surface->m_pending.surfaceLocked = true; }); m_listeners.surfaceStateCommit = m_surface->m_events.stateCommit.listen([this](auto state) { - if (!m_pending.surfaceLocked) + if (!state || !state->surfaceLocked) return; + static const auto PPEND = CConfigValue("debug:fifo_pending_workaround"); + //#TODO: // this feels wrong, but if we have no pending frames, presented might never come because // we are waiting on the barrier to unlock and no damage is around. - if (m_surface->m_enteredOutputs.empty() && m_surface->m_hlSurface) { - for (auto& m : g_pCompositor->m_monitors) { - if (!m || !m->m_enabled) - continue; + // unlock on timeout instead? + if (!state->fifoScheduled) + state->fifoScheduled = checkMonitors(*PPEND); - auto box = m_surface->m_hlSurface->getSurfaceBoxGlobal(); - if (box && !box->intersection({m->m_position, m->m_size}).empty()) { - if (m->m_tearingState.activelyTearing) - return; // dont fifo lock on tearing. - - g_pCompositor->scheduleFrameForMonitor(m, Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); - } - } - } else { - for (auto& m : m_surface->m_enteredOutputs) { - if (!m) - continue; - - if (m->m_tearingState.activelyTearing) - return; // dont fifo lock on tearing. - - g_pCompositor->scheduleFrameForMonitor(m.lock(), Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); - } - } + if (!state->fifoScheduled) + return; // only lock once its mapped. if (m_surface->m_mapped) m_surface->m_stateQueue.lock(state, LOCK_REASON_FIFO); - - m_pending = {}; }); } @@ -82,9 +71,41 @@ bool CFifoResource::good() { } void CFifoResource::presented() { + m_surface->m_current.barrierSet = false; m_surface->m_stateQueue.unlockFirst(LOCK_REASON_FIFO); } +bool CFifoResource::checkMonitors(bool needsSchedule) { + if (m_surface->m_enteredOutputs.empty() && m_surface->m_hlSurface) { + for (auto& m : g_pCompositor->m_monitors) { + if (!m || !m->m_enabled) + continue; + + auto box = m_surface->m_hlSurface->getSurfaceBoxGlobal(); + if (box && !box->intersection({m->m_position, m->m_size}).empty()) { + if (m->m_tearingState.activelyTearing) + return false; // dont fifo lock on tearing. + + if (needsSchedule) + g_pCompositor->scheduleFrameForMonitor(m, Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); + } + } + } else { + for (auto& m : m_surface->m_enteredOutputs) { + if (!m) + continue; + + if (m->m_tearingState.activelyTearing) + return false; // dont fifo lock on tearing. + + if (needsSchedule) + g_pCompositor->scheduleFrameForMonitor(m.lock(), Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); + } + } + + return true; +} + CFifoManagerResource::CFifoManagerResource(UP&& resource_) : m_resource(std::move(resource_)) { if UNLIKELY (!m_resource->resource()) return; @@ -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 8551e78c..5b143f79 100644 --- a/src/protocols/Fifo.hpp +++ b/src/protocols/Fifo.hpp @@ -21,18 +21,12 @@ class CFifoResource { WP m_surface; - struct SState { - bool barrierSet = false; - bool surfaceLocked = false; - }; - - SState m_pending; - struct { CHyprSignalListener surfaceStateCommit; } m_listeners; void presented(); + bool checkMonitors(bool needsSchedule = false); friend class CFifoProtocol; friend class CFifoManagerResource; diff --git a/src/protocols/FocusGrab.cpp b/src/protocols/FocusGrab.cpp index c6530826..62b65655 100644 --- a/src/protocols/FocusGrab.cpp +++ b/src/protocols/FocusGrab.cpp @@ -3,6 +3,7 @@ #include #include "../managers/input/InputManager.hpp" #include "../managers/SeatManager.hpp" +#include "../desktop/state/FocusState.hpp" #include "core/Compositor.hpp" #include #include @@ -104,9 +105,9 @@ void CFocusGrab::refocusKeyboard() { } if (surface) - g_pCompositor->focusSurface(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 22bef314..baabda7c 100644 --- a/src/protocols/ForeignToplevel.cpp +++ b/src/protocols/ForeignToplevel.cpp @@ -1,6 +1,6 @@ #include "ForeignToplevel.hpp" #include "../Compositor.hpp" -#include "../managers/HookSystemManager.hpp" +#include "../event/EventBus.hpp" CForeignToplevelHandle::CForeignToplevelHandle(SP resource_, PHLWINDOW pWindow_) : m_resource(resource_), m_window(pWindow_) { if UNLIKELY (!resource_->resource()) @@ -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 355117e7..0ff74e75 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 86b68584..56591261 100644 --- a/src/protocols/ForeignToplevelWlr.cpp +++ b/src/protocols/ForeignToplevelWlr.cpp @@ -1,11 +1,12 @@ #include "ForeignToplevelWlr.hpp" +#include "core/Output.hpp" #include #include "../Compositor.hpp" -#include "managers/input/InputManager.hpp" -#include "protocols/core/Output.hpp" -#include "render/Renderer.hpp" -#include "../managers/HookSystemManager.hpp" +#include "../managers/input/InputManager.hpp" +#include "../desktop/state/FocusState.hpp" +#include "../render/Renderer.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()) @@ -34,7 +35,7 @@ CForeignToplevelHandleWlr::CForeignToplevelHandleWlr(SPm_suppressedEvents & SUPPRESS_FULLSCREEN) + if UNLIKELY (PWINDOW->m_suppressedEvents & Desktop::View::SUPPRESS_FULLSCREEN) return; if UNLIKELY (!PWINDOW->m_isMapped) { @@ -50,7 +51,7 @@ CForeignToplevelHandleWlr::CForeignToplevelHandleWlr(SPm_workspace != monitor->m_activeWorkspace) { g_pCompositor->moveWindowToWorkspaceSafe(PWINDOW, monitor->m_activeWorkspace); - g_pCompositor->setActiveMonitor(monitor); + Desktop::focusState()->rawMonitorFocus(monitor); } } } @@ -65,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); @@ -77,7 +78,7 @@ CForeignToplevelHandleWlr::CForeignToplevelHandleWlr(SPm_suppressedEvents & SUPPRESS_MAXIMIZE) + if UNLIKELY (PWINDOW->m_suppressedEvents & Desktop::View::SUPPRESS_MAXIMIZE) return; if UNLIKELY (!PWINDOW->m_isMapped) { @@ -94,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); @@ -153,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; @@ -178,7 +185,7 @@ void CForeignToplevelHandleWlr::sendState() { wl_array state; wl_array_init(&state); - if (PWINDOW == g_pCompositor->m_lastWindow) { + if (PWINDOW == Desktop::focusState()->window()) { auto p = sc(wl_array_add(&state, sizeof(uint32_t))); *p = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED; } @@ -205,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); }); @@ -216,7 +223,7 @@ CForeignToplevelWlrManager::CForeignToplevelWlrManager(SPm_lastWindow; + m_lastFocus = Desktop::focusState()->window(); } void CForeignToplevelWlrManager::onMap(PHLWINDOW pWindow) { @@ -227,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()); @@ -336,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); } }); } @@ -408,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 abfadf59..444cbe0d 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 899d1390..9bdf5910 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 730a15b0..00000000 --- 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 32e2202c..00000000 --- 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 58382de9..c28b881f 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 b2692d1f..5d882206 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 4122bf24..b80bf5c6 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 00000000..9f54533e --- /dev/null +++ b/src/protocols/ImageCaptureSource.cpp @@ -0,0 +1,135 @@ +#include "ImageCaptureSource.hpp" +#include "core/Output.hpp" +#include "../helpers/Monitor.hpp" +#include "../desktop/view/Window.hpp" +#include "ForeignToplevel.hpp" + +CImageCaptureSource::CImageCaptureSource(SP resource, PHLMONITOR pMonitor) : m_resource(resource), m_monitor(pMonitor) { + if UNLIKELY (!good()) + return; + + m_resource->setData(this); + m_resource->setDestroy([this](CExtImageCaptureSourceV1* pMgr) { PROTO::imageCaptureSource->destroyResource(this); }); + m_resource->setOnDestroy([this](CExtImageCaptureSourceV1* pMgr) { PROTO::imageCaptureSource->destroyResource(this); }); +} + +CImageCaptureSource::CImageCaptureSource(SP resource, PHLWINDOW pWindow) : m_resource(resource), m_window(pWindow) { + if UNLIKELY (!good()) + return; + + m_resource->setData(this); + m_resource->setDestroy([this](CExtImageCaptureSourceV1* pMgr) { PROTO::imageCaptureSource->destroyResource(this); }); + m_resource->setOnDestroy([this](CExtImageCaptureSourceV1* pMgr) { PROTO::imageCaptureSource->destroyResource(this); }); +} + +bool CImageCaptureSource::good() { + return m_resource && m_resource->resource(); +} + +std::string CImageCaptureSource::getName() { + if (!m_monitor.expired()) + return m_monitor->m_name; + if (!m_window.expired()) + return m_window->m_title; + + return "error"; +} + +std::string CImageCaptureSource::getTypeName() { + if (!m_monitor.expired()) + return "monitor"; + if (!m_window.expired()) + return "window"; + + return "error"; +} + +CBox CImageCaptureSource::logicalBox() { + if (!m_monitor.expired()) + return m_monitor->logicalBox(); + if (!m_window.expired()) + return m_window->getFullWindowBoundingBox(); + return CBox(); +} + +COutputImageCaptureSourceProtocol::COutputImageCaptureSourceProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void COutputImageCaptureSourceProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = PROTO::imageCaptureSource->m_outputManagers.emplace_back(makeShared(client, ver, id)); + + if UNLIKELY (!RESOURCE->resource()) { + wl_client_post_no_memory(client); + PROTO::imageCaptureSource->m_outputManagers.pop_back(); + return; + } + + RESOURCE->setDestroy([](CExtOutputImageCaptureSourceManagerV1* pMgr) { PROTO::imageCaptureSource->destroyResource(pMgr); }); + RESOURCE->setOnDestroy([](CExtOutputImageCaptureSourceManagerV1* pMgr) { PROTO::imageCaptureSource->destroyResource(pMgr); }); + RESOURCE->setCreateSource([](CExtOutputImageCaptureSourceManagerV1* pMgr, uint32_t id, wl_resource* output) { + PHLMONITOR pMonitor = CWLOutputResource::fromResource(output)->m_monitor.lock(); + if (!pMonitor) { + LOGM(Log::ERR, "Client tried to create source from invalid output resource"); + pMgr->error(-1, "invalid output resource"); + return; + } + + auto PSOURCE = + PROTO::imageCaptureSource->m_sources.emplace_back(makeShared(makeShared(pMgr->client(), pMgr->version(), id), pMonitor)); + PSOURCE->m_self = PSOURCE; + + LOGM(Log::INFO, "New capture source for monitor: {}", pMonitor->m_name); + }); +} + +CToplevelImageCaptureSourceProtocol::CToplevelImageCaptureSourceProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void CToplevelImageCaptureSourceProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = PROTO::imageCaptureSource->m_toplevelManagers.emplace_back(makeShared(client, ver, id)); + + if UNLIKELY (!RESOURCE->resource()) { + RESOURCE->noMemory(); + PROTO::imageCaptureSource->m_toplevelManagers.pop_back(); + return; + } + + RESOURCE->setDestroy([](CExtForeignToplevelImageCaptureSourceManagerV1* pMgr) { PROTO::imageCaptureSource->destroyResource(pMgr); }); + RESOURCE->setOnDestroy([](CExtForeignToplevelImageCaptureSourceManagerV1* pMgr) { PROTO::imageCaptureSource->destroyResource(pMgr); }); + RESOURCE->setCreateSource([](CExtForeignToplevelImageCaptureSourceManagerV1* pMgr, uint32_t id, wl_resource* handle) { + PHLWINDOW pWindow = PROTO::foreignToplevel->windowFromHandleResource(handle); + if (!pWindow) { + LOGM(Log::ERR, "Client tried to create source from invalid foreign toplevel handle resource"); + pMgr->error(-1, "invalid foreign toplevel resource"); + return; + } + + auto PSOURCE = + PROTO::imageCaptureSource->m_sources.emplace_back(makeShared(makeShared(pMgr->client(), pMgr->version(), id), pWindow)); + PSOURCE->m_self = PSOURCE; + + LOGM(Log::INFO, "New capture source for foreign toplevel: {}", pWindow->m_title); + }); +} + +CImageCaptureSourceProtocol::CImageCaptureSourceProtocol() { + m_output = makeUnique(&ext_output_image_capture_source_manager_v1_interface, 1, "OutputImageCaptureSource"); + m_toplevel = makeUnique(&ext_foreign_toplevel_image_capture_source_manager_v1_interface, 1, "ForeignToplevelImageCaptureSource"); +} + +SP CImageCaptureSourceProtocol::sourceFromResource(wl_resource* res) { + auto data = sc(sc(wl_resource_get_user_data(res))->data()); + return data && data->m_self ? data->m_self.lock() : nullptr; +} + +void CImageCaptureSourceProtocol::destroyResource(CExtOutputImageCaptureSourceManagerV1* resource) { + std::erase_if(m_outputManagers, [&](const auto& other) { return other.get() == resource; }); +} +void CImageCaptureSourceProtocol::destroyResource(CExtForeignToplevelImageCaptureSourceManagerV1* resource) { + std::erase_if(m_toplevelManagers, [&](const auto& other) { return other.get() == resource; }); +} +void CImageCaptureSourceProtocol::destroyResource(CImageCaptureSource* resource) { + std::erase_if(m_sources, [&](const auto& other) { return other.get() == resource; }); +} diff --git a/src/protocols/ImageCaptureSource.hpp b/src/protocols/ImageCaptureSource.hpp new file mode 100644 index 00000000..47580dd2 --- /dev/null +++ b/src/protocols/ImageCaptureSource.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include + +#include "../defines.hpp" +#include "../helpers/signal/Signal.hpp" +#include "WaylandProtocol.hpp" +#include "ext-image-capture-source-v1.hpp" + +class CImageCopyCaptureSession; + +class CImageCaptureSource { + public: + CImageCaptureSource(SP resource, PHLMONITOR pMonitor); + CImageCaptureSource(SP resource, PHLWINDOW pWindow); + + bool good(); + std::string getName(); + std::string getTypeName(); + CBox logicalBox(); + + WP m_self; + + private: + SP m_resource; + + PHLMONITORREF m_monitor; + PHLWINDOWREF m_window; + + friend class CImageCopyCaptureSession; + friend class CImageCopyCaptureCursorSession; +}; + +class COutputImageCaptureSourceProtocol : public IWaylandProtocol { + public: + COutputImageCaptureSourceProtocol(const wl_interface* iface, const int& ver, const std::string& name); + + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); +}; + +class CToplevelImageCaptureSourceProtocol : public IWaylandProtocol { + public: + CToplevelImageCaptureSourceProtocol(const wl_interface* iface, const int& ver, const std::string& name); + + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); +}; + +class CImageCaptureSourceProtocol { + public: + CImageCaptureSourceProtocol(); + + SP sourceFromResource(wl_resource* resource); + + void destroyResource(CExtOutputImageCaptureSourceManagerV1* resource); + void destroyResource(CExtForeignToplevelImageCaptureSourceManagerV1* resource); + void destroyResource(CImageCaptureSource* resource); + + private: + UP m_output; + UP m_toplevel; + + std::vector> m_outputManagers; + std::vector> m_toplevelManagers; + + std::vector> m_sources; + + friend class COutputImageCaptureSourceProtocol; + friend class CToplevelImageCaptureSourceProtocol; +}; + +namespace PROTO { + inline UP imageCaptureSource; +}; diff --git a/src/protocols/ImageCopyCapture.cpp b/src/protocols/ImageCopyCapture.cpp new file mode 100644 index 00000000..eca3c939 --- /dev/null +++ b/src/protocols/ImageCopyCapture.cpp @@ -0,0 +1,516 @@ +#include "ImageCopyCapture.hpp" +#include "../managers/screenshare/ScreenshareManager.hpp" +#include "../managers/permissions/DynamicPermissionManager.hpp" +#include "../managers/PointerManager.hpp" +#include "./core/Seat.hpp" +#include "LinuxDMABUF.hpp" +#include "../desktop/view/Window.hpp" +#include "../render/OpenGL.hpp" +#include "../desktop/state/FocusState.hpp" +#include + +using namespace Screenshare; + +CImageCopyCaptureSession::CImageCopyCaptureSession(SP resource, SP source, extImageCopyCaptureManagerV1Options options) : + m_resource(resource), m_source(source), m_paintCursor(options & EXT_IMAGE_COPY_CAPTURE_MANAGER_V1_OPTIONS_PAINT_CURSORS) { + if UNLIKELY (!good()) + return; + + m_resource->setDestroy([this](CExtImageCopyCaptureSessionV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); }); + m_resource->setOnDestroy([this](CExtImageCopyCaptureSessionV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); }); + + m_resource->setCreateFrame([this](CExtImageCopyCaptureSessionV1* pMgr, uint32_t id) { + if (!m_frame.expired()) { + LOGM(Log::ERR, "Duplicate frame in session for source: \"{}\"", m_source->getName()); + m_resource->error(EXT_IMAGE_COPY_CAPTURE_SESSION_V1_ERROR_DUPLICATE_FRAME, "duplicate frame"); + return; + } + + auto PFRAME = PROTO::imageCopyCapture->m_frames.emplace_back( + makeShared(makeShared(pMgr->client(), pMgr->version(), id), m_self)); + + m_frame = PFRAME; + }); + + if (m_source->m_monitor) + m_session = Screenshare::mgr()->newSession(m_resource->client(), m_source->m_monitor.lock()); + else + m_session = Screenshare::mgr()->newSession(m_resource->client(), m_source->m_window.lock()); + + if UNLIKELY (!m_session) { + m_resource->sendStopped(); + m_resource->error(-1, "unable to share screen"); + return; + } + + sendConstraints(); + + m_listeners.constraintsChanged = m_session->m_events.constraintsChanged.listen([this]() { sendConstraints(); }); + m_listeners.stopped = m_session->m_events.stopped.listen([this]() { PROTO::imageCopyCapture->destroyResource(this); }); +} + +CImageCopyCaptureSession::~CImageCopyCaptureSession() { + if (m_session) + m_session->stop(); + if (m_resource->resource()) + m_resource->sendStopped(); +} + +bool CImageCopyCaptureSession::good() { + return m_resource && m_resource->resource(); +} + +void CImageCopyCaptureSession::sendConstraints() { + auto formats = m_session->allowedFormats(); + + if UNLIKELY (formats.empty()) { + m_session->stop(); + m_resource->error(-1, "no formats available"); + return; + } + + for (DRMFormat format : formats) { + m_resource->sendShmFormat(NFormatUtils::drmToShm(format)); + + auto modifiers = g_pHyprOpenGL->getDRMFormatModifiers(format); + + wl_array modsArr; + wl_array_init(&modsArr); + if (!modifiers.empty()) { + wl_array_add(&modsArr, modifiers.size() * sizeof(uint64_t)); + memcpy(modsArr.data, modifiers.data(), modifiers.size() * sizeof(uint64_t)); + } + m_resource->sendDmabufFormat(format, &modsArr); + wl_array_release(&modsArr); + } + + dev_t device = PROTO::linuxDma->getMainDevice(); + struct wl_array deviceArr = { + .size = sizeof(device), + .data = sc(&device), + }; + m_resource->sendDmabufDevice(&deviceArr); + + m_bufferSize = m_session->bufferSize(); + m_resource->sendBufferSize(m_bufferSize.x, m_bufferSize.y); + + m_resource->sendDone(); +} + +CImageCopyCaptureCursorSession::CImageCopyCaptureCursorSession(SP resource, SP source, SP pointer) : + m_resource(resource), m_source(source), m_pointer(pointer) { + if UNLIKELY (!good()) + return; + + if (!m_source || (!m_source->m_monitor && !m_source->m_window)) + return; + + const auto PMONITOR = m_source->m_monitor.expired() ? m_source->m_window->m_monitor.lock() : m_source->m_monitor.lock(); + + // TODO: add listeners for source being destroyed + + sendCursorEvents(); + m_listeners.commit = PMONITOR->m_events.commit.listen([this, PMONITOR]() { sendCursorEvents(); }); + + m_resource->setDestroy([this](CExtImageCopyCaptureCursorSessionV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); }); + m_resource->setOnDestroy([this](CExtImageCopyCaptureCursorSessionV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); }); + + m_resource->setGetCaptureSession([this](CExtImageCopyCaptureCursorSessionV1* pMgr, uint32_t id) { + if (m_session || m_sessionResource) { + LOGM(Log::ERR, "Duplicate cursor copy capture session for source: \"{}\"", m_source->getName()); + m_resource->error(EXT_IMAGE_COPY_CAPTURE_CURSOR_SESSION_V1_ERROR_DUPLICATE_SESSION, "duplicate session"); + return; + } + + m_sessionResource = makeShared(pMgr->client(), pMgr->version(), id); + + m_sessionResource->setDestroy([this](CExtImageCopyCaptureSessionV1* pMgr) { destroyCaptureSession(); }); + m_sessionResource->setOnDestroy([this](CExtImageCopyCaptureSessionV1* pMgr) { destroyCaptureSession(); }); + + m_sessionResource->setCreateFrame([this](CExtImageCopyCaptureSessionV1* pMgr, uint32_t id) { + if UNLIKELY (!m_session || !m_sessionResource) + return; + + if (m_frameResource) { + LOGM(Log::ERR, "Duplicate frame in session for source: \"{}\"", m_source->getName()); + m_resource->error(EXT_IMAGE_COPY_CAPTURE_SESSION_V1_ERROR_DUPLICATE_FRAME, "duplicate frame"); + return; + } + + createFrame(makeShared(pMgr->client(), pMgr->version(), id)); + }); + + m_session = Screenshare::mgr()->newCursorSession(pMgr->client(), m_pointer); + if UNLIKELY (!m_session) { + m_sessionResource->sendStopped(); + m_sessionResource->error(-1, "unable to share cursor"); + return; + } + + sendConstraints(); + + m_listeners.constraintsChanged = m_session->m_events.constraintsChanged.listen([this]() { sendConstraints(); }); + m_listeners.stopped = m_session->m_events.stopped.listen([this]() { destroyCaptureSession(); }); + }); +} + +CImageCopyCaptureCursorSession::~CImageCopyCaptureCursorSession() { + destroyCaptureSession(); +} + +bool CImageCopyCaptureCursorSession::good() { + return m_resource && m_resource->resource(); +} + +void CImageCopyCaptureCursorSession::destroyCaptureSession() { + m_listeners.constraintsChanged.reset(); + m_listeners.stopped.reset(); + + if (m_frameResource && m_frameResource->resource()) + m_frameResource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED); + m_frameResource.reset(); + + m_sessionResource.reset(); + m_session.reset(); +} + +void CImageCopyCaptureCursorSession::createFrame(SP resource) { + m_frameResource = resource; + m_captured = false; + m_buffer.reset(); + + m_frameResource->setDestroy([this](CExtImageCopyCaptureFrameV1* pMgr) { m_frameResource.reset(); }); + m_frameResource->setOnDestroy([this](CExtImageCopyCaptureFrameV1* pMgr) { m_frameResource.reset(); }); + + m_frameResource->setAttachBuffer([this](CExtImageCopyCaptureFrameV1* pMgr, wl_resource* buf) { + if UNLIKELY (!m_frameResource || !m_frameResource->resource()) + return; + + if (m_captured) { + LOGM(Log::ERR, "Frame already captured in attach_buffer, {:x}", (uintptr_t)this); + m_frameResource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured"); + m_frameResource.reset(); + return; + } + + auto PBUFFERRES = CWLBufferResource::fromResource(buf); + if (!PBUFFERRES || !PBUFFERRES->m_buffer) { + LOGM(Log::ERR, "Invalid buffer in attach_buffer {:x}", (uintptr_t)this); + m_frameResource->error(-1, "invalid buffer"); + m_frameResource.reset(); + return; + } + + m_buffer = PBUFFERRES->m_buffer.lock(); + }); + + m_frameResource->setDamageBuffer([this](CExtImageCopyCaptureFrameV1* pMgr, int32_t x, int32_t y, int32_t w, int32_t h) { + if UNLIKELY (!m_frameResource || !m_frameResource->resource()) + return; + + if (m_captured) { + LOGM(Log::ERR, "Frame already captured in damage_buffer, {:x}", (uintptr_t)this); + m_frameResource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured"); + m_frameResource.reset(); + return; + } + + if (x < 0 || y < 0 || w <= 0 || h <= 0) { + m_frameResource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_INVALID_BUFFER_DAMAGE, "invalid buffer damage"); + m_frameResource.reset(); + return; + } + + // we don't really need to keep track of damage for cursor frames because we will just copy the whole thing + }); + + m_frameResource->setCapture([this](CExtImageCopyCaptureFrameV1* pMgr) { + if UNLIKELY (!m_frameResource || !m_frameResource->resource()) + return; + + if (m_captured) { + LOGM(Log::ERR, "Frame already captured in capture, {:x}", (uintptr_t)this); + m_frameResource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured"); + m_frameResource.reset(); + return; + } + + const auto PMONITOR = m_source->m_monitor.expired() ? m_source->m_window->m_monitor.lock() : m_source->m_monitor.lock(); + + auto sourceBoxCallback = [this]() { return m_source ? m_source->logicalBox() : CBox(); }; + auto error = m_session->share(PMONITOR, m_buffer, sourceBoxCallback, [this](eScreenshareResult result) { + switch (result) { + case RESULT_COPIED: m_frameResource->sendReady(); break; + case RESULT_NOT_COPIED: m_frameResource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); break; + case RESULT_TIMESTAMP: + auto [sec, nsec] = Time::secNsec(Time::steadyNow()); + uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0; + uint32_t tvSecLo = sec & 0xFFFFFFFF; + m_frameResource->sendPresentationTime(tvSecHi, tvSecLo, nsec); + break; + } + }); + + if (!m_frameResource) + return; + + switch (error) { + case ERROR_NONE: m_captured = true; break; + case ERROR_NO_BUFFER: + m_frameResource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_NO_BUFFER, "no buffer attached"); + m_frameResource.reset(); + break; + case ERROR_BUFFER_SIZE: + case ERROR_BUFFER_FORMAT: m_frameResource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS); break; + case ERROR_STOPPED: m_frameResource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED); break; + case ERROR_UNKNOWN: m_frameResource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); break; + } + }); + + // we should always copy over the entire cursor image, it doesn't cost much + m_frameResource->sendDamage(0, 0, m_bufferSize.x, m_bufferSize.y); + + // the cursor is never transformed... probably? + m_frameResource->sendTransform(WL_OUTPUT_TRANSFORM_NORMAL); +} + +void CImageCopyCaptureCursorSession::sendConstraints() { + if UNLIKELY (!m_session || !m_sessionResource) + return; + + auto format = m_session->format(); + if UNLIKELY (format == DRM_FORMAT_INVALID) { + m_session->stop(); + m_sessionResource->error(-1, "no formats available"); + return; + } + + m_sessionResource->sendShmFormat(NFormatUtils::drmToShm(format)); + + auto modifiers = g_pHyprOpenGL->getDRMFormatModifiers(format); + + wl_array modsArr; + wl_array_init(&modsArr); + if (!modifiers.empty()) { + wl_array_add(&modsArr, modifiers.size() * sizeof(uint64_t)); + memcpy(modsArr.data, modifiers.data(), modifiers.size() * sizeof(uint64_t)); + } + m_sessionResource->sendDmabufFormat(format, &modsArr); + wl_array_release(&modsArr); + + dev_t device = PROTO::linuxDma->getMainDevice(); + struct wl_array deviceArr = { + .size = sizeof(device), + .data = sc(&device), + }; + m_sessionResource->sendDmabufDevice(&deviceArr); + + m_bufferSize = m_session->bufferSize(); + m_sessionResource->sendBufferSize(m_bufferSize.x, m_bufferSize.y); + + m_sessionResource->sendDone(); +} + +void CImageCopyCaptureCursorSession::sendCursorEvents() { + const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_resource->client(), PERMISSION_TYPE_CURSOR_POS); + if (PERM != PERMISSION_RULE_ALLOW_MODE_ALLOW) { + if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY) { + m_resource->error(-1, "client not allowed to capture cursor"); + PROTO::imageCopyCapture->destroyResource(this); + } + return; + } + + const auto PMONITOR = m_source->m_monitor.expired() ? m_source->m_window->m_monitor.lock() : m_source->m_monitor.lock(); + CBox sourceBox = m_source->logicalBox(); + bool overlaps = g_pPointerManager->getCursorBoxGlobal().overlaps(sourceBox); + + if (m_entered && !overlaps) { + m_entered = false; + m_resource->sendLeave(); + return; + } else if (!m_entered && overlaps) { + m_entered = true; + m_resource->sendEnter(); + } + + if (!overlaps) + return; + + Vector2D pos = g_pPointerManager->position() - sourceBox.pos(); + if (pos != m_pos) { + m_pos = pos; + m_resource->sendPosition(m_pos.x, m_pos.y); + } + + Vector2D hotspot = g_pPointerManager->hotspot(); + if (hotspot != m_hotspot) { + m_hotspot = hotspot; + m_resource->sendHotspot(m_hotspot.x, m_hotspot.y); + } +} + +CImageCopyCaptureFrame::CImageCopyCaptureFrame(SP resource, WP session) : m_resource(resource), m_session(session) { + if UNLIKELY (!good()) + return; + + if (m_session->m_bufferSize != m_session->m_session->bufferSize()) { + m_session->sendConstraints(); + m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); + return; + } + + m_frame = m_session->m_session->nextFrame(m_session->m_paintCursor); + + m_resource->setDestroy([this](CExtImageCopyCaptureFrameV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); }); + m_resource->setOnDestroy([this](CExtImageCopyCaptureFrameV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); }); + + m_resource->setAttachBuffer([this](CExtImageCopyCaptureFrameV1* pMgr, wl_resource* buf) { + if (m_captured) { + LOGM(Log::ERR, "Frame already captured in attach_buffer, {:x}", (uintptr_t)this); + m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured"); + return; + } + + auto PBUFFERRES = CWLBufferResource::fromResource(buf); + if (!PBUFFERRES || !PBUFFERRES->m_buffer) { + LOGM(Log::ERR, "Invalid buffer in attach_buffer {:x}", (uintptr_t)this); + m_resource->error(-1, "invalid buffer"); + return; + } + + m_buffer = PBUFFERRES->m_buffer.lock(); + }); + + m_resource->setDamageBuffer([this](CExtImageCopyCaptureFrameV1* pMgr, int32_t x, int32_t y, int32_t w, int32_t h) { + if (m_captured) { + LOGM(Log::ERR, "Frame already captured in damage_buffer, {:x}", (uintptr_t)this); + m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured"); + return; + } + + if (x < 0 || y < 0 || w <= 0 || h <= 0) { + m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_INVALID_BUFFER_DAMAGE, "invalid buffer damage"); + return; + } + + m_clientDamage.add(x, y, w, h); + }); + + m_resource->setCapture([this](CExtImageCopyCaptureFrameV1* pMgr) { + if (m_captured) { + LOGM(Log::ERR, "Frame already captured in capture, {:x}", (uintptr_t)this); + m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured"); + return; + } + + auto error = m_frame->share(m_buffer, m_clientDamage, [this](eScreenshareResult result) { + switch (result) { + case RESULT_COPIED: m_resource->sendReady(); break; + case RESULT_NOT_COPIED: m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); break; + case RESULT_TIMESTAMP: + auto [sec, nsec] = Time::secNsec(Time::steadyNow()); + uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0; + uint32_t tvSecLo = sec & 0xFFFFFFFF; + m_resource->sendPresentationTime(tvSecHi, tvSecLo, nsec); + break; + } + }); + + switch (error) { + case ERROR_NONE: m_captured = true; break; + case ERROR_NO_BUFFER: m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_NO_BUFFER, "no buffer attached"); break; + case ERROR_BUFFER_SIZE: + case ERROR_BUFFER_FORMAT: m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS); break; + case ERROR_STOPPED: m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED); break; + case ERROR_UNKNOWN: m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); break; + } + }); + + m_clientDamage.clear(); + + // TODO: see ScreenshareFrame::share() for "add a damage ring for output damage since last shared frame" + m_resource->sendDamage(0, 0, m_session->m_bufferSize.x, m_session->m_bufferSize.y); + + m_resource->sendTransform(m_frame->transform()); +} + +CImageCopyCaptureFrame::~CImageCopyCaptureFrame() { + if (m_session) + m_session->m_frame.reset(); +} + +bool CImageCopyCaptureFrame::good() { + return m_resource && m_resource->resource(); +} + +CImageCopyCaptureProtocol::CImageCopyCaptureProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void CImageCopyCaptureProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = m_managers.emplace_back(makeShared(client, ver, id)); + + if UNLIKELY (!RESOURCE->resource()) { + wl_client_post_no_memory(client); + m_managers.pop_back(); + return; + } + + RESOURCE->setDestroy([this](CExtImageCopyCaptureManagerV1* pMgr) { destroyResource(pMgr); }); + RESOURCE->setOnDestroy([this](CExtImageCopyCaptureManagerV1* pMgr) { destroyResource(pMgr); }); + + RESOURCE->setCreateSession([this](CExtImageCopyCaptureManagerV1* pMgr, uint32_t id, wl_resource* source_, extImageCopyCaptureManagerV1Options options) { + auto source = PROTO::imageCaptureSource->sourceFromResource(source_); + if (!source) { + LOGM(Log::ERR, "Client tried to create image copy capture session from invalid source"); + pMgr->error(-1, "invalid image capture source"); + return; + } + + if (options > 1) { + LOGM(Log::ERR, "Client tried to create image copy capture session with invalid options"); + pMgr->error(EXT_IMAGE_COPY_CAPTURE_MANAGER_V1_ERROR_INVALID_OPTION, "Options can't be above 1"); + return; + } + + auto& PSESSION = + m_sessions.emplace_back(makeShared(makeShared(pMgr->client(), pMgr->version(), id), source, options)); + PSESSION->m_self = PSESSION; + LOGM(Log::INFO, "New image copy capture session for source ({}): \"{}\"", source->getTypeName(), source->getName()); + }); + + RESOURCE->setCreatePointerCursorSession([this](CExtImageCopyCaptureManagerV1* pMgr, uint32_t id, wl_resource* source_, wl_resource* pointer_) { + SP source = PROTO::imageCaptureSource->sourceFromResource(source_); + if (!source) { + LOGM(Log::ERR, "Client tried to create image copy capture session from invalid source"); + destroyResource(pMgr); + return; + } + + const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(pMgr->client(), PERMISSION_TYPE_CURSOR_POS); + if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY) + return; + + m_cursorSessions.emplace_back(makeShared(makeShared(pMgr->client(), pMgr->version(), id), source, + CWLPointerResource::fromResource(pointer_))); + + LOGM(Log::INFO, "New image copy capture cursor session for source ({}): \"{}\"", source->getTypeName(), source->getName()); + }); +} + +void CImageCopyCaptureProtocol::destroyResource(CExtImageCopyCaptureManagerV1* resource) { + std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; }); +} + +void CImageCopyCaptureProtocol::destroyResource(CImageCopyCaptureSession* resource) { + std::erase_if(m_sessions, [&](const auto& other) { return other.get() == resource; }); +} + +void CImageCopyCaptureProtocol::destroyResource(CImageCopyCaptureCursorSession* resource) { + std::erase_if(m_cursorSessions, [&](const auto& other) { return other.get() == resource; }); +} + +void CImageCopyCaptureProtocol::destroyResource(CImageCopyCaptureFrame* resource) { + std::erase_if(m_frames, [&](const auto& other) { return other.get() == resource; }); +} diff --git a/src/protocols/ImageCopyCapture.hpp b/src/protocols/ImageCopyCapture.hpp new file mode 100644 index 00000000..b8cfa1e8 --- /dev/null +++ b/src/protocols/ImageCopyCapture.hpp @@ -0,0 +1,133 @@ +#pragma once + +#include + +#include "../defines.hpp" +#include "../helpers/signal/Signal.hpp" +#include "../helpers/Format.hpp" +#include "WaylandProtocol.hpp" +#include "ImageCaptureSource.hpp" +#include "ext-image-copy-capture-v1.hpp" + +class IHLBuffer; +class CWLPointerResource; +namespace Screenshare { + class CCursorshareSession; + class CScreenshareSession; + class CScreenshareFrame; +}; + +class CImageCopyCaptureFrame { + public: + CImageCopyCaptureFrame(SP resource, WP session); + ~CImageCopyCaptureFrame(); + + bool good(); + + private: + SP m_resource; + WP m_session; + UP m_frame; + + bool m_captured = false; + SP m_buffer; + CRegion m_clientDamage; + + friend class CImageCopyCaptureSession; +}; + +class CImageCopyCaptureSession { + public: + CImageCopyCaptureSession(SP resource, SP source, extImageCopyCaptureManagerV1Options options); + ~CImageCopyCaptureSession(); + + bool good(); + + private: + SP m_resource; + + SP m_source; + UP m_session; + WP m_frame; + + Vector2D m_bufferSize = Vector2D(0, 0); + bool m_paintCursor = true; + + struct { + CHyprSignalListener constraintsChanged; + CHyprSignalListener stopped; + } m_listeners; + + WP m_self; + + // + void sendConstraints(); + + friend class CImageCopyCaptureProtocol; + friend class CImageCopyCaptureFrame; +}; + +class CImageCopyCaptureCursorSession { + public: + CImageCopyCaptureCursorSession(SP resource, SP source, SP pointer); + ~CImageCopyCaptureCursorSession(); + + bool good(); + + private: + SP m_resource; + SP m_source; + SP m_pointer; + + // cursor session stuff + bool m_entered = false; + Vector2D m_pos = Vector2D(0, 0); + Vector2D m_hotspot = Vector2D(0, 0); + + // capture session stuff + SP m_sessionResource; + UP m_session; + Vector2D m_bufferSize = Vector2D(0, 0); + + // frame stuff + SP m_frameResource; + bool m_captured = false; + SP m_buffer; + + struct { + CHyprSignalListener constraintsChanged; + CHyprSignalListener stopped; + CHyprSignalListener commit; + } m_listeners; + + void sendCursorEvents(); + + void createFrame(SP resource); + void destroyCaptureSession(); + void sendConstraints(); +}; + +class CImageCopyCaptureProtocol : public IWaylandProtocol { + public: + CImageCopyCaptureProtocol(const wl_interface* iface, const int& ver, const std::string& name); + + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); + + void destroyResource(CExtImageCopyCaptureManagerV1* resource); + void destroyResource(CImageCopyCaptureSession* resource); + void destroyResource(CImageCopyCaptureCursorSession* resource); + void destroyResource(CImageCopyCaptureFrame* resource); + + private: + std::vector> m_managers; + std::vector> m_sessions; + std::vector> m_cursorSessions; + + std::vector> m_frames; + + friend class CImageCopyCaptureSession; +}; + +namespace PROTO { + inline UP imageCopyCapture; +}; diff --git a/src/protocols/InputMethodV2.cpp b/src/protocols/InputMethodV2.cpp index d66d1f24..0917d100 100644 --- a/src/protocols/InputMethodV2.cpp +++ b/src/protocols/InputMethodV2.cpp @@ -1,7 +1,8 @@ #include "InputMethodV2.hpp" -#include "../Compositor.hpp" +#include "../desktop/state/FocusState.hpp" #include "../managers/SeatManager.hpp" #include "../devices/IKeyboard.hpp" +#include "../helpers/MiscFunctions.hpp" #include #include "core/Compositor.hpp" #include @@ -14,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; } @@ -35,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; } @@ -96,8 +97,8 @@ CInputMethodPopupV2::CInputMethodPopupV2(SP resource_, m_listeners.destroySurface.reset(); m_listeners.commitSurface.reset(); - if (g_pCompositor->m_lastFocus == m_surface) - g_pCompositor->m_lastFocus.reset(); + if (Desktop::focusState()->surface() == m_surface) + Desktop::focusState()->surface().reset(); m_surface.reset(); }); @@ -193,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); @@ -210,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); }); @@ -366,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 4ee579ec..b948d609 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 15db1445..80222627 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" @@ -159,7 +159,7 @@ CLayerShellResource::CLayerShellResource(SP resource_, SPerror(ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_EXCLUSIVE_EDGE, "Exclusive edge doesn't align with anchor"); return; } @@ -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 4e90d870..f16c8c56 100644 --- a/src/protocols/LinuxDMABUF.cpp +++ b/src/protocols/LinuxDMABUF.cpp @@ -10,9 +10,9 @@ #include "core/Compositor.hpp" #include "types/DMABuffer.hpp" #include "types/WLBuffer.hpp" -#include "../managers/HookSystemManager.hpp" #include "../render/OpenGL.hpp" #include "../Compositor.hpp" +#include "../event/EventBus.hpp" using namespace Hyprutils::OS; @@ -26,6 +26,8 @@ static std::optional devIDFromFD(int fd) { CDMABUFFormatTable::CDMABUFFormatTable(SDMABUFTranche _rendererTranche, std::vector> tranches_) : m_rendererTranche(_rendererTranche), m_monitorTranches(tranches_) { + static const auto PSKIP_NON_KMS = CConfigValue("quirks:skip_non_kms_dmabuf_formats"); + std::vector formatsVec; std::set> formats; @@ -35,6 +37,17 @@ CDMABUFFormatTable::CDMABUFFormatTable(SDMABUFTranche _rendererTranche, std::vec m_rendererTranche.indices.clear(); for (auto const& fmt : m_rendererTranche.formats) { for (auto const& mod : fmt.modifiers) { + LOGM(Log::TRACE, "Render format 0x{:x} ({}) with mod 0x{:x} ({})", fmt.drmFormat, NFormatUtils::drmFormatName(fmt.drmFormat), mod, NFormatUtils::drmModifierName(mod)); + if (*PSKIP_NON_KMS && !m_monitorTranches.empty()) { + if (std::ranges::none_of(m_monitorTranches, [fmt, mod](const std::pair& pair) { + return std::ranges::any_of(pair.second.formats, [fmt, mod](const SDRMFormat& format) { + return format.drmFormat == fmt.drmFormat && std::ranges::any_of(format.modifiers, [mod](uint64_t modifier) { return mod == modifier; }); + }); + })) { + LOGM(Log::TRACE, " skipped"); + continue; + } + } auto format = std::make_pair<>(fmt.drmFormat, mod); auto [_, inserted] = formats.insert(format); if (inserted) { @@ -56,6 +69,9 @@ CDMABUFFormatTable::CDMABUFFormatTable(SDMABUFTranche _rendererTranche, std::vec tranche.indices.clear(); for (auto const& fmt : tranche.formats) { for (auto const& mod : fmt.modifiers) { + LOGM(Log::TRACE, "[DMA] Monitor format 0x{:x} ({}) with mod 0x{:x} ({})", fmt.drmFormat, NFormatUtils::drmFormatName(fmt.drmFormat), mod, + NFormatUtils::drmModifierName(mod)); + // FIXME: recheck this. DRM_FORMAT_MOD_INVALID is allowed by the proto "For legacy support". DRM_FORMAT_MOD_LINEAR should be the most compatible mod // apparently these can implode on planes, so don't use them if (mod == DRM_FORMAT_MOD_INVALID || mod == DRM_FORMAT_MOD_LINEAR) continue; @@ -83,7 +99,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 +121,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 +163,17 @@ CLinuxDMABUFParamsResource::CLinuxDMABUFParamsResource(UP(modHi) << 32) | modLo; + + if (m_resource->version() >= 5 && m_attrs->modifier && m_attrs->modifier != modifier) { + r->error(ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT, "planes have different modifiers"); + return; + } + m_attrs->fds[plane] = fd; m_attrs->strides[plane] = stride; m_attrs->offsets[plane] = offset; - m_attrs->modifier = (sc(modHi) << 32) | modLo; + m_attrs->modifier = modifier; }); m_resource->setCreate([this](CZwpLinuxBufferParamsV1* r, int32_t w, int32_t h, uint32_t fmt, zwpLinuxBufferParamsV1Flags flags) { @@ -161,7 +184,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 +210,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 +230,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 +267,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"); 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; } } @@ -382,8 +412,7 @@ CLinuxDMABUFResource::CLinuxDMABUFResource(UP&& resource_) : } }); - if (m_resource->version() < 4) - sendMods(); + sendMods(); } bool CLinuxDMABUFResource::good() { @@ -392,27 +421,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; } @@ -440,29 +467,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 +506,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 +519,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 +534,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 +543,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 +604,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 +618,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 +641,7 @@ void CLinuxDMABufV1Protocol::updateScanoutTranche(SP surface feedbackResource->m_lastFeedbackWasScanout = true; } + +dev_t CLinuxDMABufV1Protocol::getMainDevice() { + return m_mainDevice; +} diff --git a/src/protocols/LinuxDMABUF.hpp b/src/protocols/LinuxDMABUF.hpp index 296ef04d..b1d59155 100644 --- a/src/protocols/LinuxDMABUF.hpp +++ b/src/protocols/LinuxDMABUF.hpp @@ -113,6 +113,7 @@ class CLinuxDMABufV1Protocol : public IWaylandProtocol { virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); void updateScanoutTranche(SP surface, PHLMONITOR pMonitor); + dev_t getMainDevice(); private: void destroyResource(CLinuxDMABUFResource* resource); diff --git a/src/protocols/LockNotify.cpp b/src/protocols/LockNotify.cpp index 1855f891..46736ead 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 789f90b6..8a0b08b7 100644 --- a/src/protocols/MesaDRM.cpp +++ b/src/protocols/MesaDRM.cpp @@ -6,9 +6,9 @@ #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 +20,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() { @@ -116,7 +116,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 +124,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 6cb2f474..f85578e2 100644 --- a/src/protocols/OutputManagement.cpp +++ b/src/protocols/OutputManagement.cpp @@ -2,8 +2,8 @@ #include #include "../Compositor.hpp" #include "../managers/input/InputManager.hpp" -#include "../managers/HookSystemManager.hpp" #include "../config/ConfigManager.hpp" +#include "../event/EventBus.hpp" using namespace Aquamarine; @@ -11,14 +11,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())); @@ -28,6 +28,8 @@ COutputManager::COutputManager(SP resource_) : m_resource( PROTO::outputManagement->m_configurations.pop_back(); return; } + + RESOURCE->m_self = RESOURCE; }); // send all heads at start @@ -35,7 +37,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); } @@ -169,9 +171,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; } @@ -200,9 +202,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; } @@ -241,7 +243,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) @@ -269,14 +271,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; } @@ -291,25 +293,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)) @@ -335,7 +337,7 @@ COutputConfiguration::COutputConfiguration(SP resour const auto SUCCESS = applyTestConfiguration(false); if (SUCCESS) - m_resource->sendSucceeded(); + PROTO::outputManagement->m_pendingConfigurationSuccessEvents.emplace_back(m_self); else m_resource->sendFailed(); @@ -349,14 +351,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; } @@ -371,7 +373,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)) @@ -383,36 +385,36 @@ 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. @@ -423,7 +425,7 @@ bool COutputConfiguration::applyTestConfiguration(bool test) { m_owner->m_monitorStates[PMONITOR->m_name] = newState; } - LOGM(LOG, "Saved configuration"); + LOGM(Log::DEBUG, "Saved configuration"); return true; } @@ -438,12 +440,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; } @@ -455,12 +457,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; } @@ -475,19 +477,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; } @@ -499,12 +501,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; } @@ -521,12 +523,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; } @@ -545,12 +547,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; } @@ -567,7 +569,7 @@ COutputConfigurationHead::COutputConfigurationHead(SPm_name, as); + LOGM(Log::DEBUG, " | configHead for {}: set adaptiveSync to {}", m_monitor->m_name, as); }); } @@ -576,7 +578,10 @@ 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) { this->updateAllOutputs(); }); + static auto P = Event::bus()->m_events.monitor.layoutChanged.listen([this] { + updateAllOutputs(); + sendPendingSuccessEvents(); + }); } void COutputManagementProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { @@ -647,3 +652,19 @@ SP COutputManagementProtocol::getOutputStateFor(PHL return nullptr; } + +void COutputManagementProtocol::sendPendingSuccessEvents() { + if (m_pendingConfigurationSuccessEvents.empty()) + return; + + LOGM(Log::DEBUG, "Sending {} pending configuration success events", m_pendingConfigurationSuccessEvents.size()); + + for (auto const& config : m_pendingConfigurationSuccessEvents) { + if (!config) + continue; + + config->m_resource->sendSucceeded(); + } + + m_pendingConfigurationSuccessEvents.clear(); +} diff --git a/src/protocols/OutputManagement.hpp b/src/protocols/OutputManagement.hpp index cb03661e..f26e9c91 100644 --- a/src/protocols/OutputManagement.hpp +++ b/src/protocols/OutputManagement.hpp @@ -141,8 +141,12 @@ class COutputConfiguration { SP m_resource; std::vector> m_heads; WP m_owner; + WP m_self; bool applyTestConfiguration(bool test); + + friend class COutputManagementProtocol; + friend class COutputManager; }; class COutputManagementProtocol : public IWaylandProtocol { @@ -154,6 +158,8 @@ class COutputManagementProtocol : public IWaylandProtocol { // doesn't have to return one SP getOutputStateFor(PHLMONITOR pMonitor); + void sendPendingSuccessEvents(); + private: void destroyResource(COutputManager* resource); void destroyResource(COutputHead* resource); @@ -169,6 +175,7 @@ class COutputManagementProtocol : public IWaylandProtocol { std::vector> m_modes; std::vector> m_configurations; std::vector> m_configurationHeads; + std::vector> m_pendingConfigurationSuccessEvents; SP headFromResource(wl_resource* r); SP modeFromResource(wl_resource* r); diff --git a/src/protocols/PointerConstraints.cpp b/src/protocols/PointerConstraints.cpp index 24ccc980..a78f3548 100644 --- a/src/protocols/PointerConstraints.cpp +++ b/src/protocols/PointerConstraints.cpp @@ -1,6 +1,7 @@ #include "PointerConstraints.hpp" -#include "../desktop/WLSurface.hpp" -#include "../Compositor.hpp" +#include "../desktop/view/WLSurface.hpp" +#include "../desktop/state/FocusState.hpp" +#include "../desktop/view/Window.hpp" #include "../config/ConfigValue.hpp" #include "../managers/SeatManager.hpp" #include "core/Compositor.hpp" @@ -16,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; @@ -34,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; @@ -55,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; @@ -158,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(); } @@ -216,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; } @@ -232,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; @@ -242,7 +243,7 @@ void CPointerConstraintsProtocol::onNewConstraint(SP constra g_pInputManager->m_constraints.emplace_back(constraint); - if (g_pCompositor->m_lastFocus == OWNER->resource()) + if (Desktop::focusState()->surface() == OWNER->resource()) constraint->activate(); } diff --git a/src/protocols/PointerConstraints.hpp b/src/protocols/PointerConstraints.hpp index 1691b7c0..b190c041 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 00576778..eb14bbf8 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 bde0b913..a297a04d 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 10803406..456ad724 100644 --- a/src/protocols/PresentationTime.cpp +++ b/src/protocols/PresentationTime.cpp @@ -1,7 +1,7 @@ #include "PresentationTime.hpp" #include #include "../helpers/Monitor.hpp" -#include "../managers/HookSystemManager.hpp" +#include "../event/EventBus.hpp" #include "core/Compositor.hpp" #include "core/Output.hpp" #include @@ -40,46 +40,45 @@ bool CPresentationFeedback::good() { return m_resource->resource(); } -void CPresentationFeedback::sendQueued(WP data, const Time::steady_tp& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags) { +void CPresentationFeedback::sendQueued(WP data, const timespec& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags) { auto client = m_resource->client(); - if LIKELY (PROTO::outputs.contains(data->m_monitor->m_name)) { - if LIKELY (auto outputResource = PROTO::outputs.at(data->m_monitor->m_name)->outputResourceFrom(client); outputResource) - m_resource->sendSyncOutput(outputResource->getResource()->resource()); + if LIKELY (PROTO::outputs.contains(data->m_monitor->m_name) && data->m_wasPresented) { + if LIKELY (auto outputResources = PROTO::outputs.at(data->m_monitor->m_name)->outputResourcesFrom(client); !outputResources.empty()) { + for (const auto& r : outputResources) { + m_resource->sendSyncOutput(r->getResource()->resource()); + } + } } - 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; diff --git a/src/protocols/PresentationTime.hpp b/src/protocols/PresentationTime.hpp index c348c175..caf63ace 100644 --- a/src/protocols/PresentationTime.hpp +++ b/src/protocols/PresentationTime.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include "WaylandProtocol.hpp" @@ -37,7 +38,7 @@ class CPresentationFeedback { bool good(); - void sendQueued(WP data, const Time::steady_tp& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags); + void sendQueued(WP data, const timespec& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags); private: UP m_resource; @@ -53,7 +54,7 @@ class CPresentationProtocol : public IWaylandProtocol { virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); - void onPresented(PHLMONITOR pMonitor, const Time::steady_tp& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags); + void onPresented(PHLMONITOR pMonitor, const timespec& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags); void queueData(UP&& data); private: diff --git a/src/protocols/PrimarySelection.cpp b/src/protocols/PrimarySelection.cpp index dd0eefad..7da1fa0a 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/Screencopy.cpp b/src/protocols/Screencopy.cpp index 687c4045..825939ef 100644 --- a/src/protocols/Screencopy.cpp +++ b/src/protocols/Screencopy.cpp @@ -1,424 +1,12 @@ #include "Screencopy.hpp" -#include "../Compositor.hpp" -#include "../managers/eventLoop/EventLoopManager.hpp" -#include "../managers/PointerManager.hpp" -#include "../managers/input/InputManager.hpp" -#include "../managers/EventManager.hpp" -#include "../managers/permissions/DynamicPermissionManager.hpp" -#include "../render/Renderer.hpp" -#include "../render/OpenGL.hpp" -#include "../helpers/Monitor.hpp" +#include "../managers/screenshare/ScreenshareManager.hpp" #include "core/Output.hpp" -#include "types/WLBuffer.hpp" +#include "../render/Renderer.hpp" #include "types/Buffer.hpp" -#include "ColorManagement.hpp" #include "../helpers/Format.hpp" #include "../helpers/time/Time.hpp" -#include "XDGShell.hpp" -#include -#include - -CScreencopyFrame::CScreencopyFrame(SP resource_, int32_t overlay_cursor, wl_resource* output, CBox box_) : m_resource(resource_) { - if UNLIKELY (!good()) - return; - - m_overlayCursor = !!overlay_cursor; - m_monitor = CWLOutputResource::fromResource(output)->m_monitor; - - if (!m_monitor) { - LOGM(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(); - - for (auto const& l : g_pCompositor->m_layers) { - if (!l->m_noScreenShare) - 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, {}); - } - - for (auto const& w : g_pCompositor->m_windows) { - if (!w->m_windowData.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) || w->m_windowData.noRounding.valueOrDefault(); - 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( - [&](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); - }, - 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 Screenshare; CScreencopyClient::CScreencopyClient(SP resource_) : m_resource(resource_) { if UNLIKELY (!good()) @@ -427,58 +15,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 = NFormatUtils::getPixelFormatFromDRM(format); + + if (!PSHMINFO) { + LOGM(Log::ERR, "No pixel format for drm format"); + m_resource->sendFailed(); + return; + } + + const auto stride = NFormatUtils::minStride(PSHMINFO, bufSize.x); + m_resource->sendBuffer(NFormatUtils::drmToShm(format), bufSize.x, bufSize.y, stride); + + if (m_resource->version() >= 3) { + if LIKELY (format != DRM_FORMAT_INVALID) + m_resource->sendLinuxDmabuf(format, bufSize.x, bufSize.y); + + m_resource->sendBufferDone(); + } +} + +void CScreencopyFrame::shareFrame(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer, bool withDamage) { + if UNLIKELY (!good()) { + LOGM(Log::ERR, "No frame in shareFrame??"); + return; + } + + if UNLIKELY (m_session.expired() || !m_session->monitor()) { + LOGM(Log::ERR, "Session stopped for frame {:x}", (uintptr_t)this); + m_resource->sendFailed(); + return; + } + + if UNLIKELY (m_buffer) { + LOGM(Log::ERR, "Buffer used in {:x}", (uintptr_t)this); + m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_ALREADY_USED, "frame already used"); + m_resource->sendFailed(); + return; + } + + const auto PBUFFERRES = CWLBufferResource::fromResource(buffer); + if UNLIKELY (!PBUFFERRES || !PBUFFERRES->m_buffer) { + LOGM(Log::ERR, "Invalid buffer in {:x}", (uintptr_t)this); + m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); + m_resource->sendFailed(); + return; + } + + const auto& PBUFFER = PBUFFERRES->m_buffer.lock(); + + if (!withDamage) + g_pHyprRenderer->damageMonitor(m_session->monitor()); + + auto error = m_frame->share(PBUFFER, {}, [this, withDamage, self = m_self](eScreenshareResult result) { + if (self.expired() || !good()) + return; + switch (result) { + case RESULT_COPIED: { + m_resource->sendFlags(sc(0)); + if (withDamage) + m_frame->damage().forEachRect([&](const auto& rect) { m_resource->sendDamage(rect.x1, rect.y1, rect.x2 - rect.x1, rect.y2 - rect.y1); }); + + const auto [sec, nsec] = Time::secNsec(m_timestamp); + uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0; + uint32_t tvSecLo = sec & 0xFFFFFFFF; + m_resource->sendReady(tvSecHi, tvSecLo, nsec); + break; + } + case RESULT_NOT_COPIED: + LOGM(Log::ERR, "Frame share failed in {:x}", (uintptr_t)this); + m_resource->sendFailed(); + break; + case RESULT_TIMESTAMP: m_timestamp = Time::steadyNow(); break; + } + }); + + switch (error) { + case ERROR_NONE: m_buffer = CHLBufferReference(PBUFFER); break; + case ERROR_NO_BUFFER: m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); break; + case ERROR_BUFFER_SIZE: + case ERROR_BUFFER_FORMAT: m_resource->sendFailed(); break; + case ERROR_UNKNOWN: + case ERROR_STOPPED: m_resource->sendFailed(); break; + } +} + +bool CScreencopyFrame::good() { + return m_resource && m_resource->resource(); } CScreencopyProtocol::CScreencopyProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { @@ -489,7 +170,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; @@ -497,68 +178,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 54b0d28c..b73c090d 100644 --- a/src/protocols/Screencopy.hpp +++ b/src/protocols/Screencopy.hpp @@ -1,50 +1,32 @@ #pragma once -#include "../defines.hpp" -#include "./types/Buffer.hpp" -#include "wlr-screencopy-unstable-v1.hpp" #include "WaylandProtocol.hpp" +#include "wlr-screencopy-unstable-v1.hpp" -#include -#include -#include "../managers/HookSystemManager.hpp" -#include "../helpers/time/Timer.hpp" #include "../helpers/time/Time.hpp" -#include "../render/Framebuffer.hpp" -#include "../managers/eventLoop/EventLoopTimer.hpp" +#include "./types/Buffer.hpp" #include +#include + class CMonitor; class IHLBuffer; - -enum eClientOwners { - CLIENT_SCREENCOPY = 0, - CLIENT_TOPLEVEL_EXPORT +namespace Screenshare { + class CScreenshareSession; + class CScreenshareFrame; }; class CScreencopyClient { public: CScreencopyClient(SP resource_); - ~CScreencopyClient(); - bool good(); - wl_client* client(); - - WP m_self; - eClientOwners m_clientOwner = CLIENT_SCREENCOPY; - - CTimer m_lastFrame; - int m_frameCounter = 0; + bool good(); private: SP m_resource; + WP m_self; - int m_framesInLastHalfSecond = 0; - CTimer m_lastMeasure; - bool m_sentScreencast = false; - - SP m_tickCallback; - void onTick(); + wl_client* m_savedClient = nullptr; void captureOutput(uint32_t frame, int32_t overlayCursor, wl_resource* output, CBox box); @@ -53,38 +35,27 @@ class CScreencopyClient { class CScreencopyFrame { public: - CScreencopyFrame(SP resource, int32_t overlay_cursor, wl_resource* output, CBox box); + CScreencopyFrame(SP resource, WP session, bool overlayCursor); - bool good(); - - WP m_self; - WP m_client; + bool good(); private: - SP m_resource; + SP m_resource; + WP m_self; + WP m_client; - PHLMONITORREF m_monitor; - bool m_overlayCursor = false; - bool m_withDamage = false; + WP m_session; + UP m_frame; - CHLBufferReference m_buffer; - bool m_bufferDMA = false; - uint32_t m_shmFormat = 0; - uint32_t m_dmabufFormat = 0; - int m_shmStride = 0; - CBox m_box = {}; + CHLBufferReference m_buffer; + Time::steady_tp m_timestamp; + bool m_overlayCursor = true; - // if we have a pending perm, hold the buffer. - CFramebuffer m_tempFb; - - void copy(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer); - void copyDmabuf(std::function callback); - bool copyShm(); - void renderMon(); - void storeTempFB(); - void share(); + // + void shareFrame(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer, bool withDamage); friend class CScreencopyProtocol; + friend class CScreencopyClient; }; class CScreencopyProtocol : public IWaylandProtocol { @@ -95,21 +66,10 @@ class CScreencopyProtocol : public IWaylandProtocol { void destroyResource(CScreencopyClient* resource); void destroyResource(CScreencopyFrame* resource); - void onOutputCommit(PHLMONITOR pMonitor); - private: std::vector> m_frames; - std::vector> m_framesAwaitingWrite; std::vector> m_clients; - void shareAllFrames(PHLMONITOR pMonitor); - void shareFrame(CScreencopyFrame* frame); - void sendFrameDamage(CScreencopyFrame* frame); - bool copyFrameDmabuf(CScreencopyFrame* frame); - bool copyFrameShm(CScreencopyFrame* frame, const Time::steady_tp& now); - - uint32_t drmFormatForMonitor(PHLMONITOR pMonitor); - friend class CScreencopyFrame; friend class CScreencopyClient; }; diff --git a/src/protocols/SecurityContext.cpp b/src/protocols/SecurityContext.cpp index 00f7b4d8..6c7f8226 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 6ff9171e..88231f38 100644 --- a/src/protocols/SessionLock.cpp +++ b/src/protocols/SessionLock.cpp @@ -1,5 +1,4 @@ #include "SessionLock.hpp" -#include "../Compositor.hpp" #include "../managers/SeatManager.hpp" #include "FractionalScale.hpp" #include "LockNotify.hpp" @@ -7,6 +6,7 @@ #include "core/Output.hpp" #include "../helpers/Monitor.hpp" #include "../render/Renderer.hpp" +#include "../desktop/state/FocusState.hpp" CSessionLockSurface::CSessionLockSurface(SP resource_, SP surface_, PHLMONITOR pMonitor_, WP owner_) : m_resource(resource_), m_sessionLock(owner_), m_surface(surface_), m_monitor(pMonitor_) { @@ -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,19 +47,23 @@ 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(); - if (g_pCompositor->m_lastFocus == m_surface) - g_pCompositor->m_lastFocus.reset(); + if (Desktop::focusState()->surface() == m_surface) + Desktop::focusState()->surface().reset(); m_surface.reset(); }); - if (m_monitor) + if (m_monitor) { PROTO::fractional->sendScale(surface_, m_monitor->m_scale); + if (m_surface) + m_surface->enter(m_monitor.lock()); + } + sendConfigure(); m_listeners.monitorMode = m_monitor->m_events.modeChanged.listen([this] { sendConfigure(); }); @@ -75,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; } @@ -108,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; } @@ -180,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))); @@ -197,7 +201,7 @@ 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(); diff --git a/src/protocols/ShortcutsInhibit.cpp b/src/protocols/ShortcutsInhibit.cpp index b33db998..6e6bf002 100644 --- a/src/protocols/ShortcutsInhibit.cpp +++ b/src/protocols/ShortcutsInhibit.cpp @@ -1,6 +1,7 @@ #include "ShortcutsInhibit.hpp" #include #include "../Compositor.hpp" +#include "../desktop/state/FocusState.hpp" #include "core/Compositor.hpp" CKeyboardShortcutsInhibitor::CKeyboardShortcutsInhibitor(SP resource_, SP surf) : m_resource(resource_), m_surface(surf) { @@ -61,20 +62,20 @@ 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; } } bool CKeyboardShortcutsInhibitProtocol::isInhibited() { - if (!g_pCompositor->m_lastFocus) + if (!Desktop::focusState()->surface()) return false; - if (const auto PWINDOW = g_pCompositor->getWindowFromSurface(g_pCompositor->m_lastFocus.lock()); PWINDOW && PWINDOW->m_windowData.noShortcutsInhibit.valueOrDefault()) + if (const auto PWINDOW = g_pCompositor->getWindowFromSurface(Desktop::focusState()->surface()); PWINDOW && PWINDOW->m_ruleApplicator->noShortcutsInhibit().valueOrDefault()) return false; for (auto const& in : m_inhibitors) { - if (in->surface() != g_pCompositor->m_lastFocus) + if (in->surface() != Desktop::focusState()->surface()) continue; return true; diff --git a/src/protocols/SinglePixel.cpp b/src/protocols/SinglePixel.cpp index 6ca48a35..c32379a3 100644 --- a/src/protocols/SinglePixel.cpp +++ b/src/protocols/SinglePixel.cpp @@ -1,10 +1,10 @@ #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(); @@ -12,16 +12,16 @@ CSinglePixelBuffer::CSinglePixelBuffer(uint32_t id, wl_client* client, CHyprColo m_opaque = col_.a >= 1.F; - m_texture = makeShared(DRM_FORMAT_ARGB8888, rc(&m_color), 4, Vector2D{1, 1}); + m_texture = g_pHyprRenderer->createTexture(DRM_FORMAT_ARGB8888, rc(&m_color), 4, Vector2D{1, 1}); m_resource = CWLBufferResource::create(makeShared(client, 1, id)); - m_success = m_texture->m_texID; + m_success = m_texture->ok(); size = {1, 1}; 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() { diff --git a/src/protocols/Tablet.cpp b/src/protocols/Tablet.cpp index b4f3b3eb..00f811a4 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 ee65cc9e..3fd346a7 100644 --- a/src/protocols/TearingControl.cpp +++ b/src/protocols/TearingControl.cpp @@ -1,13 +1,12 @@ #include "TearingControl.hpp" #include "../managers/ProtocolManager.hpp" -#include "../desktop/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 b58ec8e3..ec31b6f6 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 7143b081..d77bb736 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 8a5ee478..595467c4 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 eb0a39aa..d7ba7519 100644 --- a/src/protocols/ToplevelExport.cpp +++ b/src/protocols/ToplevelExport.cpp @@ -1,19 +1,14 @@ #include "ToplevelExport.hpp" #include "../Compositor.hpp" #include "ForeignToplevelWlr.hpp" -#include "../managers/PointerManager.hpp" -#include "../managers/SeatManager.hpp" -#include "types/WLBuffer.hpp" -#include "types/Buffer.hpp" +#include "../managers/screenshare/ScreenshareManager.hpp" #include "../helpers/Format.hpp" -#include "../managers/EventManager.hpp" -#include "../managers/input/InputManager.hpp" -#include "../managers/permissions/DynamicPermissionManager.hpp" #include "../render/Renderer.hpp" -#include #include +using namespace Screenshare; + CToplevelExportClient::CToplevelExportClient(SP resource_) : m_resource(resource_) { if UNLIKELY (!good()) return; @@ -21,364 +16,135 @@ CToplevelExportClient::CToplevelExportClient(SPsetOnDestroy([this](CHyprlandToplevelExportManagerV1* pMgr) { PROTO::toplevelExport->destroyResource(this); }); m_resource->setDestroy([this](CHyprlandToplevelExportManagerV1* pMgr) { PROTO::toplevelExport->destroyResource(this); }); m_resource->setCaptureToplevel([this](CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, uint32_t handle) { - this->captureToplevel(pMgr, frame, overlayCursor, g_pCompositor->getWindowFromHandle(handle)); + captureToplevel(frame, overlayCursor, g_pCompositor->getWindowFromHandle(handle)); }); m_resource->setCaptureToplevelWithWlrToplevelHandle([this](CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* handle) { - this->captureToplevel(pMgr, frame, overlayCursor, PROTO::foreignToplevelWlr->windowFromHandleResource(handle)); + captureToplevel(frame, overlayCursor, PROTO::foreignToplevelWlr->windowFromHandleResource(handle)); }); - m_lastMeasure.reset(); - m_lastFrame.reset(); - m_tickCallback = g_pHookSystem->hookDynamic("tick", [&](void* self, SCallbackInfo& info, std::any data) { onTick(); }); + m_savedClient = m_resource->client(); } -void CToplevelExportClient::captureToplevel(CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor_, PHLWINDOW handle) { +void CToplevelExportClient::captureToplevel(uint32_t frame, int32_t overlayCursor_, PHLWINDOW handle) { + auto session = Screenshare::mgr()->getManagedSession(m_resource->client(), handle); + // create a frame const auto FRAME = PROTO::toplevelExport->m_frames.emplace_back( - makeShared(makeShared(m_resource->client(), m_resource->version(), frame), overlayCursor_, handle)); + makeShared(makeShared(m_resource->client(), m_resource->version(), frame), session, !!overlayCursor_)); if UNLIKELY (!FRAME->good()) { - LOGM(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 = NFormatUtils::getPixelFormatFromDRM(format); + const auto stride = NFormatUtils::minStride(PSHMINFO, bufSize.x); + m_resource->sendBuffer(NFormatUtils::drmToShm(format), bufSize.x, bufSize.y, stride); - m_box = {0, 0, sc(m_window->m_realSize->value().x * PMONITOR->m_scale), sc(m_window->m_realSize->value().y * PMONITOR->m_scale)}; - - m_box.transform(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_windowData.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_windowData.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 +155,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 +163,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 8ccd881b..5d30f09b 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 4cc822f0..823956df 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 2acc2298..2f7e0bd1 100644 --- a/src/protocols/VirtualKeyboard.cpp +++ b/src/protocols/VirtualKeyboard.cpp @@ -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; @@ -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 9e258b7a..075f7cb9 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 65099248..4cb4f991 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 d46d6aaf..5c187c7d 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 f25ffca8..3d094fca 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 88456473..b53621d5 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 0339db6b..e711ab3b 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 c38a1077..ad6b3eeb 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; - g_pCompositor->updateWindowAnimatedDecorationValues(HLSurface->getWindow()); + 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/XDGOutput.cpp b/src/protocols/XDGOutput.cpp index ccb78e98..3553a74b 100644 --- a/src/protocols/XDGOutput.cpp +++ b/src/protocols/XDGOutput.cpp @@ -2,7 +2,7 @@ #include "../config/ConfigValue.hpp" #include "../helpers/Monitor.hpp" #include "../xwayland/XWayland.hpp" -#include "../managers/HookSystemManager.hpp" +#include "../event/EventBus.hpp" #include "core/Output.hpp" #define OUTPUT_MANAGER_VERSION 3 @@ -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 58f297b9..969556a3 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; @@ -254,10 +257,14 @@ CXDGToplevelResource::CXDGToplevelResource(SP resource_, SPm_children.emplace_back(m_self); - LOGM(LOG, "Toplevel {:x} sets parent to {:x}{}", (uintptr_t)this, (uintptr_t)newp.get(), (oldParent ? std::format(" (was {:x})", (uintptr_t)oldParent.get()) : "")); + 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)newp.get(), (oldParent ? std::format(" (was {:x})", (uintptr_t)oldParent.get()) : "")); }); } @@ -402,7 +409,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 +465,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 +494,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 +512,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 +606,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 +752,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 +783,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 +827,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/XDGTag.cpp b/src/protocols/XDGTag.cpp index 2966ac90..84415d71 100644 --- a/src/protocols/XDGTag.cpp +++ b/src/protocols/XDGTag.cpp @@ -1,5 +1,6 @@ #include "XDGTag.hpp" #include "XDGShell.hpp" +#include "../desktop/view/Window.hpp" CXDGToplevelTagManagerResource::CXDGToplevelTagManagerResource(UP&& resource) : m_resource(std::move(resource)) { if UNLIKELY (!good()) @@ -17,6 +18,8 @@ CXDGToplevelTagManagerResource::CXDGToplevelTagManagerResource(UPm_toplevelTag = tag; + if (TOPLEVEL->m_window) + TOPLEVEL->m_window->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_XDG_TAG); }); m_resource->setSetToplevelDescription([](CXdgToplevelTagManagerV1* r, wl_resource* toplevel, const char* description) { diff --git a/src/protocols/XXColorManagement.cpp b/src/protocols/XXColorManagement.cpp deleted file mode 100644 index 8ec780b4..00000000 --- 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 0407730a..00000000 --- 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 06a4587f..06f430b9 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); } @@ -356,7 +364,7 @@ void CWLSurfaceResource::bfHelper(std::vector> const& nod break; if (c->m_surface.expired()) continue; - nodes2.push_back(c->m_surface.lock()); + nodes2.emplace_back(c->m_surface.lock()); } } @@ -381,7 +389,7 @@ void CWLSurfaceResource::bfHelper(std::vector> const& nod continue; if (c->m_surface.expired()) continue; - nodes2.push_back(c->m_surface.lock()); + nodes2.emplace_back(c->m_surface.lock()); } } @@ -391,7 +399,7 @@ 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); } @@ -500,20 +508,32 @@ void CWLSurfaceResource::scheduleState(WP state) { if (state->updated.bits.acquire) { // wait on acquire point for this surface, from explicit sync protocol - state->acquire.addWaiter([state, whenReadable]() { whenReadable(state, LOCK_REASON_FENCE); }); + if (!state->acquire.addWaiter([state, whenReadable]() { whenReadable(state, LOCK_REASON_FENCE); })) { + Log::logger->log(Log::ERR, "Failed to addWaiter in CWLSurfaceResource::scheduleState"); + whenReadable(state, LOCK_REASON_FENCE); + } } else if (state->buffer && state->buffer->isSynchronous()) { // synchronous (shm) buffers can be read immediately - m_stateQueue.unlock(state); + m_stateQueue.unlock(state, LOCK_REASON_FENCE); } else if (state->buffer && state->buffer->m_syncFd.isValid()) { // async buffer and is dmabuf, then we can wait on implicit fences g_pEventLoopManager->doOnReadable(std::move(state->buffer->m_syncFd), [state, whenReadable]() { whenReadable(state, LOCK_REASON_FENCE); }); } else { // state commit without a buffer. - m_stateQueue.unlock(state); + m_stateQueue.tryProcess(); } } void CWLSurfaceResource::commitState(SSurfaceState& state) { + // TODO might be incorrect. needed for VRR with FIFO to avoid same buffer extra frames for second commit when it's used in this way: + // wp_fifo_v1#43.set_barrier() + // wp_fifo_v1#43.wait_barrier() + // wl_surface#3.commit() + // wp_fifo_v1#43.wait_barrier() + // wl_surface#3.commit() + if (!state.updated.all && m_mapped && state.fifoScheduled) + return; + auto lastTexture = m_current.texture; m_current.updateFrom(state); @@ -528,7 +548,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(); @@ -550,20 +570,19 @@ void CWLSurfaceResource::commitState(SSurfaceState& state) { nullptr); } - if (m_current.updated.bits.damage) { - // damage is always relative to the current commit - m_current.updated.bits.damage = false; - m_current.damage.clear(); - m_current.bufferDamage.clear(); - } - // release the buffer if it's synchronous (SHM) as updateSynchronousTexture() has copied the buffer data to a GPU tex // if it doesn't have a role, we can't release it yet, in case it gets turned into a cursor. if (m_current.buffer && m_current.buffer->isSynchronous() && m_role->role() != SURFACE_ROLE_UNASSIGNED) 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(); @@ -572,8 +591,8 @@ 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(); } @@ -602,6 +621,43 @@ void CWLSurfaceResource::sortSubsurfaces() { } } +bool CWLSurfaceResource::hasVisibleSubsurface() { + for (auto const& subsurface : m_subsurfaces) { + if (!subsurface || !subsurface->m_surface) + continue; + + const auto& surf = subsurface->m_surface; + if (surf->m_current.size.x > 0 && surf->m_current.size.y > 0) + return true; + } + + 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; @@ -615,7 +671,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; } @@ -646,8 +702,14 @@ void CWLSurfaceResource::presentFeedback(const Time::steady_tp& when, PHLMONITOR FEEDBACK->attachMonitor(pMonitor); if (discarded) FEEDBACK->discarded(); - else + else { FEEDBACK->presented(); + if (!pMonitor->m_lastScanout.expired()) { + const auto WINDOW = m_hlSurface ? Desktop::View::CWindow::fromView(m_hlSurface->view()) : nullptr; + if (WINDOW == pMonitor->m_lastScanout) + FEEDBACK->setPresentationType(true); + } + } PROTO::presentation->queueData(std::move(FEEDBACK)); } @@ -669,7 +731,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); }); @@ -685,7 +747,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()); }); } diff --git a/src/protocols/core/Compositor.hpp b/src/protocols/core/Compositor.hpp index c024a3eb..37ca51b7 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,8 +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 b3239cf1..22ccae6c 100644 --- a/src/protocols/core/DataDevice.cpp +++ b/src/protocols/core/DataDevice.cpp @@ -10,11 +10,11 @@ #include "../../xwayland/XWayland.hpp" #include "../../xwayland/Server.hpp" #include "../../managers/input/InputManager.hpp" -#include "../../managers/HookSystemManager.hpp" #include "../../managers/cursor/CursorShapeOverrideController.hpp" #include "../../helpers/Monitor.hpp" #include "../../render/Renderer.hpp" #include "../../xwayland/Dnd.hpp" +#include "../../event/EventBus.hpp" using namespace Hyprutils::OS; CWLDataOfferResource::CWLDataOfferResource(SP resource_, SP source_) : m_source(source_), m_resource(resource_) { @@ -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); } }); @@ -682,11 +685,11 @@ 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); diff --git a/src/protocols/core/DataDevice.hpp b/src/protocols/core/DataDevice.hpp index 8b52933e..f3717f78 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 d0f057e3..dd9c3166 100644 --- a/src/protocols/core/Output.cpp +++ b/src/protocols/core/Output.cpp @@ -31,7 +31,7 @@ CWLOutputResource::CWLOutputResource(SP resource_, PHLMONITOR pMonito updateState(); PROTO::compositor->forEachSurface([](SP surf) { - auto HLSurf = CWLSurface::fromResource(surf); + auto HLSurf = Desktop::View::CWLSurface::fromResource(surf); if (!HLSurf) return; @@ -95,7 +95,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 +117,17 @@ void CWLOutputProtocol::destroyResource(CWLOutputResource* resource) { PROTO::outputs.erase(m_name); } -SP CWLOutputProtocol::outputResourceFrom(wl_client* client) { +std::vector> CWLOutputProtocol::outputResourcesFrom(wl_client* client) { + std::vector> ret; + for (auto const& r : m_outputs) { if (r->client() != client) continue; - return r; + ret.emplace_back(r); } - return nullptr; + return ret; } void CWLOutputProtocol::remove() { diff --git a/src/protocols/core/Output.hpp b/src/protocols/core/Output.hpp index d0c6fb13..cf266685 100644 --- a/src/protocols/core/Output.hpp +++ b/src/protocols/core/Output.hpp @@ -34,13 +34,13 @@ class CWLOutputProtocol : public IWaylandProtocol { public: CWLOutputProtocol(const wl_interface* iface, const int& ver, const std::string& name, PHLMONITOR pMonitor); - virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); - SP outputResourceFrom(wl_client* client); - void sendDone(); + std::vector> outputResourcesFrom(wl_client* client); + void sendDone(); - PHLMONITORREF m_monitor; - WP m_self; + PHLMONITORREF m_monitor; + WP m_self; // will mark the protocol for removal, will be removed when no. of bound outputs is 0 (or when overwritten by a new global) void remove(); diff --git a/src/protocols/core/Seat.cpp b/src/protocols/core/Seat.cpp index 74e5615e..52015fd8 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; } @@ -141,6 +141,10 @@ CWLPointerResource::CWLPointerResource(SP resource_, SPm_state.pointerFocus.lock(), {-1, -1} /* Coords don't really matter that much, they will be updated next move */); } +CWLPointerResource::~CWLPointerResource() { + m_events.destroyed.emit(); +} + int CWLPointerResource::version() { return m_resource->version(); } @@ -162,7 +166,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 +222,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 +332,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 +384,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 +535,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 c30bbd71..85dc5c39 100644 --- a/src/protocols/core/Seat.hpp +++ b/src/protocols/core/Seat.hpp @@ -71,6 +71,7 @@ class CWLTouchResource { class CWLPointerResource { public: CWLPointerResource(SP resource_, SP owner_); + ~CWLPointerResource(); bool good(); int version(); @@ -88,6 +89,10 @@ class CWLPointerResource { WP m_owner; + struct { + CSignalT<> destroyed; + } m_events; + // static SP fromResource(wl_resource* res); diff --git a/src/protocols/core/Shm.cpp b/src/protocols/core/Shm.cpp index 43c087bc..7cca3814 100644 --- a/src/protocols/core/Shm.cpp +++ b/src/protocols/core/Shm.cpp @@ -87,7 +87,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 +96,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 +176,7 @@ CWLSHMResource::CWLSHMResource(UP&& resource_) : m_resource(std::move(re if UNLIKELY (!good()) return; + m_resource->setRelease([this](CWlShm* r) { PROTO::shm->destroyResource(this); }); m_resource->setOnDestroy([this](CWlShm* r) { PROTO::shm->destroyResource(this); }); m_resource->setCreatePool([](CWlShm* r, uint32_t id, int32_t fd, int32_t size) { diff --git a/src/protocols/core/Subcompositor.cpp b/src/protocols/core/Subcompositor.cpp index c198052c..14dc4666 100644 --- a/src/protocols/core/Subcompositor.cpp +++ b/src/protocols/core/Subcompositor.cpp @@ -186,7 +186,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/types/Buffer.cpp b/src/protocols/types/Buffer.cpp index 86b37be0..93bd5d20 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 f85670ef..afff11a5 100644 --- a/src/protocols/types/Buffer.hpp +++ b/src/protocols/types/Buffer.hpp @@ -26,7 +26,7 @@ class IHLBuffer : public Aquamarine::IBuffer { void onBackendRelease(const std::function& fn); void addReleasePoint(CDRMSyncPointState& point); - SP m_texture; + SP m_texture; bool m_opaque = false; SP m_resource; std::vector> m_syncReleasers; @@ -49,10 +49,13 @@ class CHLBufferReference { public: CHLBufferReference(); CHLBufferReference(const CHLBufferReference& other); + CHLBufferReference(CHLBufferReference&& other) noexcept; CHLBufferReference(SP buffer); ~CHLBufferReference(); CHLBufferReference& operator=(const CHLBufferReference& other); + CHLBufferReference& operator=(CHLBufferReference&& other); + bool operator==(const CHLBufferReference& other) const; bool operator==(const SP& other) const; bool operator==(const SP& other) const; diff --git a/src/protocols/types/ColorManagement.cpp b/src/protocols/types/ColorManagement.cpp deleted file mode 100644 index e6b470d0..00000000 --- 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/ContentType.cpp b/src/protocols/types/ContentType.cpp index c0a3d30f..b5b0041c 100644 --- a/src/protocols/types/ContentType.cpp +++ b/src/protocols/types/ContentType.cpp @@ -12,7 +12,15 @@ namespace NContentType { if (it != table.end()) return it->second; else - throw std::invalid_argument(std::format("Unknown content type {}", name)); + return CONTENT_TYPE_NONE; + } + + std::string toString(eContentType type) { + for (const auto& [k, v] : table) { + if (v == type) + return k; + } + return ""; } eContentType fromWP(wpContentTypeV1Type contentType) { diff --git a/src/protocols/types/ContentType.hpp b/src/protocols/types/ContentType.hpp index 66fcbca7..68bc7a41 100644 --- a/src/protocols/types/ContentType.hpp +++ b/src/protocols/types/ContentType.hpp @@ -13,6 +13,7 @@ namespace NContentType { }; eContentType fromString(const std::string name); + std::string toString(eContentType); eContentType fromWP(wpContentTypeV1Type contentType); uint16_t toDRM(eContentType contentType); } \ No newline at end of file diff --git a/src/protocols/types/DMABuffer.cpp b/src/protocols/types/DMABuffer.cpp index ab8bc86d..86db8ca6 100644 --- a/src/protocols/types/DMABuffer.cpp +++ b/src/protocols/types/DMABuffer.cpp @@ -1,46 +1,43 @@ #include "DMABuffer.hpp" #include "WLBuffer.hpp" -#include "../../desktop/LayerSurface.hpp" +#include "../../desktop/view/LayerSurface.hpp" #include "../../render/Renderer.hpp" #include "../../helpers/Format.hpp" #if defined(__linux__) #include #include -#include #endif +#include using namespace Hyprutils::OS; CDMABuffer::CDMABuffer(uint32_t id, wl_client* client, Aquamarine::SDMABUFAttrs const& attrs_) : m_attrs(attrs_) { - g_pHyprRenderer->makeEGLCurrent(); - m_listeners.resourceDestroy = events.destroy.listen([this] { closeFDs(); m_listeners.resourceDestroy.reset(); }); - size = m_attrs.size; - m_resource = CWLBufferResource::create(makeShared(client, 1, id)); - auto eglImage = g_pHyprOpenGL->createEGLImage(m_attrs); + size = m_attrs.size; + m_resource = CWLBufferResource::create(makeShared(client, 1, id)); + m_opaque = NFormatUtils::isFormatOpaque(m_attrs.format); + m_texture = g_pHyprRenderer->createTexture(m_attrs, m_opaque); // texture takes ownership of the eglImage - if UNLIKELY (!eglImage) { - 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 17437fbc..da98d3fb 100644 --- a/src/protocols/types/SurfaceState.cpp +++ b/src/protocols/types/SurfaceState.cpp @@ -1,6 +1,7 @@ #include "SurfaceState.hpp" #include "helpers/Format.hpp" #include "protocols/types/Buffer.hpp" +#include "render/Renderer.hpp" #include "render/Texture.hpp" Vector2D SSurfaceState::sourceSize() { @@ -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,13 +64,17 @@ 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, bool merge) { - if (merge) - updated.all |= ref.updated.all; - else - updated = ref.updated; +void SSurfaceState::updateFrom(SSurfaceState& ref) { + updated = ref.updated; if (ref.updated.bits.buffer) { buffer = ref.buffer; @@ -81,6 +86,10 @@ void SSurfaceState::updateFrom(SSurfaceState& ref, bool merge) { if (ref.updated.bits.damage) { damage = ref.damage; bufferDamage = ref.bufferDamage; + } else { + // damage is always relative to the current commit + damage.clear(); + bufferDamage.clear(); } if (ref.updated.bits.input) @@ -111,4 +120,7 @@ void SSurfaceState::updateFrom(SSurfaceState& ref, bool merge) { 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 d41b3d3b..d5b7e4b9 100644 --- a/src/protocols/types/SurfaceState.hpp +++ b/src/protocols/types/SurfaceState.hpp @@ -1,10 +1,12 @@ #pragma once #include "../../helpers/math/Math.hpp" +#include "../../helpers/time/Time.hpp" +#include "../../managers/eventLoop/EventLoopTimer.hpp" #include "../WaylandProtocol.hpp" #include "./Buffer.hpp" -class CTexture; +class ITexture; class CDRMSyncPointState; class CWLCallbackResource; @@ -48,6 +50,7 @@ struct SSurfaceState { bool acquire : 1; bool acked : 1; bool frame : 1; + bool fifo : 1; } bits; } updated; @@ -85,11 +88,20 @@ 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 - void updateFrom(SSurfaceState& ref, bool merge = false); // updates this state based on a reference state. - void reset(); // resets pending state after commit + CRegion accumulateBufferDamage(); // transforms state.damage and merges it into state.bufferDamage + void updateFrom(SSurfaceState& ref); // updates this state based on a reference state. + void reset(); // resets pending state after commit }; diff --git a/src/protocols/types/SurfaceStateQueue.cpp b/src/protocols/types/SurfaceStateQueue.cpp index 3408d61c..82a04878 100644 --- a/src/protocols/types/SurfaceStateQueue.cpp +++ b/src/protocols/types/SurfaceStateQueue.cpp @@ -21,6 +21,7 @@ void CSurfaceStateQueue::dropState(const WP& state) { } void CSurfaceStateQueue::lock(const WP& weakState, eLockReason reason) { + ASSERT(reason != LOCK_REASON_NONE); auto it = find(weakState); if (it == m_queue.end()) return; @@ -29,6 +30,7 @@ void CSurfaceStateQueue::lock(const WP& weakState, eLockReason re } void CSurfaceStateQueue::unlock(const WP& state, eLockReason reason) { + ASSERT(reason != LOCK_REASON_NONE); auto it = find(state); if (it == m_queue.end()) return; @@ -38,6 +40,7 @@ void CSurfaceStateQueue::unlock(const WP& state, eLockReason reas } void CSurfaceStateQueue::unlockFirst(eLockReason reason) { + ASSERT(reason != LOCK_REASON_NONE); for (auto& it : m_queue) { if ((it->lockMask & reason) != LOCK_REASON_NONE) { it->lockMask &= ~reason; @@ -63,28 +66,15 @@ auto CSurfaceStateQueue::find(const WP& state) -> std::dequelockMask & LOCK_REASON_FIFO && !m_surface->m_current.barrierSet) + front->lockMask &= ~LOCK_REASON_FIFO; - auto front = m_queue.begin(); - if (front->get()->lockMask != LOCK_REASON_NONE) - return; + if (front->lockMask != LOCK_REASON_NONE) + return; - auto next = std::next(front); - if (next == m_queue.end()) { - m_surface->commitState(**front); + m_surface->commitState(*front); m_queue.pop_front(); - return; } - - while (!m_queue.empty() && next != m_queue.end() && next->get()->lockMask == LOCK_REASON_NONE && !next->get()->updated.bits.buffer) { - next->get()->updateFrom(**front, true); - m_queue.pop_front(); - - front = m_queue.begin(); - next = std::next(front); - } - - m_surface->commitState(**front); - m_queue.pop_front(); } diff --git a/src/protocols/types/SurfaceStateQueue.hpp b/src/protocols/types/SurfaceStateQueue.hpp index 1841ed20..8d9db7a5 100644 --- a/src/protocols/types/SurfaceStateQueue.hpp +++ b/src/protocols/types/SurfaceStateQueue.hpp @@ -15,7 +15,7 @@ class CSurfaceStateQueue { WP enqueue(UP&& state); void dropState(const WP& state); void lock(const WP& state, eLockReason reason); - void unlock(const WP& state, eLockReason reason = LOCK_REASON_NONE); + void unlock(const WP& state, eLockReason reason); void unlockFirst(eLockReason reason); void tryProcess(); diff --git a/src/render/Framebuffer.cpp b/src/render/Framebuffer.cpp index 070bcc1b..b2ff7e68 100644 --- a/src/render/Framebuffer.cpp +++ b/src/render/Framebuffer.cpp @@ -1,127 +1,30 @@ #include "Framebuffer.hpp" -#include "OpenGL.hpp" -CFramebuffer::CFramebuffer() { - ; -} +IFramebuffer::IFramebuffer(const std::string& name) : m_name(name) {} -bool CFramebuffer::alloc(int w, int h, uint32_t drmFormat) { - bool firstAlloc = false; +bool IFramebuffer::alloc(int w, int h, uint32_t format) { RASSERT((w > 0 && h > 0), "cannot alloc a FB with negative / zero size! (attempted {}x{})", w, h); - uint32_t glFormat = NFormatUtils::drmFormatToGL(drmFormat); - uint32_t glType = NFormatUtils::glFormatToType(glFormat); + const bool sizeChanged = (m_size != Vector2D(w, h)); + const bool formatChanged = (format != m_drmFormat); - if (drmFormat != m_drmFormat || m_size != Vector2D{w, h}) - release(); + if (m_fbAllocated && !sizeChanged && !formatChanged) + return true; - m_drmFormat = drmFormat; - - if (!m_tex) { - m_tex = makeShared(); - m_tex->allocate(); - m_tex->bind(); - m_tex->setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - m_tex->setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - m_tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); - m_tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); - firstAlloc = true; - } - - if (!m_fbAllocated) { - glGenFramebuffers(1, &m_fb); - m_fbAllocated = true; - firstAlloc = true; - } - - if (firstAlloc || m_size != Vector2D(w, h)) { - m_tex->bind(); - glTexImage2D(GL_TEXTURE_2D, 0, glFormat, w, h, 0, GL_RGBA, glType, nullptr); - glBindFramebuffer(GL_FRAMEBUFFER, m_fb); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_tex->m_texID, 0); - - if (m_stencilTex) { - m_stencilTex->bind(); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, w, h, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_stencilTex->m_texID, 0); - } - - auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - RASSERT((status == GL_FRAMEBUFFER_COMPLETE), "Framebuffer incomplete, couldn't create! (FB status: {}, GL Error: 0x{:x})", status, sc(glGetError())); - - Debug::log(LOG, "Framebuffer created, status {}", status); - } - - glBindTexture(GL_TEXTURE_2D, 0); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - m_size = Vector2D(w, h); - - return true; + m_size = {w, h}; + m_drmFormat = format; + m_fbAllocated = internalAlloc(w, h, format); + return m_fbAllocated; } -void CFramebuffer::addStencil(SP tex) { - m_stencilTex = tex; - m_stencilTex->bind(); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, m_size.x, m_size.y, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr); - - glBindFramebuffer(GL_FRAMEBUFFER, m_fb); - - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_stencilTex->m_texID, 0); - - auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - RASSERT((status == GL_FRAMEBUFFER_COMPLETE), "Failed adding a stencil to fbo!", status); - - m_stencilTex->unbind(); - glBindFramebuffer(GL_FRAMEBUFFER, 0); -} - -void CFramebuffer::bind() { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fb); - - if (g_pHyprOpenGL) - g_pHyprOpenGL->setViewport(0, 0, g_pHyprOpenGL->m_renderData.pMonitor->m_pixelSize.x, g_pHyprOpenGL->m_renderData.pMonitor->m_pixelSize.y); - else - glViewport(0, 0, m_size.x, m_size.y); -} - -void CFramebuffer::unbind() { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); -} - -void CFramebuffer::release() { - if (m_fbAllocated) { - glBindFramebuffer(GL_FRAMEBUFFER, m_fb); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - glDeleteFramebuffers(1, &m_fb); - m_fbAllocated = false; - m_fb = 0; - } - - if (m_tex) - m_tex.reset(); - - m_size = Vector2D(); -} - -CFramebuffer::~CFramebuffer() { - release(); -} - -bool CFramebuffer::isAllocated() { +bool IFramebuffer::isAllocated() { return m_fbAllocated && m_tex; } -SP CFramebuffer::getTexture() { +SP IFramebuffer::getTexture() { return m_tex; } -GLuint CFramebuffer::getFBID() { - return m_fbAllocated ? m_fb : 0; -} - -SP CFramebuffer::getStencilTex() { +SP IFramebuffer::getStencilTex() { return m_stencilTex; } diff --git a/src/render/Framebuffer.hpp b/src/render/Framebuffer.hpp index 0e18df5f..7e33f227 100644 --- a/src/render/Framebuffer.hpp +++ b/src/render/Framebuffer.hpp @@ -3,32 +3,38 @@ #include "../defines.hpp" #include "../helpers/Format.hpp" #include "Texture.hpp" +#include +#include -class CFramebuffer { +class CHLBufferReference; + +class IFramebuffer { public: - CFramebuffer(); - ~CFramebuffer(); + IFramebuffer() = default; + IFramebuffer(const std::string& name); + virtual ~IFramebuffer() = default; + + virtual bool alloc(int w, int h, uint32_t format = DRM_FORMAT_ARGB8888); + virtual void release() = 0; + virtual bool readPixels(CHLBufferReference buffer, uint32_t offsetX = 0, uint32_t offsetY = 0, uint32_t width = 0, uint32_t height = 0) = 0; + + virtual void bind() = 0; - bool alloc(int w, int h, uint32_t format = GL_RGBA); - void addStencil(SP tex); - void bind(); - void unbind(); - void release(); - void reset(); bool isAllocated(); - SP getTexture(); - SP getStencilTex(); - GLuint getFBID(); + SP getTexture(); + SP getStencilTex(); + + virtual void addStencil(SP tex) = 0; Vector2D m_size; - DRMFormat m_drmFormat = 0 /* DRM_FORMAT_INVALID */; + DRMFormat m_drmFormat = DRM_FORMAT_INVALID; - private: - SP m_tex; - GLuint m_fb = -1; + protected: + virtual bool internalAlloc(int w, int h, uint32_t format = DRM_FORMAT_ARGB8888) = 0; + + SP m_tex; bool m_fbAllocated = false; - SP m_stencilTex; - - friend class CRenderbuffer; + SP m_stencilTex; + std::string m_name; // name for logging }; diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index baf69b96..83ad05ca 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -10,22 +11,28 @@ #include "../Compositor.hpp" #include "../helpers/MiscFunctions.hpp" #include "../helpers/CursorShapes.hpp" +#include "../helpers/TransferFunction.hpp" #include "../config/ConfigValue.hpp" #include "../config/ConfigManager.hpp" #include "../managers/PointerManager.hpp" -#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 "../event/EventBus.hpp" +#include "../managers/screenshare/ScreenshareManager.hpp" #include "debug/HyprNotificationOverlay.hpp" #include "hyprerror/HyprError.hpp" +#include "macros.hpp" #include "pass/TexPassElement.hpp" #include "pass/RectPassElement.hpp" #include "pass/PreBlurElement.hpp" @@ -34,40 +41,38 @@ #include "AsyncResourceGatherer.hpp" #include #include +#include #include #include #include #include +#include +#include "ShaderLoader.hpp" +#include "Texture.hpp" #include -#include "./shaders/Shaders.hpp" +#include "gl/GLFramebuffer.hpp" +#include "gl/GLTexture.hpp" using namespace Hyprutils::OS; using namespace NColorManagement; - -const std::vector ASSET_PATHS = { -#ifdef DATAROOTDIR - DATAROOTDIR, -#endif - "/usr/share", - "/usr/local/share", -}; +using namespace Render; static inline void loadGLProc(void* pProc, const char* name) { void* proc = rc(eglGetProcAddress(name)); 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; } } @@ -94,7 +99,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) { @@ -104,14 +109,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"); @@ -120,11 +125,11 @@ 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; @@ -155,19 +160,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); @@ -178,7 +190,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); @@ -198,9 +210,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); @@ -220,12 +232,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; } @@ -233,13 +245,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; } @@ -249,21 +261,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"); @@ -292,7 +304,8 @@ CHyprOpenGLImpl::CHyprOpenGLImpl() : m_drmFD(g_pCompositor->m_drmRenderNode.fd > loadGLProc(&m_proc.eglQueryDisplayAttribEXT, "eglQueryDisplayAttribEXT"); } - if (EGLEXTENSIONS.contains("EGL_KHR_debug")) { + static const auto GLDEBUG = CConfigValue("debug:gl_debugging"); + if (EGLEXTENSIONS.contains("EGL_KHR_debug") && *GLDEBUG) { loadGLProc(&m_proc.eglDebugMessageControlKHR, "eglDebugMessageControlKHR"); static const EGLAttrib debugAttrs[] = { EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE, EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE, EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE, EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE, EGL_NONE, @@ -313,7 +326,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)}; @@ -335,33 +348,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,7 +391,7 @@ CHyprOpenGLImpl::CHyprOpenGLImpl() : m_drmFD(g_pCompositor->m_drmRenderNode.fd > initAssets(); - static auto P = g_pHookSystem->hookDynamic("preRender", [&](void* self, SCallbackInfo& info, std::any data) { preRender(std::any_cast(data)); }); + static auto P = Event::bus()->m_events.render.pre.listen([&](PHLMONITOR mon) { preRender(mon); }); RASSERT(eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT), "Couldn't unset current EGL!"); @@ -409,26 +422,24 @@ CHyprOpenGLImpl::CHyprOpenGLImpl() : m_drmFD(g_pCompositor->m_drmRenderNode.fd > #endif }; - static auto P2 = g_pHookSystem->hookDynamic("mouseButton", [](void* self, SCallbackInfo& info, std::any e) { - auto E = std::any_cast(e); - - if (E.state != WL_POINTER_BUTTON_STATE_PRESSED) + static auto P2 = Event::bus()->m_events.input.mouse.button.listen([](IPointer::SButtonEvent e, Event::SCallbackInfo&) { + if (e.state != WL_POINTER_BUTTON_STATE_PRESSED) return; addLastPressToHistory(g_pInputManager->getMouseCoordsInternal(), g_pInputManager->getClickMode() == CLICKMODE_KILL, false); }); - static auto P3 = g_pHookSystem->hookDynamic("touchDown", [](void* self, SCallbackInfo& info, std::any e) { - auto E = std::any_cast(e); + 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 : ""); - auto PMONITOR = g_pCompositor->getMonitorFromName(!E.device->m_boundOutput.empty() ? E.device->m_boundOutput : ""); + PMONITOR = PMONITOR ? PMONITOR : Desktop::focusState()->monitor(); - PMONITOR = PMONITOR ? PMONITOR : g_pCompositor->m_lastMonitor.lock(); - - 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() { @@ -452,7 +463,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; } @@ -490,12 +501,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; } @@ -504,7 +515,7 @@ 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); @@ -513,11 +524,11 @@ void CHyprOpenGLImpl::initDRMFormats() { } 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 @@ -549,7 +560,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?")); @@ -567,16 +578,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; } @@ -628,102 +639,14 @@ 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) { +void CHyprOpenGLImpl::beginSimple(PHLMONITOR pMonitor, const CRegion& damage, SP rb, SP fb) { m_renderData.pMonitor = pMonitor; const GLenum RESETSTATUS = glGetGraphicsResetStatus(); @@ -750,7 +673,7 @@ void CHyprOpenGLImpl::beginSimple(PHLMONITOR pMonitor, const CRegion& damage, SP 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.monitorProjection.translate(FBO->m_size / 2.0).transform(Math::wlTransformToHyprutils(pMonitor->m_transform)).translate(-tfmd / 2.0); } m_renderData.pCurrentMonData = &m_monitorRenderResources[pMonitor]; @@ -758,6 +681,7 @@ void CHyprOpenGLImpl::beginSimple(PHLMONITOR pMonitor, const CRegion& damage, SP if (!m_shadersInitialized) initShaders(); + m_renderData.transformDamage = true; m_renderData.damage.set(damage); m_renderData.finalDamage.set(damage); @@ -775,7 +699,7 @@ void CHyprOpenGLImpl::beginSimple(PHLMONITOR pMonitor, const CRegion& damage, SP pushMonitorTransformEnabled(false); } -void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, CFramebuffer* fb, std::optional finalDamage) { +void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, SP fb, std::optional finalDamage) { m_renderData.pMonitor = pMonitor; const GLenum RESETSTATUS = glGetGraphicsResetStatus(); @@ -799,7 +723,8 @@ void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, CFrameb m_renderData.monitorProjection = pMonitor->m_projMatrix; - if (m_monitorRenderResources.contains(pMonitor) && m_monitorRenderResources.at(pMonitor).offloadFB.m_size != pMonitor->m_pixelSize) + if (m_monitorRenderResources.contains(pMonitor) && + (!m_monitorRenderResources.at(pMonitor).offloadFB || m_monitorRenderResources.at(pMonitor).offloadFB->m_size != pMonitor->m_pixelSize)) destroyMonitorResources(pMonitor); m_renderData.pCurrentMonData = &m_monitorRenderResources[pMonitor]; @@ -810,23 +735,40 @@ void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, CFrameb const auto DRM_FORMAT = fb ? fb->m_drmFormat : pMonitor->m_output->state->state().drmFormat; // ensure a framebuffer for the monitor exists - if (m_renderData.pCurrentMonData->offloadFB.m_size != pMonitor->m_pixelSize || DRM_FORMAT != m_renderData.pCurrentMonData->offloadFB.m_drmFormat) { - m_renderData.pCurrentMonData->stencilTex->allocate(); + if (!m_renderData.pCurrentMonData->offloadFB || m_renderData.pCurrentMonData->offloadFB->m_size != pMonitor->m_pixelSize || + DRM_FORMAT != m_renderData.pCurrentMonData->offloadFB->m_drmFormat) { + m_renderData.pCurrentMonData->stencilTex = g_pHyprRenderer->createStencilTexture(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y); + m_renderData.pCurrentMonData->offloadFB = g_pHyprRenderer->createFB(); + m_renderData.pCurrentMonData->mirrorFB = g_pHyprRenderer->createFB(); + m_renderData.pCurrentMonData->mirrorSwapFB = g_pHyprRenderer->createFB(); - m_renderData.pCurrentMonData->offloadFB.alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT); - m_renderData.pCurrentMonData->mirrorFB.alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT); - m_renderData.pCurrentMonData->mirrorSwapFB.alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT); + m_renderData.pCurrentMonData->offloadFB->addStencil(m_renderData.pCurrentMonData->stencilTex); + m_renderData.pCurrentMonData->mirrorFB->addStencil(m_renderData.pCurrentMonData->stencilTex); + m_renderData.pCurrentMonData->mirrorSwapFB->addStencil(m_renderData.pCurrentMonData->stencilTex); - m_renderData.pCurrentMonData->offloadFB.addStencil(m_renderData.pCurrentMonData->stencilTex); - m_renderData.pCurrentMonData->mirrorFB.addStencil(m_renderData.pCurrentMonData->stencilTex); - m_renderData.pCurrentMonData->mirrorSwapFB.addStencil(m_renderData.pCurrentMonData->stencilTex); + m_renderData.pCurrentMonData->offloadFB->alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT); + m_renderData.pCurrentMonData->mirrorFB->alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT); + m_renderData.pCurrentMonData->mirrorSwapFB->alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT); } - if (m_renderData.pCurrentMonData->monitorMirrorFB.isAllocated() && m_renderData.pMonitor->m_mirrors.empty()) - m_renderData.pCurrentMonData->monitorMirrorFB.release(); + const bool HAS_MIRROR_FB = m_renderData.pCurrentMonData->monitorMirrorFB && m_renderData.pCurrentMonData->monitorMirrorFB->isAllocated(); + const bool NEEDS_COPY_FB = needsACopyFB(m_renderData.pMonitor.lock()); - m_renderData.damage.set(damage_); - m_renderData.finalDamage.set(finalDamage.value_or(damage_)); + if (HAS_MIRROR_FB && !NEEDS_COPY_FB) + m_renderData.pCurrentMonData->monitorMirrorFB->release(); + else if (!HAS_MIRROR_FB && NEEDS_COPY_FB && m_renderData.pCurrentMonData->monitorMirrorFB) + m_renderData.pCurrentMonData->monitorMirrorFB->alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y, + m_renderData.pMonitor->m_output->state->state().drmFormat); + + m_renderData.transformDamage = true; + if (HAS_MIRROR_FB != NEEDS_COPY_FB) { + // force full damage because the mirror fb will be empty + m_renderData.damage.set({0, 0, INT32_MAX, INT32_MAX}); + m_renderData.finalDamage.set(m_renderData.damage); + } else { + m_renderData.damage.set(damage_); + m_renderData.finalDamage.set(finalDamage.value_or(damage_)); + } m_fakeFrame = fb; @@ -836,8 +778,8 @@ void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, CFrameb applyScreenShader(*PSHADER); } - m_renderData.pCurrentMonData->offloadFB.bind(); - m_renderData.currentFB = &m_renderData.pCurrentMonData->offloadFB; + m_renderData.pCurrentMonData->offloadFB->bind(); + m_renderData.currentFB = m_renderData.pCurrentMonData->offloadFB; m_offloadedFramebuffer = true; m_renderData.mainFB = m_renderData.currentFB; @@ -847,49 +789,46 @@ void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, CFrameb } void CHyprOpenGLImpl::end() { - static auto PZOOMRIGID = CConfigValue("cursor:zoom_rigid"); static auto PZOOMDISABLEAA = CConfigValue("cursor:zoom_disable_aa"); TRACY_GPU_ZONE("RenderEnd"); + m_renderData.currentWindow.reset(); + m_renderData.surface.reset(); + m_renderData.currentLS.reset(); + m_renderData.clipBox = {}; + m_renderData.clipRegion.clear(); + // end the render, copy the data to the main framebuffer - if (m_offloadedFramebuffer) { + if LIKELY (m_offloadedFramebuffer) { m_renderData.damage = m_renderData.finalDamage; pushMonitorTransformEnabled(true); CBox monbox = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}; - if (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; - - 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; - } + if LIKELY (g_pHyprRenderer->m_renderMode == RENDER_MODE_NORMAL && m_renderData.mouseZoomFactor == 1.0f) + m_renderData.pMonitor->m_zoomController.m_resetCameraState = true; + m_renderData.pMonitor->m_zoomController.applyZoomTransform(monbox, m_renderData); m_applyFinalShader = !m_renderData.blockScreenShader; - if (m_renderData.mouseZoomUseMouse && *PZOOMDISABLEAA) + if UNLIKELY (m_renderData.mouseZoomFactor != 1.F && m_renderData.mouseZoomUseMouse && *PZOOMDISABLEAA) m_renderData.useNearestNeighbor = true; // copy the damaged areas into the mirror buffer - // we can't use the offloadFB for mirroring, as it contains artifacts from blurring - if (!m_renderData.pMonitor->m_mirrors.empty() && !m_fakeFrame) + // we can't use the offloadFB for mirroring / ss, as it contains artifacts from blurring + if UNLIKELY (needsACopyFB(m_renderData.pMonitor.lock()) && !m_fakeFrame) saveBufferForMirror(monbox); m_renderData.outFB->bind(); blend(false); - if (m_finalScreenShader.program < 1 && !g_pHyprRenderer->m_crashingInProgress) - renderTexturePrimitive(m_renderData.pCurrentMonData->offloadFB.getTexture(), monbox); - else - renderTexture(m_renderData.pCurrentMonData->offloadFB.getTexture(), monbox, {}); + const auto PRIMITIVE_BLOCKED = + m_finalScreenShader->program() >= 1 || g_pHyprRenderer->m_crashingInProgress || m_renderData.pMonitor->m_imageDescription->value() != SImageDescription{}; + + if LIKELY (!PRIMITIVE_BLOCKED || g_pHyprRenderer->m_renderMode != RENDER_MODE_NORMAL) + renderTexturePrimitive(m_renderData.pCurrentMonData->offloadFB->getTexture(), monbox); + else // we need to use renderTexture if we do any CM whatsoever. + renderTexture(m_renderData.pCurrentMonData->offloadFB->getTexture(), monbox, {.finalMonitorCM = true}); blend(true); @@ -898,6 +837,24 @@ void CHyprOpenGLImpl::end() { popMonitorTransformEnabled(); } + // invalidate our render FBs to signal to the driver we don't need them anymore + if (m_renderData.pCurrentMonData->mirrorFB) { + m_renderData.pCurrentMonData->mirrorFB->bind(); + GLFB(m_renderData.pCurrentMonData->mirrorFB)->invalidate({GL_STENCIL_ATTACHMENT, GL_COLOR_ATTACHMENT0}); + } + if (m_renderData.pCurrentMonData->mirrorSwapFB) { + m_renderData.pCurrentMonData->mirrorSwapFB->bind(); + GLFB(m_renderData.pCurrentMonData->mirrorSwapFB)->invalidate({GL_STENCIL_ATTACHMENT, GL_COLOR_ATTACHMENT0}); + } + if (m_renderData.pCurrentMonData->offloadFB) { + m_renderData.pCurrentMonData->offloadFB->bind(); + GLFB(m_renderData.pCurrentMonData->offloadFB)->invalidate({GL_STENCIL_ATTACHMENT, GL_COLOR_ATTACHMENT0}); + } + if (m_renderData.pCurrentMonData->offMainFB) { + m_renderData.pCurrentMonData->offMainFB->bind(); + GLFB(m_renderData.pCurrentMonData->offMainFB)->invalidate({GL_STENCIL_ATTACHMENT, GL_COLOR_ATTACHMENT0}); + } + // reset our data m_renderData.pMonitor.reset(); m_renderData.mouseZoomFactor = 1.f; @@ -911,14 +868,22 @@ void CHyprOpenGLImpl::end() { // if we dropped to offMain, release it now. // if there is a plugin constantly using it, this might be a bit slow, // but I haven't seen a single plugin yet use these, so it's better to drop a bit of vram. - if (m_renderData.pCurrentMonData->offMainFB.isAllocated()) - m_renderData.pCurrentMonData->offMainFB.release(); + if UNLIKELY (m_renderData.pCurrentMonData->offMainFB && m_renderData.pCurrentMonData->offMainFB->isAllocated()) + m_renderData.pCurrentMonData->offMainFB->release(); - // check for gl errors - const GLenum ERR = glGetError(); + static const auto GLDEBUG = CConfigValue("debug:gl_debugging"); - if (ERR == GL_CONTEXT_LOST) /* We don't have infra to recover from this */ - RASSERT(false, "glGetError at Opengl::end() returned GL_CONTEXT_LOST. Cannot continue until proper GPU reset handling is implemented."); + if (*GLDEBUG) { + // check for gl errors + const GLenum ERR = glGetError(); + + if UNLIKELY (ERR == GL_CONTEXT_LOST) /* We don't have infra to recover from this */ + RASSERT(false, "glGetError at Opengl::end() returned GL_CONTEXT_LOST. Cannot continue until proper GPU reset handling is implemented."); + } +} + +bool CHyprOpenGLImpl::needsACopyFB(PHLMONITOR mon) { + return !mon->m_mirrors.empty() || Screenshare::mgr()->isOutputBeingSSd(mon); } void CHyprOpenGLImpl::setDamage(const CRegion& damage_, std::optional finalDamage) { @@ -926,335 +891,43 @@ void CHyprOpenGLImpl::setDamage(const CRegion& damage_, std::optional f m_renderData.finalDamage.set(finalDamage.value_or(damage_)); } -// TODO notify user if bundled shader is newer than ~/.config override -static std::string loadShader(const std::string& filename) { - const auto home = Hyprutils::Path::getHome(); - if (home.has_value()) { - const auto src = NFsUtils::readFileAsString(home.value() + "/hypr/shaders/" + filename); - if (src.has_value()) - return src.value(); - } - for (auto& e : ASSET_PATHS) { - const auto src = NFsUtils::readFileAsString(std::string{e} + "/hypr/shaders/" + filename); - if (src.has_value()) - return src.value(); - } - if (SHADERS.contains(filename)) - return SHADERS.at(filename); - throw std::runtime_error(std::format("Couldn't load shader {}", filename)); -} +static const std::vector SHADER_INCLUDES = { + "defines.h", "constants.h", "cm_helpers.glsl", "rounding.glsl", "CM.glsl", "tonemap.glsl", "gain.glsl", + "border.glsl", "shadow.glsl", "blurprepare.glsl", "blur1.glsl", "blur2.glsl", "blurFinish.glsl", +}; -static void loadShaderInclude(const std::string& filename, std::map& includes) { - includes.insert({filename, loadShader(filename)}); -} +// order matters, see ePreparedFragmentShader +const std::array FRAG_SHADERS = { + "quad.frag", "passthru.frag", "rgbamatte.frag", "ext.frag", "blur1.frag", "blur2.frag", + "blurprepare.frag", "blurfinish.frag", "shadow.frag", "surface.frag", "border.frag", "glitch.frag", +}; -static void processShaderIncludes(std::string& source, const std::map& includes) { - for (auto it = includes.begin(); it != includes.end(); ++it) { - Hyprutils::String::replaceInString(source, "#include \"" + it->first + "\"", it->second); - } -} - -static std::string processShader(const std::string& filename, const std::map& includes) { - auto source = loadShader(filename); - processShaderIncludes(source, includes); - return source; -} - -// shader has #include "CM.glsl" -static void getCMShaderUniforms(SShader& shader) { - shader.uniformLocations[SHADER_SKIP_CM] = glGetUniformLocation(shader.program, "skipCM"); - shader.uniformLocations[SHADER_SOURCE_TF] = glGetUniformLocation(shader.program, "sourceTF"); - shader.uniformLocations[SHADER_TARGET_TF] = glGetUniformLocation(shader.program, "targetTF"); - shader.uniformLocations[SHADER_SRC_TF_RANGE] = glGetUniformLocation(shader.program, "srcTFRange"); - shader.uniformLocations[SHADER_DST_TF_RANGE] = glGetUniformLocation(shader.program, "dstTFRange"); - shader.uniformLocations[SHADER_TARGET_PRIMARIES] = glGetUniformLocation(shader.program, "targetPrimaries"); - shader.uniformLocations[SHADER_MAX_LUMINANCE] = glGetUniformLocation(shader.program, "maxLuminance"); - shader.uniformLocations[SHADER_DST_MAX_LUMINANCE] = glGetUniformLocation(shader.program, "dstMaxLuminance"); - shader.uniformLocations[SHADER_DST_REF_LUMINANCE] = glGetUniformLocation(shader.program, "dstRefLuminance"); - shader.uniformLocations[SHADER_SDR_SATURATION] = glGetUniformLocation(shader.program, "sdrSaturation"); - shader.uniformLocations[SHADER_SDR_BRIGHTNESS] = glGetUniformLocation(shader.program, "sdrBrightnessMultiplier"); - shader.uniformLocations[SHADER_CONVERT_MATRIX] = glGetUniformLocation(shader.program, "convertMatrix"); -} - -// shader has #include "rounding.glsl" -static void getRoundingShaderUniforms(SShader& shader) { - shader.uniformLocations[SHADER_TOP_LEFT] = glGetUniformLocation(shader.program, "topLeft"); - shader.uniformLocations[SHADER_FULL_SIZE] = glGetUniformLocation(shader.program, "fullSize"); - shader.uniformLocations[SHADER_RADIUS] = glGetUniformLocation(shader.program, "radius"); - shader.uniformLocations[SHADER_ROUNDING_POWER] = glGetUniformLocation(shader.program, "roundingPower"); -} - -bool CHyprOpenGLImpl::initShaders() { - auto shaders = makeShared(); - const bool isDynamic = m_shadersInitialized; - static const auto PCM = CConfigValue("render:cm_enabled"); +bool CHyprOpenGLImpl::initShaders(const std::string& path) { + auto shaders = makeShared(); + static const auto PCM = CConfigValue("render:cm_enabled"); try { - std::map includes; - loadShaderInclude("rounding.glsl", includes); - loadShaderInclude("CM.glsl", includes); + auto shaderLoader = makeUnique(SHADER_INCLUDES, FRAG_SHADERS, path); - shaders->TEXVERTSRC = processShader("tex300.vert", includes); - shaders->TEXVERTSRC320 = processShader("tex320.vert", includes); + shaders->TEXVERTSRC = shaderLoader->process("tex300.vert"); + shaders->TEXVERTSRC320 = shaderLoader->process("tex320.vert"); - GLuint prog; + m_cmSupported = *PCM; - if (!*PCM) - m_cmSupported = false; - else { - const auto TEXFRAGSRCCM = processShader("CM.frag", includes); - - prog = createProgram(shaders->TEXVERTSRC, TEXFRAGSRCCM, true, true); - if (m_shadersInitialized && m_cmSupported && prog == 0) - g_pHyprNotificationOverlay->addNotification("CM shader reload failed, falling back to rgba/rgbx", 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; } @@ -1262,7 +935,7 @@ void CHyprOpenGLImpl::applyScreenShader(const std::string& path) { static auto PDT = CConfigValue("debug:damage_tracking"); - m_finalScreenShader.destroy(); + m_finalScreenShader->destroy(); if (path.empty() || path == STRVAL_EMPTY) return; @@ -1276,47 +949,23 @@ void CHyprOpenGLImpl::applyScreenShader(const std::string& path) { std::string fragmentShader((std::istreambuf_iterator(infile)), (std::istreambuf_iterator())); - m_finalScreenShader.program = createProgram( // - fragmentShader.starts_with("#version 320 es") // do not break existing custom shaders - ? - m_shaders->TEXVERTSRC320 : - m_shaders->TEXVERTSRC, - fragmentShader, true); - - if (!m_finalScreenShader.program) { + if (!m_finalScreenShader->createProgram( // + fragmentShader.starts_with("#version 320 es") // do not break existing custom shaders + ? + m_shaders->TEXVERTSRC320 : + m_shaders->TEXVERTSRC, + fragmentShader, true)) { // Error will have been sent by now by the underlying cause return; } - m_finalScreenShader.uniformLocations[SHADER_POINTER_HIDDEN] = glGetUniformLocation(m_finalScreenShader.program, "pointer_hidden"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_KILLING] = glGetUniformLocation(m_finalScreenShader.program, "pointer_killing"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_SHAPE] = glGetUniformLocation(m_finalScreenShader.program, "pointer_shape"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_SHAPE_PREVIOUS] = glGetUniformLocation(m_finalScreenShader.program, "pointer_shape_previous"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_SWITCH_TIME] = glGetUniformLocation(m_finalScreenShader.program, "pointer_switch_time"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_PRESSED_POSITIONS] = glGetUniformLocation(m_finalScreenShader.program, "pointer_pressed_positions"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_PRESSED_TIMES] = glGetUniformLocation(m_finalScreenShader.program, "pointer_pressed_times"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_PRESSED_KILLED] = glGetUniformLocation(m_finalScreenShader.program, "pointer_pressed_killed"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_PRESSED_TOUCHED] = glGetUniformLocation(m_finalScreenShader.program, "pointer_pressed_touched"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_INACTIVE_TIMEOUT] = glGetUniformLocation(m_finalScreenShader.program, "pointer_inactive_timeout"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_LAST_ACTIVE] = glGetUniformLocation(m_finalScreenShader.program, "pointer_last_active"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_SIZE] = glGetUniformLocation(m_finalScreenShader.program, "pointer_size"); - m_finalScreenShader.uniformLocations[SHADER_POINTER] = glGetUniformLocation(m_finalScreenShader.program, "pointer_position"); - m_finalScreenShader.uniformLocations[SHADER_PROJ] = glGetUniformLocation(m_finalScreenShader.program, "proj"); - m_finalScreenShader.uniformLocations[SHADER_TEX] = glGetUniformLocation(m_finalScreenShader.program, "tex"); - m_finalScreenShader.uniformLocations[SHADER_TIME] = glGetUniformLocation(m_finalScreenShader.program, "time"); - if (m_finalScreenShader.uniformLocations[SHADER_TIME] != -1) - m_finalScreenShader.initialTime = m_globalTimer.getSeconds(); - m_finalScreenShader.uniformLocations[SHADER_WL_OUTPUT] = glGetUniformLocation(m_finalScreenShader.program, "wl_output"); - m_finalScreenShader.uniformLocations[SHADER_FULL_SIZE] = glGetUniformLocation(m_finalScreenShader.program, "screen_size"); - if (m_finalScreenShader.uniformLocations[SHADER_FULL_SIZE] == -1) - m_finalScreenShader.uniformLocations[SHADER_FULL_SIZE] = glGetUniformLocation(m_finalScreenShader.program, "screenSize"); - m_finalScreenShader.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(m_finalScreenShader.program, "texcoord"); - m_finalScreenShader.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(m_finalScreenShader.program, "pos"); + if (m_finalScreenShader->getUniformLocation(SHADER_TIME) != -1) + m_finalScreenShader->setInitialTime(m_globalTimer.getSeconds()); static auto uniformRequireNoDamage = [this](eShaderUniform uniform, const std::string& name) { if (*PDT == 0) return; - if (m_finalScreenShader.uniformLocations[uniform] == -1) + if (m_finalScreenShader->getUniformLocation(uniform) == -1) return; // The screen shader uses the uniform @@ -1340,8 +989,6 @@ void CHyprOpenGLImpl::applyScreenShader(const std::string& path) { uniformRequireNoDamage(SHADER_POINTER_KILLING, "pointer_killing"); uniformRequireNoDamage(SHADER_POINTER_SHAPE, "pointer_shape"); uniformRequireNoDamage(SHADER_POINTER_SHAPE_PREVIOUS, "pointer_shape_previous"); - - m_finalScreenShader.createVao(); } void CHyprOpenGLImpl::clear(const CHyprColor& color) { @@ -1349,22 +996,20 @@ void CHyprOpenGLImpl::clear(const CHyprColor& color) { TRACY_GPU_ZONE("RenderClear"); - glClearColor(color.r, color.g, color.b, color.a); + GLCALL(glClearColor(color.r, color.g, color.b, color.a)); if (!m_renderData.damage.empty()) { m_renderData.damage.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glClear(GL_COLOR_BUFFER_BIT); }); } - - scissor(nullptr); } void CHyprOpenGLImpl::blend(bool enabled) { if (enabled) { setCapStatus(GL_BLEND, true); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // everything is premultiplied + GLCALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); // everything is premultiplied } else setCapStatus(GL_BLEND, false); @@ -1379,11 +1024,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; } @@ -1392,7 +1037,7 @@ void CHyprOpenGLImpl::scissor(const CBox& originalBox, bool transform) { } if (originalBox != m_lastScissorBox) { - glScissor(originalBox.x, originalBox.y, originalBox.width, originalBox.height); + GLCALL(glScissor(originalBox.x, originalBox.y, originalBox.width, originalBox.height)); m_lastScissorBox = originalBox; } @@ -1434,44 +1079,19 @@ void CHyprOpenGLImpl::renderRectWithBlurInternal(const CBox& box, const CHyprCol CRegion damage{m_renderData.damage}; damage.intersect(box); - CFramebuffer* POUTFB = data.xray ? &m_renderData.pCurrentMonData->blurFB : blurMainFramebufferWithDamage(data.blurA, &damage); + auto POUTFB = data.xray ? m_renderData.pCurrentMonData->blurFB : blurMainFramebufferWithDamage(data.blurA, &damage); m_renderData.currentFB->bind(); - // make a stencil for rounded corners to work with blur - scissor(nullptr); // allow the entire window and stencil to render - glClearStencil(0); - glClear(GL_STENCIL_BUFFER_BIT); - - setCapStatus(GL_STENCIL_TEST, true); - - glStencilFunc(GL_ALWAYS, 1, 0xFF); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - renderRect(box, CHyprColor(0, 0, 0, 0), SRectRenderData{.round = data.round, .roundingPower = data.roundingPower}); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - - glStencilFunc(GL_EQUAL, 1, 0xFF); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - - scissor(box); CBox MONITORBOX = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}; pushMonitorTransformEnabled(true); const auto SAVEDRENDERMODIF = m_renderData.renderModif; m_renderData.renderModif = {}; // fix shit renderTexture(POUTFB->getTexture(), MONITORBOX, - STextureRenderData{.damage = &damage, .a = data.blurA, .round = 0, .roundingPower = 2.0f, .allowCustomUV = false, .allowDim = false, .noAA = false}); + STextureRenderData{.damage = &damage, .a = data.blurA, .round = data.round, .roundingPower = 2.F, .allowCustomUV = false, .allowDim = false, .noAA = false}); popMonitorTransformEnabled(); m_renderData.renderModif = SAVEDRENDERMODIF; - glClearStencil(0); - glClear(GL_STENCIL_BUFFER_BIT); - setCapStatus(GL_STENCIL_TEST, false); - glStencilMask(0xFF); - glStencilFunc(GL_ALWAYS, 1, 0xFF); - scissor(nullptr); - renderRectWithDamageInternal(box, col, data); } @@ -1485,29 +1105,29 @@ void CHyprOpenGLImpl::renderRectWithDamageInternal(const CBox& box, const CHyprC 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); + newBox, Math::wlTransformToHyprutils(Math::invertTransform(!m_monitorTransformEnabled ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform)), newBox.rot); Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); - useProgram(m_shaders->m_shQUAD.program); - m_shaders->m_shQUAD.setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + auto shader = useShader(getShaderVariant(SH_FRAG_QUAD, data.round > 0 ? SH_FEAT_ROUNDING : 0)); + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); // premultiply the color as well as we don't work with straight alpha - m_shaders->m_shQUAD.setUniformFloat4(SHADER_COLOR, col.r * col.a, col.g * col.a, col.b * col.a, col.a); + shader->setUniformFloat4(SHADER_COLOR, col.r * col.a, col.g * col.a, col.b * col.a, col.a); CBox transformedBox = box; - transformedBox.transform(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}; @@ -1515,13 +1135,13 @@ void CHyprOpenGLImpl::renderRectWithDamageInternal(const CBox& box, const CHyprC if (!damageClip.empty()) { damageClip.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } } else { data.damage->forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } @@ -1530,7 +1150,7 @@ void CHyprOpenGLImpl::renderRectWithDamageInternal(const CBox& box, const CHyprC scissor(nullptr); } -void CHyprOpenGLImpl::renderTexture(SP tex, const CBox& box, STextureRenderData data) { +void CHyprOpenGLImpl::renderTexture(SP tex, const CBox& box, STextureRenderData data) { RASSERT(m_renderData.pMonitor, "Tried to render texture without begin()!"); if (!data.damage) { @@ -1558,194 +1178,99 @@ static bool isSDR2HDR(const NColorManagement::SImageDescription& imageDescriptio targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_HLG); } -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"); +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); +} - 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); +void CHyprOpenGLImpl::passCMUniforms(WP shader, const NColorManagement::PImageDescription imageDescription, + const NColorManagement::PImageDescription targetImageDescription, bool modifySDR, float sdrMinLuminance, int sdrMaxLuminance) { + const auto settings = g_pHyprRenderer->getCMSettings(imageDescription, targetImageDescription, m_renderData.surface.valid() ? m_renderData.surface.lock() : nullptr, modifySDR, + sdrMinLuminance, sdrMaxLuminance); - shader.setUniformInt(SHADER_TARGET_TF, targetImageDescription.transferFunction); + shader->setUniformInt(SHADER_SOURCE_TF, settings.sourceTF); + shader->setUniformInt(SHADER_TARGET_TF, settings.targetTF); + shader->setUniformFloat2(SHADER_SRC_TF_RANGE, settings.srcTFRange.min, settings.srcTFRange.max); + shader->setUniformFloat2(SHADER_DST_TF_RANGE, settings.dstTFRange.min, settings.dstTFRange.max); + shader->setUniformFloat(SHADER_SRC_REF_LUMINANCE, settings.srcRefLuminance); + shader->setUniformFloat(SHADER_DST_REF_LUMINANCE, settings.dstRefLuminance); + shader->setUniformFloat(SHADER_MAX_LUMINANCE, settings.maxLuminance); + shader->setUniformFloat(SHADER_DST_MAX_LUMINANCE, settings.dstMaxLuminance); + shader->setUniformFloat(SHADER_SDR_SATURATION, settings.sdrSaturation); + shader->setUniformFloat(SHADER_SDR_BRIGHTNESS, settings.sdrBrightnessMultiplier); - const auto targetPrimaries = targetImageDescription.primariesNameSet || targetImageDescription.primaries == SPCPRimaries{} ? - getPrimaries(targetImageDescription.primariesNamed) : - targetImageDescription.primaries; + if (!targetImageDescription->value().icc.present) { + const auto cacheKey = std::make_pair(imageDescription->id(), targetImageDescription->id()); + if (!primariesConversionCache.contains(cacheKey)) { + const auto& mat = settings.convertMatrix; + const std::array glConvertMatrix = { + mat[0][0], mat[1][0], mat[2][0], // + mat[0][1], mat[1][1], mat[2][1], // + mat[0][2], mat[1][2], mat[2][2], // + }; + primariesConversionCache.insert(std::make_pair(cacheKey, glConvertMatrix)); + } + shader->setUniformMatrix3fv(SHADER_CONVERT_MATRIX, 1, false, primariesConversionCache[cacheKey]); - const 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 PImageDescription imageDescription) { + passCMUniforms(shader, imageDescription, g_pHyprRenderer->workBufferImageDescription(), true, m_renderData.pMonitor->m_sdrMinLuminance, + m_renderData.pMonitor->m_sdrMaxLuminance); } -void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, const STextureRenderData& data) { - RASSERT(m_renderData.pMonitor, "Tried to render texture without begin()!"); - RASSERT((tex->m_texID > 0), "Attempted to draw nullptr texture!"); - - TRACY_GPU_ZONE("RenderTextureInternalWithDamage"); - - float alpha = std::clamp(data.a, 0.f, 1.f); - - if (data.damage->empty()) - return; - - CBox newBox = box; - m_renderData.renderModif.applyToBox(newBox); - +WP CHyprOpenGLImpl::renderToOutputInternal() { static const auto PDT = CConfigValue("debug:damage_tracking"); - static const auto PPASS = CConfigValue("render:cm_fs_passthrough"); - static const auto PENABLECM = CConfigValue("render:cm_enabled"); static const auto PCURSORTIMEOUT = CConfigValue("cursor:inactive_timeout"); - // get the needed transform for this texture - const 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)); + WP shader = + g_pHyprRenderer->m_crashingInProgress ? getShaderVariant(SH_FRAG_GLITCH) : (m_finalScreenShader->program() ? m_finalScreenShader : getShaderVariant(SH_FRAG_PASSTHRURGBA)); - Mat3x3 matrix = m_renderData.monitorProjection.projectBox(newBox, TRANSFORM, newBox.rot); - Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); + shader = useShader(shader); - SShader* shader = nullptr; - - bool usingFinalShader = false; - - const bool CRASHING = m_applyFinalShader && g_pHyprRenderer->m_crashingInProgress; - - auto texType = tex->m_type; - - if (CRASHING) { - shader = &m_shaders->m_shGLITCH; - usingFinalShader = true; - } else if (m_applyFinalShader && m_finalScreenShader.program) { - shader = &m_finalScreenShader; - usingFinalShader = true; - } else { - if (m_applyFinalShader) { - shader = &m_shaders->m_shPASSTHRURGBA; - usingFinalShader = true; - } else { - switch (tex->m_type) { - case TEXTURE_RGBA: shader = &m_shaders->m_shRGBA; break; - case TEXTURE_RGBX: shader = &m_shaders->m_shRGBX; break; - - case TEXTURE_EXTERNAL: shader = &m_shaders->m_shEXT; break; // might be unused - default: RASSERT(false, "tex->m_iTarget unsupported!"); - } - } - } - - if (m_renderData.currentWindow && m_renderData.currentWindow->m_windowData.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, 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>(); @@ -1763,7 +1288,7 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c shader->setUniformFloat(SHADER_POINTER_LAST_ACTIVE, g_pInputManager->m_lastCursorMovement.getSeconds()); shader->setUniformFloat(SHADER_POINTER_SWITCH_TIME, g_pHyprRenderer->m_lastCursorData.switchedTimer.getSeconds()); - } else if (usingFinalShader) { + } else { shader->setUniformFloat2(SHADER_POINTER, 0.f, 0.f); static const std::vector pressedPosDefault(POINTER_PRESSED_HISTORY_LENGTH * 2uz, 0.f); @@ -1777,68 +1302,249 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c shader->setUniformFloat(SHADER_POINTER_SWITCH_TIME, 0.f); } - if (CRASHING) { + if (g_pHyprRenderer->m_crashingInProgress) { shader->setUniformFloat(SHADER_DISTORT, g_pHyprRenderer->m_crashingDistort); shader->setUniformFloat2(SHADER_FULL_SIZE, m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y); } - if (!usingFinalShader) { - shader->setUniformFloat(SHADER_ALPHA, alpha); + return shader; +} - if (data.discardActive) { - shader->setUniformInt(SHADER_DISCARD_OPAQUE, !!(m_renderData.discardMode & DISCARD_OPAQUE)); - shader->setUniformInt(SHADER_DISCARD_ALPHA, !!(m_renderData.discardMode & DISCARD_ALPHA)); - shader->setUniformFloat(SHADER_DISCARD_ALPHA_VALUE, m_renderData.discardOpacity); - } else { - shader->setUniformInt(SHADER_DISCARD_OPAQUE, 0); - shader->setUniformInt(SHADER_DISCARD_ALPHA, 0); +WP CHyprOpenGLImpl::renderToFBInternal(const STextureRenderData& data, eTextureType texType, const CBox& newBox) { + static const auto PPASS = CConfigValue("render:cm_fs_passthrough"); + static const auto PENABLECM = CConfigValue("render:cm_enabled"); + static auto PBLEND = CConfigValue("render:use_shader_blur_blend"); + + float alpha = std::clamp(data.a, 0.f, 1.f); + + WP shader; + ShaderFeatureFlags shaderFeatures = 0; + + switch (texType) { + case TEXTURE_RGBA: shaderFeatures |= SH_FEAT_RGBA; break; + case TEXTURE_RGBX: shaderFeatures &= ~SH_FEAT_RGBA; break; + + // TODO set correct features + case TEXTURE_EXTERNAL: shader = getShaderVariant(SH_FRAG_EXT, SH_FEAT_ROUNDING | SH_FEAT_DISCARD | SH_FEAT_TINT); break; // might be unused + default: RASSERT(false, "tex->m_iTarget unsupported!"); + } + + if (data.finalMonitorCM || (m_renderData.currentWindow && m_renderData.currentWindow->m_ruleApplicator->RGBX().valueOrDefault())) + shaderFeatures &= ~SH_FEAT_RGBA; + + const auto surface = m_renderData.surface; + const bool isHDRSurface = surface.valid() && surface->m_colorManagement.valid() ? surface->m_colorManagement->isHDR() : false; + const bool canPassHDRSurface = isHDRSurface && !surface->m_colorManagement->isWindowsScRGB(); // windows scRGB requires CM shader + + const auto WORK_BUFFER_IMAGE_DESCRIPTION = g_pHyprRenderer->workBufferImageDescription(); + + // chosenSdrEotf contains the valid eotf for this display + + const auto SOURCE_IMAGE_DESCRIPTION = [&] { + // if valid CM surface, use that as a source + if (m_renderData.surface.valid() && m_renderData.surface->m_colorManagement.valid()) + return CImageDescription::from(m_renderData.surface->m_colorManagement->imageDescription()); + + // otherwise, if we are CM'ing back into source, use chosen, because that's what our work buffer is in + // the same applies to the final monitor CM + if (data.cmBackToSRGB || data.finalMonitorCM) // NOLINTNEXTLINE + return WORK_BUFFER_IMAGE_DESCRIPTION; + + // otherwise, default + return DEFAULT_IMAGE_DESCRIPTION; + }(); + + const auto TARGET_IMAGE_DESCRIPTION = [&] { + // if we are CM'ing back, use default sRGB + if (data.cmBackToSRGB) + return DEFAULT_IMAGE_DESCRIPTION; + + // for final CM, use the target description + if (data.finalMonitorCM) + return m_renderData.pMonitor->m_imageDescription; + // otherwise, use chosen, we're drawing into the work buffer + // NOLINTNEXTLINE + return WORK_BUFFER_IMAGE_DESCRIPTION; + }(); + + if (data.blur && *PBLEND && data.blurredBG) + shaderFeatures |= SH_FEAT_BLUR; + + if (data.discardActive) + shaderFeatures |= SH_FEAT_DISCARD; + + const bool CANT_CHECK_CM_EQUALITY = data.cmBackToSRGB || data.finalMonitorCM || (!m_renderData.surface || !m_renderData.surface->m_colorManagement); + + const bool skipCM = !*PENABLECM || !m_cmSupported /* CM unsupported or disabled */ + || m_renderData.pMonitor->doesNoShaderCM() /* no shader needed */ + || (SOURCE_IMAGE_DESCRIPTION->id() == TARGET_IMAGE_DESCRIPTION->id() && !CANT_CHECK_CM_EQUALITY) /* Source and target have the same image description */ + || (((*PPASS && canPassHDRSurface) || + (*PPASS == 1 && !isHDRSurface && m_renderData.pMonitor->m_cmType != NCMType::CM_HDR && m_renderData.pMonitor->m_cmType != NCMType::CM_HDR_EDID)) && + m_renderData.pMonitor->inFullscreenMode()) /* Fullscreen window with pass cm enabled */; + + if (data.allowDim && m_renderData.currentWindow && (m_renderData.currentWindow->m_notRespondingTint->value() > 0 || m_renderData.currentWindow->m_dimPercent->value() > 0)) + shaderFeatures |= SH_FEAT_TINT; + + if (data.round > 0) + shaderFeatures |= SH_FEAT_ROUNDING; + + if (!skipCM) { + shaderFeatures |= SH_FEAT_CM; + + if (TARGET_IMAGE_DESCRIPTION->value().icc.present) + shaderFeatures |= SH_FEAT_ICC; + else { + const bool needsSDRmod = isSDR2HDR(SOURCE_IMAGE_DESCRIPTION->value(), TARGET_IMAGE_DESCRIPTION->value()); + const bool needsHDRmod = !needsSDRmod && isHDR2SDR(SOURCE_IMAGE_DESCRIPTION->value(), TARGET_IMAGE_DESCRIPTION->value()); + const float maxLuminance = needsHDRmod ? + SOURCE_IMAGE_DESCRIPTION->value().getTFMaxLuminance(-1) : + (SOURCE_IMAGE_DESCRIPTION->value().luminances.max > 0 ? SOURCE_IMAGE_DESCRIPTION->value().luminances.max : SOURCE_IMAGE_DESCRIPTION->value().luminances.reference); + const auto dstMaxLuminance = TARGET_IMAGE_DESCRIPTION->value().luminances.max > 0 ? TARGET_IMAGE_DESCRIPTION->value().luminances.max : 10000; + + if (maxLuminance >= dstMaxLuminance * 1.01) + shaderFeatures |= SH_FEAT_TONEMAP; + + if (data.finalMonitorCM && + (SOURCE_IMAGE_DESCRIPTION->value().transferFunction == CM_TRANSFER_FUNCTION_SRGB || + SOURCE_IMAGE_DESCRIPTION->value().transferFunction == CM_TRANSFER_FUNCTION_GAMMA22) && + TARGET_IMAGE_DESCRIPTION->value().transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ && + ((m_renderData.pMonitor->m_sdrSaturation > 0 && m_renderData.pMonitor->m_sdrSaturation != 1.0f) || + (m_renderData.pMonitor->m_sdrBrightness > 0 && m_renderData.pMonitor->m_sdrBrightness != 1.0f))) + shaderFeatures |= SH_FEAT_SDR_MOD; } } + if (!shader) + shader = getShaderVariant(SH_FRAG_SURFACE, shaderFeatures); + shader = useShader(shader); + + if (!skipCM) { + if (data.finalMonitorCM || data.cmBackToSRGB) + passCMUniforms(shader, SOURCE_IMAGE_DESCRIPTION, TARGET_IMAGE_DESCRIPTION, true, m_renderData.pMonitor->m_sdrMinLuminance, m_renderData.pMonitor->m_sdrMaxLuminance); + else + passCMUniforms(shader, SOURCE_IMAGE_DESCRIPTION); + } + + shader->setUniformFloat(SHADER_ALPHA, alpha); + + if (shaderFeatures & SH_FEAT_BLUR) { + shader->setUniformInt(SHADER_BLURRED_BG, 1); + // shader->setUniformFloat2(SHADER_UV_OFFSET, 0, 0); + shader->setUniformFloat2(SHADER_UV_OFFSET, newBox.x / m_renderData.pMonitor->m_transformedSize.x, newBox.y / m_renderData.pMonitor->m_transformedSize.y); + shader->setUniformFloat2(SHADER_UV_SIZE, newBox.width / m_renderData.pMonitor->m_transformedSize.x, newBox.height / m_renderData.pMonitor->m_transformedSize.y); + + glActiveTexture(GL_TEXTURE0 + 1); + data.blurredBG->bind(); + } + + if (data.discardActive) { + shader->setUniformInt(SHADER_DISCARD_OPAQUE, !!(m_renderData.discardMode & DISCARD_OPAQUE)); + shader->setUniformInt(SHADER_DISCARD_ALPHA, !!(m_renderData.discardMode & DISCARD_ALPHA)); + shader->setUniformFloat(SHADER_DISCARD_ALPHA_VALUE, m_renderData.discardOpacity); + } else { + shader->setUniformInt(SHADER_DISCARD_OPAQUE, 0); + shader->setUniformInt(SHADER_DISCARD_ALPHA, 0); + } + CBox transformedBox = newBox; - transformedBox.transform(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 && m_renderData.currentWindow) { + if (m_renderData.currentWindow->m_notRespondingTint->value() > 0) { + const auto DIM = m_renderData.currentWindow->m_notRespondingTint->value(); + shader->setUniformInt(SHADER_APPLY_TINT, 1); + shader->setUniformFloat3(SHADER_TINT, 1.f - DIM, 1.f - DIM, 1.f - DIM); + } else if (m_renderData.currentWindow->m_dimPercent->value() > 0) { + shader->setUniformInt(SHADER_APPLY_TINT, 1); + const auto DIM = m_renderData.currentWindow->m_dimPercent->value(); + shader->setUniformFloat3(SHADER_TINT, 1.f - DIM, 1.f - DIM, 1.f - DIM); } else shader->setUniformInt(SHADER_APPLY_TINT, 0); - } + } else + shader->setUniformInt(SHADER_APPLY_TINT, 0); - glBindVertexArray(shader->uniformLocations[SHADER_SHADER_VAO]); - if (data.allowCustomUV && m_renderData.primarySurfaceUVTopLeft != Vector2D(-1, -1)) { - const float customUVs[] = { - m_renderData.primarySurfaceUVBottomRight.x, m_renderData.primarySurfaceUVTopLeft.y, m_renderData.primarySurfaceUVTopLeft.x, - m_renderData.primarySurfaceUVTopLeft.y, m_renderData.primarySurfaceUVBottomRight.x, m_renderData.primarySurfaceUVBottomRight.y, - m_renderData.primarySurfaceUVTopLeft.x, m_renderData.primarySurfaceUVBottomRight.y, - }; + return shader; +} - glBindBuffer(GL_ARRAY_BUFFER, shader->uniformLocations[SHADER_SHADER_VBO_UV]); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(customUVs), customUVs); +void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, const STextureRenderData& data) { + RASSERT(m_renderData.pMonitor, "Tried to render texture without begin()!"); + RASSERT((tex->ok()), "Attempted to draw nullptr texture!"); + + TRACY_GPU_ZONE("RenderTextureInternalWithDamage"); + + if (data.damage->empty()) + return; + + CBox newBox = box; + m_renderData.renderModif.applyToBox(newBox); + + // get the needed transform for this texture + const auto MONITOR_INVERTED = Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)); + Hyprutils::Math::eTransform TRANSFORM = tex->m_transform; + + if (m_monitorTransformEnabled) + TRANSFORM = Math::composeTransform(MONITOR_INVERTED, TRANSFORM); + + Mat3x3 matrix = m_renderData.monitorProjection.projectBox(newBox, TRANSFORM, newBox.rot); + Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); + + const bool renderToOutput = m_applyFinalShader && g_pHyprRenderer->workBufferImageDescription()->id() == m_renderData.pMonitor->m_imageDescription->id(); + + glActiveTexture(GL_TEXTURE0); + tex->bind(); + + tex->setTexParameter(GL_TEXTURE_WRAP_S, data.wrapX); + tex->setTexParameter(GL_TEXTURE_WRAP_T, data.wrapY); + + if (m_renderData.useNearestNeighbor) { + tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); + tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); } else { - glBindBuffer(GL_ARRAY_BUFFER, shader->uniformLocations[SHADER_SHADER_VBO_UV]); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(fullVerts), fullVerts); + tex->setTexParameter(GL_TEXTURE_MAG_FILTER, tex->magFilter); + tex->setTexParameter(GL_TEXTURE_MIN_FILTER, tex->minFilter); } + auto shader = renderToOutput ? renderToOutputInternal() : renderToFBInternal(data, tex->m_type, newBox); + + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniformInt(SHADER_TEX, 0); + GLCALL(glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO))); + GLCALL(glBindBuffer(GL_ARRAY_BUFFER, shader->getUniformLocation(SHADER_SHADER_VBO))); + + // this tells GPU can keep reading the old block for previous draws while the CPU writes to a new one. + // to avoid stalls if renderTextureInternal is called multiple times on same renderpass + // at the cost of some temporar vram usage. + glBufferData(GL_ARRAY_BUFFER, sizeof(fullVerts), nullptr, GL_DYNAMIC_DRAW); + + auto verts = fullVerts; + + if (data.allowCustomUV && m_renderData.primarySurfaceUVTopLeft != Vector2D(-1, -1)) { + const float u0 = m_renderData.primarySurfaceUVTopLeft.x; + const float v0 = m_renderData.primarySurfaceUVTopLeft.y; + const float u1 = m_renderData.primarySurfaceUVBottomRight.x; + const float v1 = m_renderData.primarySurfaceUVBottomRight.y; + + verts[0].u = u0; + verts[0].v = v0; + verts[1].u = u0; + verts[1].v = v1; + verts[2].u = u1; + verts[2].v = v0; + verts[3].u = u1; + verts[3].v = v1; + } + + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts.data()); + if (!m_renderData.clipBox.empty() || !m_renderData.clipRegion.empty()) { CRegion damageClip = m_renderData.clipBox; @@ -1851,25 +1557,25 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c if (!damageClip.empty()) { damageClip.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } } else { data.damage->forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); + GLCALL(glBindVertexArray(0)); + GLCALL(glBindBuffer(GL_ARRAY_BUFFER, 0)); tex->unbind(); } -void CHyprOpenGLImpl::renderTexturePrimitive(SP tex, const CBox& box) { +void CHyprOpenGLImpl::renderTexturePrimitive(SP tex, const CBox& box) { RASSERT(m_renderData.pMonitor, "Tried to render texture without begin()!"); - RASSERT((tex->m_texID > 0), "Attempted to draw nullptr texture!"); + RASSERT((tex->ok()), "Attempted to draw nullptr texture!"); TRACY_GPU_ZONE("RenderTexturePrimitive"); @@ -1880,12 +1586,10 @@ void CHyprOpenGLImpl::renderTexturePrimitive(SP tex, const CBox& box) m_renderData.renderModif.applyToBox(newBox); // get transform - const auto TRANSFORM = wlTransformToHyprutils(invertTransform(!m_monitorTransformEnabled ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform)); + const auto TRANSFORM = Math::wlTransformToHyprutils(Math::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; - glActiveTexture(GL_TEXTURE0); tex->bind(); @@ -1895,17 +1599,17 @@ void CHyprOpenGLImpl::renderTexturePrimitive(SP tex, const CBox& box) tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); } else { - tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); - tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); + tex->setTexParameter(GL_TEXTURE_MAG_FILTER, tex->magFilter); + tex->setTexParameter(GL_TEXTURE_MIN_FILTER, tex->minFilter); } - useProgram(shader->program); + auto shader = useShader(getShaderVariant(SH_FRAG_PASSTHRURGBA)); shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); shader->setUniformInt(SHADER_TEX, 0); - glBindVertexArray(shader->uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); m_renderData.damage.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); @@ -1914,9 +1618,9 @@ void CHyprOpenGLImpl::renderTexturePrimitive(SP tex, const CBox& box) tex->unbind(); } -void CHyprOpenGLImpl::renderTextureMatte(SP tex, const CBox& box, CFramebuffer& matte) { +void CHyprOpenGLImpl::renderTextureMatte(SP tex, const CBox& box, SP matte) { RASSERT(m_renderData.pMonitor, "Tried to render texture without begin()!"); - RASSERT((tex->m_texID > 0), "Attempted to draw nullptr texture!"); + RASSERT((tex->ok()), "Attempted to draw nullptr texture!"); TRACY_GPU_ZONE("RenderTextureMatte"); @@ -1927,13 +1631,11 @@ void CHyprOpenGLImpl::renderTextureMatte(SP tex, const CBox& box, CFra m_renderData.renderModif.applyToBox(newBox); // get transform - const auto TRANSFORM = wlTransformToHyprutils(invertTransform(!m_monitorTransformEnabled ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform)); + const auto TRANSFORM = Math::wlTransformToHyprutils(Math::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_shMATTE; - - useProgram(shader->program); + auto shader = useShader(getShaderVariant(SH_FRAG_MATTE)); shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); shader->setUniformInt(SHADER_TEX, 0); shader->setUniformInt(SHADER_ALPHA_MATTE, 1); @@ -1942,13 +1644,13 @@ void CHyprOpenGLImpl::renderTextureMatte(SP tex, const CBox& box, CFra tex->bind(); glActiveTexture(GL_TEXTURE0 + 1); - auto matteTex = matte.getTexture(); + auto matteTex = matte->getTexture(); matteTex->bind(); - glBindVertexArray(shader->uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); m_renderData.damage.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); @@ -1961,16 +1663,16 @@ void CHyprOpenGLImpl::renderTextureMatte(SP tex, const CBox& box, CFra // but it works... well, I guess? // // Dual (or more) kawase blur -CFramebuffer* CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* originalDamage) { +SP CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* originalDamage) { if (!m_renderData.currentFB->getTexture()) { - 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 + Log::logger->log(Log::ERR, "BUG THIS: null fb texture while attempting to blur main fb?! (introspection off?!)"); + return m_renderData.pCurrentMonData->mirrorFB; // return something to sample from at least } - return blurFramebufferWithDamage(a, originalDamage, *m_renderData.currentFB); + return blurFramebufferWithDamage(a, originalDamage, *GLFB(m_renderData.currentFB)); } -CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* originalDamage, CFramebuffer& source) { +SP CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* originalDamage, CGLFramebuffer& source) { TRACY_GPU_ZONE("RenderBlurFramebufferWithDamage"); const auto BLENDBEFORE = m_blend; @@ -1978,7 +1680,7 @@ CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* origi setCapStatus(GL_STENCIL_TEST, false); // get transforms for the full monitor - const auto TRANSFORM = wlTransformToHyprutils(invertTransform(m_renderData.pMonitor->m_transform)); + 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}; Mat3x3 matrix = m_renderData.monitorProjection.projectBox(MONITORBOX, TRANSFORM); Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); @@ -1993,21 +1695,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 = m_renderData.pCurrentMonData->mirrorFB; + const auto PMIRRORSWAPFB = m_renderData.pCurrentMonData->mirrorSwapFB; - CFramebuffer* currentRenderToFB = PMIRRORFB; + auto currentRenderToFB = PMIRRORFB; // Begin with base color adjustments - global brightness and contrast // TODO: make this a part of the first pass maybe to save on a drawcall? { static auto PBLURCONTRAST = CConfigValue("decoration:blur:contrast"); static auto PBLURBRIGHTNESS = CConfigValue("decoration:blur:brightness"); + static auto PBLEND = CConfigValue("render:use_shader_blur_blend"); PMIRRORSWAPFB->bind(); @@ -2018,31 +1721,34 @@ CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* origi currentTex->bind(); currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); - useProgram(m_shaders->m_shBLURPREPARE.program); + WP shader; // From FB to sRGB - const bool skipCM = !m_cmSupported || m_renderData.pMonitor->m_imageDescription == SImageDescription{}; - m_shaders->m_shBLURPREPARE.setUniformInt(SHADER_SKIP_CM, skipCM); + const bool skipCM = !m_cmSupported || g_pHyprRenderer->workBufferImageDescription()->id() == DEFAULT_IMAGE_DESCRIPTION->id(); if (!skipCM) { - passCMUniforms(m_shaders->m_shBLURPREPARE, m_renderData.pMonitor->m_imageDescription, 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, m_renderData.pMonitor->m_imageDescription, DEFAULT_IMAGE_DESCRIPTION); + shader->setUniformFloat(SHADER_SDR_SATURATION, + m_renderData.pMonitor->m_sdrSaturation > 0 && + m_renderData.pMonitor->m_imageDescription->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ? + m_renderData.pMonitor->m_sdrSaturation : + 1.0f); + shader->setUniformFloat(SHADER_SDR_BRIGHTNESS, + m_renderData.pMonitor->m_sdrBrightness > 0 && + m_renderData.pMonitor->m_imageDescription->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ? + m_renderData.pMonitor->m_sdrBrightness : + 1.0f); + } else + shader = useShader(getShaderVariant(SH_FRAG_BLURPREPARE)); - m_shaders->m_shBLURPREPARE.setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - m_shaders->m_shBLURPREPARE.setUniformFloat(SHADER_CONTRAST, *PBLURCONTRAST); - m_shaders->m_shBLURPREPARE.setUniformFloat(SHADER_BRIGHTNESS, *PBLURBRIGHTNESS); - m_shaders->m_shBLURPREPARE.setUniformInt(SHADER_TEX, 0); + Mat3x3 matrix = m_renderData.monitorProjection.projectBox(MONITORBOX, *PBLEND ? HYPRUTILS_TRANSFORM_NORMAL : TRANSFORM); + Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniformFloat(SHADER_CONTRAST, *PBLURCONTRAST); + shader->setUniformFloat(SHADER_BRIGHTNESS, *PBLURBRIGHTNESS); + shader->setUniformInt(SHADER_TEX, 0); - glBindVertexArray(m_shaders->m_shBLURPREPARE.uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); if (!damage.empty()) { damage.forEachRect([this](const auto& RECT) { @@ -2056,7 +1762,7 @@ CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* origi } // declare the draw func - auto drawPass = [&](SShader* pShader, CRegion* pDamage) { + auto drawPass = [&](WP shader, ePreparedFragmentShader frag, CRegion* pDamage) { if (currentRenderToFB == PMIRRORFB) PMIRRORSWAPFB->bind(); else @@ -2070,21 +1776,19 @@ CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* origi currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); - useProgram(pShader->program); - // prep two shaders - pShader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - pShader->setUniformFloat(SHADER_RADIUS, *PBLURSIZE * a); // this makes the blursize change with a - if (pShader == &m_shaders->m_shBLUR1) { - m_shaders->m_shBLUR1.setUniformFloat2(SHADER_HALFPIXEL, 0.5f / (m_renderData.pMonitor->m_pixelSize.x / 2.f), 0.5f / (m_renderData.pMonitor->m_pixelSize.y / 2.f)); - m_shaders->m_shBLUR1.setUniformInt(SHADER_PASSES, BLUR_PASSES); - m_shaders->m_shBLUR1.setUniformFloat(SHADER_VIBRANCY, *PBLURVIBRANCY); - m_shaders->m_shBLUR1.setUniformFloat(SHADER_VIBRANCY_DARKNESS, *PBLURVIBRANCYDARKNESS); + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniformFloat(SHADER_RADIUS, *PBLURSIZE * a); // this makes the blursize change with a + if (frag == SH_FRAG_BLUR1) { + shader->setUniformFloat2(SHADER_HALFPIXEL, 0.5f / (m_renderData.pMonitor->m_pixelSize.x / 2.f), 0.5f / (m_renderData.pMonitor->m_pixelSize.y / 2.f)); + shader->setUniformInt(SHADER_PASSES, BLUR_PASSES); + shader->setUniformFloat(SHADER_VIBRANCY, *PBLURVIBRANCY); + shader->setUniformFloat(SHADER_VIBRANCY_DARKNESS, *PBLURVIBRANCYDARKNESS); } else - m_shaders->m_shBLUR2.setUniformFloat2(SHADER_HALFPIXEL, 0.5f / (m_renderData.pMonitor->m_pixelSize.x * 2.f), 0.5f / (m_renderData.pMonitor->m_pixelSize.y * 2.f)); - pShader->setUniformInt(SHADER_TEX, 0); + shader->setUniformFloat2(SHADER_HALFPIXEL, 0.5f / (m_renderData.pMonitor->m_pixelSize.x * 2.f), 0.5f / (m_renderData.pMonitor->m_pixelSize.y * 2.f)); + shader->setUniformInt(SHADER_TEX, 0); - glBindVertexArray(pShader->uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); if (!pDamage->empty()) { pDamage->forEachRect([this](const auto& RECT) { @@ -2110,14 +1814,16 @@ CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* origi CRegion tempDamage{damage}; // and draw + auto shader = useShader(getShaderVariant(SH_FRAG_BLUR1)); for (auto i = 1; i <= BLUR_PASSES; ++i) { tempDamage = damage.copy().scale(1.f / (1 << i)); - drawPass(&m_shaders->m_shBLUR1, &tempDamage); // down + drawPass(shader, SH_FRAG_BLUR1, &tempDamage); // down } + shader = useShader(getShaderVariant(SH_FRAG_BLUR2)); for (auto i = BLUR_PASSES - 1; i >= 0; --i) { tempDamage = damage.copy().scale(1.f / (1 << i)); // when upsampling we make the region twice as big - drawPass(&m_shaders->m_shBLUR2, &tempDamage); // up + drawPass(shader, SH_FRAG_BLUR2, &tempDamage); // up } // finalize the image @@ -2138,14 +1844,14 @@ CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* origi currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); - useProgram(m_shaders->m_shBLURFINISH.program); - m_shaders->m_shBLURFINISH.setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - m_shaders->m_shBLURFINISH.setUniformFloat(SHADER_NOISE, *PBLURNOISE); - m_shaders->m_shBLURFINISH.setUniformFloat(SHADER_BRIGHTNESS, *PBLURBRIGHTNESS); + auto shader = useShader(getShaderVariant(SH_FRAG_BLURFINISH)); + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniformFloat(SHADER_NOISE, *PBLURNOISE); + shader->setUniformFloat(SHADER_BRIGHTNESS, *PBLURBRIGHTNESS); - m_shaders->m_shBLURFINISH.setUniformInt(SHADER_TEX, 0); + shader->setUniformInt(SHADER_TEX, 0); - glBindVertexArray(m_shaders->m_shBLURFINISH.uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); if (!damage.empty()) { damage.forEachRect([this](const auto& RECT) { @@ -2194,13 +1900,13 @@ void CHyprOpenGLImpl::preRender(PHLMONITOR pMonitor) { if (!pWindow) return false; - if (pWindow->m_windowData.noBlur.valueOrDefault()) + 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(); @@ -2238,7 +1944,7 @@ void CHyprOpenGLImpl::preRender(PHLMONITOR pMonitor) { for (auto const& m : g_pCompositor->m_monitors) { for (auto const& lsl : m->m_layerSurfaceLayers) { for (auto const& ls : lsl) { - if (!ls->m_layerSurface || ls->m_xray != 1) + if (!ls->m_layerSurface || ls->m_ruleApplicator->xray().valueOrDefault() != 1) continue; // if (ls->layerSurface->surface->opaque && ls->alpha->value() >= 1.f) @@ -2269,9 +1975,12 @@ void CHyprOpenGLImpl::preBlurForCurrentMonitor() { const auto POUTFB = blurMainFramebufferWithDamage(1, &fakeDamage); // render onto blurFB - m_renderData.pCurrentMonData->blurFB.alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y, - m_renderData.pMonitor->m_output->state->state().drmFormat); - m_renderData.pCurrentMonData->blurFB.bind(); + if (!m_renderData.pCurrentMonData->blurFB) + m_renderData.pCurrentMonData->blurFB = g_pHyprRenderer->createFB(); + + m_renderData.pCurrentMonData->blurFB->alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y, + m_renderData.pMonitor->m_output->state->state().drmFormat); + m_renderData.pCurrentMonData->blurFB->bind(); clear(CHyprColor(0, 0, 0, 0)); @@ -2307,29 +2016,31 @@ bool CHyprOpenGLImpl::shouldUseNewBlurOptimizations(PHLLS pLayer, PHLWINDOW pWin static auto PBLURNEWOPTIMIZE = CConfigValue("decoration:blur:new_optimizations"); static auto PBLURXRAY = CConfigValue("decoration:blur:xray"); - if (!m_renderData.pCurrentMonData->blurFB.getTexture()) + if (!m_renderData.pCurrentMonData->blurFB || !m_renderData.pCurrentMonData->blurFB->getTexture()) return false; - if (pWindow && pWindow->m_windowData.xray.hasValue() && !pWindow->m_windowData.xray.valueOrDefault()) + if (pWindow && pWindow->m_ruleApplicator->xray().hasValue() && !pWindow->m_ruleApplicator->xray().valueOrDefault()) return false; - if (pLayer && pLayer->m_xray == 0) + 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_xray == 1) || (pWindow && pWindow->m_windowData.xray.valueOrDefault())) + 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) { RASSERT(m_renderData.pMonitor, "Tried to render texture with blur without begin()!"); TRACY_GPU_ZONE("RenderTextureWithBlur"); + static auto PBLEND = CConfigValue("render:use_shader_blur_blend"); + // make a damage region for this window CRegion texDamage{m_renderData.damage}; texDamage.intersect(box.x, box.y, box.width, box.height); @@ -2363,107 +2074,112 @@ void CHyprOpenGLImpl::renderTextureWithBlurInternal(SP tex, const CBox inverseOpaque.scale(m_renderData.pMonitor->m_scale); // vvv TODO: layered blur fbs? - const bool USENEWOPTIMIZE = shouldUseNewBlurOptimizations(m_renderData.currentLS.lock(), m_renderData.currentWindow.lock()) && !data.blockBlurOptimization; + const bool USENEWOPTIMIZE = shouldUseNewBlurOptimizations(m_renderData.currentLS.lock(), m_renderData.currentWindow.lock()) && !data.blockBlurOptimization; - CFramebuffer* POUTFB = nullptr; + SP POUTFB = nullptr; if (!USENEWOPTIMIZE) { inverseOpaque.translate(box.pos()); m_renderData.renderModif.applyToRegion(inverseOpaque); inverseOpaque.intersect(texDamage); POUTFB = blurMainFramebufferWithDamage(data.a, &inverseOpaque); } else - POUTFB = &m_renderData.pCurrentMonData->blurFB; + POUTFB = m_renderData.pCurrentMonData->blurFB; m_renderData.currentFB->bind(); - // make a stencil for rounded corners to work with blur - scissor(nullptr); // allow the entire window and stencil to render - glClearStencil(0); - glClear(GL_STENCIL_BUFFER_BIT); + auto blurredBG = POUTFB->getTexture(); - setCapStatus(GL_STENCIL_TEST, true); + const auto NEEDS_STENCIL = m_renderData.discardMode != 0 && (!data.blockBlurOptimization || (m_renderData.discardMode & DISCARD_ALPHA)); + if (!*PBLEND) { - glStencilFunc(GL_ALWAYS, 1, 0xFF); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + if (NEEDS_STENCIL) { + scissor(nullptr); // allow the entire window and stencil to render + glClearStencil(0); + glClear(GL_STENCIL_BUFFER_BIT); - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - if (USENEWOPTIMIZE && !(m_renderData.discardMode & DISCARD_ALPHA)) - renderRect(box, CHyprColor(0, 0, 0, 0), SRectRenderData{.round = data.round, .roundingPower = data.roundingPower}); - else - renderTexture(tex, box, - STextureRenderData{.a = data.a, - .round = data.round, - .roundingPower = data.roundingPower, - .discardActive = true, - .allowCustomUV = true, - .wrapX = data.wrapX, - .wrapY = data.wrapY}); // discard opaque - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + setCapStatus(GL_STENCIL_TEST, true); - glStencilFunc(GL_EQUAL, 1, 0xFF); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + glStencilFunc(GL_ALWAYS, 1, 0xFF); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - // stencil done. Render everything. - const auto LASTTL = m_renderData.primarySurfaceUVTopLeft; - const auto LASTBR = m_renderData.primarySurfaceUVBottomRight; + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + renderTexture(tex, box, + STextureRenderData{.a = data.a, + .round = data.round, + .roundingPower = data.roundingPower, + .discardActive = true, + .allowCustomUV = true, + .wrapX = data.wrapX, + .wrapY = data.wrapY}); // discard opaque and alpha < discardOpacity - CBox transformedBox = box; - transformedBox.transform(wlTransformToHyprutils(invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, - m_renderData.pMonitor->m_transformedSize.y); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - CBox monitorSpaceBox = {transformedBox.pos().x / m_renderData.pMonitor->m_pixelSize.x * m_renderData.pMonitor->m_transformedSize.x, - transformedBox.pos().y / m_renderData.pMonitor->m_pixelSize.y * m_renderData.pMonitor->m_transformedSize.y, - transformedBox.width / m_renderData.pMonitor->m_pixelSize.x * m_renderData.pMonitor->m_transformedSize.x, - transformedBox.height / m_renderData.pMonitor->m_pixelSize.y * m_renderData.pMonitor->m_transformedSize.y}; + glStencilFunc(GL_EQUAL, 1, 0xFF); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + } - m_renderData.primarySurfaceUVTopLeft = monitorSpaceBox.pos() / m_renderData.pMonitor->m_transformedSize; - m_renderData.primarySurfaceUVBottomRight = (monitorSpaceBox.pos() + monitorSpaceBox.size()) / m_renderData.pMonitor->m_transformedSize; + // stencil done. Render everything. + const auto LASTTL = m_renderData.primarySurfaceUVTopLeft; + const auto LASTBR = m_renderData.primarySurfaceUVBottomRight; - static auto PBLURIGNOREOPACITY = CConfigValue("decoration:blur:ignore_opacity"); - pushMonitorTransformEnabled(true); - if (!USENEWOPTIMIZE) - setRenderModifEnabled(false); - renderTextureInternal(POUTFB->getTexture(), box, - STextureRenderData{ - .damage = &texDamage, - .a = (*PBLURIGNOREOPACITY ? data.blurA : data.a * data.blurA) * data.overallA, - .round = data.round, - .roundingPower = data.roundingPower, - .discardActive = false, - .allowCustomUV = true, - .noAA = false, - .wrapX = data.wrapX, - .wrapY = data.wrapY, - }); - if (!USENEWOPTIMIZE) - setRenderModifEnabled(true); - popMonitorTransformEnabled(); + CBox transformedBox = box; + transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, + m_renderData.pMonitor->m_transformedSize.y); - m_renderData.primarySurfaceUVTopLeft = LASTTL; - m_renderData.primarySurfaceUVBottomRight = LASTBR; + CBox monitorSpaceBox = {transformedBox.pos().x / m_renderData.pMonitor->m_pixelSize.x * m_renderData.pMonitor->m_transformedSize.x, + transformedBox.pos().y / m_renderData.pMonitor->m_pixelSize.y * m_renderData.pMonitor->m_transformedSize.y, + transformedBox.width / m_renderData.pMonitor->m_pixelSize.x * m_renderData.pMonitor->m_transformedSize.x, + transformedBox.height / m_renderData.pMonitor->m_pixelSize.y * m_renderData.pMonitor->m_transformedSize.y}; - // render the window, but clear stencil - glClearStencil(0); - glClear(GL_STENCIL_BUFFER_BIT); + m_renderData.primarySurfaceUVTopLeft = monitorSpaceBox.pos() / m_renderData.pMonitor->m_transformedSize; + m_renderData.primarySurfaceUVBottomRight = (monitorSpaceBox.pos() + monitorSpaceBox.size()) / m_renderData.pMonitor->m_transformedSize; + + static auto PBLURIGNOREOPACITY = CConfigValue("decoration:blur:ignore_opacity"); + pushMonitorTransformEnabled(true); + bool renderModif = m_renderData.renderModif.enabled; + if (!USENEWOPTIMIZE) + setRenderModifEnabled(false); + renderTextureInternal(blurredBG, box, + STextureRenderData{ + .damage = &texDamage, + .a = (*PBLURIGNOREOPACITY ? data.blurA : data.a * data.blurA) * data.overallA, + .round = data.round, + .roundingPower = data.roundingPower, + .discardActive = false, + .allowCustomUV = true, + .noAA = false, + .wrapX = data.wrapX, + .wrapY = data.wrapY, + }); + if (!USENEWOPTIMIZE) + setRenderModifEnabled(renderModif); + popMonitorTransformEnabled(); + + if (NEEDS_STENCIL) + setCapStatus(GL_STENCIL_TEST, false); + + m_renderData.primarySurfaceUVTopLeft = LASTTL; + m_renderData.primarySurfaceUVBottomRight = LASTBR; + } // draw window - setCapStatus(GL_STENCIL_TEST, false); renderTextureInternal(tex, box, STextureRenderData{ .damage = &texDamage, .a = data.a * data.overallA, + .blur = *PBLEND, .round = data.round, .roundingPower = data.roundingPower, - .discardActive = false, + .discardActive = *PBLEND && NEEDS_STENCIL, .allowCustomUV = true, .allowDim = true, .noAA = false, .wrapX = data.wrapX, .wrapY = data.wrapY, + .blurredBG = blurredBG, }); - glStencilMask(0xFF); - glStencilFunc(GL_ALWAYS, 1, 0xFF); + GLFB(m_renderData.currentFB)->invalidate({GL_STENCIL_ATTACHMENT}); scissor(nullptr); } @@ -2473,7 +2189,7 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr TRACY_GPU_ZONE("RenderBorder"); - if (m_renderData.damage.empty() || (m_renderData.currentWindow && m_renderData.currentWindow->m_windowData.noBorder.valueOrDefault())) + if (m_renderData.damage.empty()) return; CBox newBox = box; @@ -2494,42 +2210,45 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr 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); + newBox, Math::wlTransformToHyprutils(Math::invertTransform(!m_monitorTransformEnabled ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform)), newBox.rot); Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); 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()->id() == DEFAULT_IMAGE_DESCRIPTION->id(); + if (!skipCM) { + shader = useShader(getShaderVariant(SH_FRAG_BORDER1, SH_FEAT_ROUNDING | SH_FEAT_CM | (IS_ICC ? SH_FEAT_ICC : SH_FEAT_TONEMAP | SH_FEAT_SDR_MOD))); + passCMUniforms(shader, DEFAULT_IMAGE_DESCRIPTION); + } else + shader = useShader(getShaderVariant(SH_FRAG_BORDER1, SH_FEAT_ROUNDING)); - m_shaders->m_shBORDER1.setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - m_shaders->m_shBORDER1.setUniform4fv(SHADER_GRADIENT, grad.m_colorsOkLabA.size() / 4, grad.m_colorsOkLabA); - m_shaders->m_shBORDER1.setUniformInt(SHADER_GRADIENT_LENGTH, grad.m_colorsOkLabA.size() / 4); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_ANGLE, sc(grad.m_angle / (std::numbers::pi / 180.0)) % 360 * (std::numbers::pi / 180.0)); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_ALPHA, data.a); - m_shaders->m_shBORDER1.setUniformInt(SHADER_GRADIENT2_LENGTH, 0); + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniform4fv(SHADER_GRADIENT, grad.m_colorsOkLabA.size() / 4, grad.m_colorsOkLabA); + shader->setUniformInt(SHADER_GRADIENT_LENGTH, grad.m_colorsOkLabA.size() / 4); + shader->setUniformFloat(SHADER_ANGLE, sc(grad.m_angle / (std::numbers::pi / 180.0)) % 360 * (std::numbers::pi / 180.0)); + shader->setUniformFloat(SHADER_ALPHA, data.a); + shader->setUniformInt(SHADER_GRADIENT2_LENGTH, 0); CBox transformedBox = newBox; - transformedBox.transform(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 @@ -2541,7 +2260,7 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr if (!borderRegion.empty()) { borderRegion.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } @@ -2557,7 +2276,7 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr TRACY_GPU_ZONE("RenderBorder2"); - if (m_renderData.damage.empty() || (m_renderData.currentWindow && m_renderData.currentWindow->m_windowData.noBorder.valueOrDefault())) + if (m_renderData.damage.empty()) return; CBox newBox = box; @@ -2578,46 +2297,48 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr 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); + newBox, Math::wlTransformToHyprutils(Math::invertTransform(!m_monitorTransformEnabled ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform)), newBox.rot); Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); const auto BLEND = m_blend; blend(true); - useProgram(m_shaders->m_shBORDER1.program); + WP shader; + const bool IS_ICC = g_pHyprRenderer->workBufferImageDescription()->value().icc.present; + const bool skipCM = !m_cmSupported || g_pHyprRenderer->workBufferImageDescription()->id() == DEFAULT_IMAGE_DESCRIPTION->id(); + if (!skipCM) { + shader = useShader(getShaderVariant(SH_FRAG_BORDER1, SH_FEAT_ROUNDING | SH_FEAT_CM | (IS_ICC ? SH_FEAT_ICC : SH_FEAT_TONEMAP | SH_FEAT_SDR_MOD))); + passCMUniforms(shader, DEFAULT_IMAGE_DESCRIPTION); + } else + shader = useShader(getShaderVariant(SH_FRAG_BORDER1, SH_FEAT_ROUNDING)); - const bool skipCM = !m_cmSupported || m_renderData.pMonitor->m_imageDescription == 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 @@ -2629,7 +2350,7 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr if (!borderRegion.empty()) { borderRegion.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } @@ -2641,7 +2362,6 @@ 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) { 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()) return; @@ -2658,34 +2378,34 @@ 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); + newBox, Math::wlTransformToHyprutils(Math::invertTransform(!m_monitorTransformEnabled ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform)), newBox.rot); Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); 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()->id() == DEFAULT_IMAGE_DESCRIPTION->id(); + auto shader = useShader(getShaderVariant(SH_FRAG_SHADOW, skipCM ? 0 : SH_FEAT_CM | (IS_ICC ? SH_FEAT_ICC : SH_FEAT_TONEMAP | SH_FEAT_SDR_MOD))); if (!skipCM) - passCMUniforms(m_shaders->m_shSHADOW, SImageDescription{}); + passCMUniforms(shader, DEFAULT_IMAGE_DESCRIPTION); - m_shaders->m_shSHADOW.setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - m_shaders->m_shSHADOW.setUniformFloat4(SHADER_COLOR, col.r, col.g, col.b, col.a * a); + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniformFloat4(SHADER_COLOR, col.r, col.g, col.b, col.a * a); const auto TOPLEFT = Vector2D(range + round, range + round); const auto BOTTOMRIGHT = Vector2D(newBox.width - (range + round), newBox.height - (range + round)); const auto FULLSIZE = Vector2D(newBox.width, newBox.height); // Rounded corners - m_shaders->m_shSHADOW.setUniformFloat2(SHADER_TOP_LEFT, sc(TOPLEFT.x), sc(TOPLEFT.y)); - m_shaders->m_shSHADOW.setUniformFloat2(SHADER_BOTTOM_RIGHT, sc(BOTTOMRIGHT.x), sc(BOTTOMRIGHT.y)); - m_shaders->m_shSHADOW.setUniformFloat2(SHADER_FULL_SIZE, sc(FULLSIZE.x), sc(FULLSIZE.y)); - m_shaders->m_shSHADOW.setUniformFloat(SHADER_RADIUS, range + round); - m_shaders->m_shSHADOW.setUniformFloat(SHADER_ROUNDING_POWER, roundingPower); - m_shaders->m_shSHADOW.setUniformFloat(SHADER_RANGE, range); - m_shaders->m_shSHADOW.setUniformFloat(SHADER_SHADOW_POWER, SHADOWPOWER); + shader->setUniformFloat2(SHADER_TOP_LEFT, sc(TOPLEFT.x), sc(TOPLEFT.y)); + shader->setUniformFloat2(SHADER_BOTTOM_RIGHT, sc(BOTTOMRIGHT.x), sc(BOTTOMRIGHT.y)); + shader->setUniformFloat2(SHADER_FULL_SIZE, sc(FULLSIZE.x), sc(FULLSIZE.y)); + shader->setUniformFloat(SHADER_RADIUS, range + round); + shader->setUniformFloat(SHADER_ROUNDING_POWER, roundingPower); + shader->setUniformFloat(SHADER_RANGE, range); + shader->setUniformFloat(SHADER_SHADOW_POWER, SHADOWPOWER); - glBindVertexArray(m_shaders->m_shSHADOW.uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); if (m_renderData.clipBox.width != 0 && m_renderData.clipBox.height != 0) { CRegion damageClip{m_renderData.clipBox.x, m_renderData.clipBox.y, m_renderData.clipBox.width, m_renderData.clipBox.height}; @@ -2693,13 +2413,13 @@ void CHyprOpenGLImpl::renderRoundedShadow(const CBox& box, int round, float roun if (!damageClip.empty()) { damageClip.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } } else { m_renderData.damage.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } @@ -2708,21 +2428,25 @@ void CHyprOpenGLImpl::renderRoundedShadow(const CBox& box, int round, float roun } void CHyprOpenGLImpl::saveBufferForMirror(const CBox& box) { + if (!m_renderData.pCurrentMonData->monitorMirrorFB) + m_renderData.pCurrentMonData->monitorMirrorFB = g_pHyprRenderer->createFB(); - if (!m_renderData.pCurrentMonData->monitorMirrorFB.isAllocated()) - m_renderData.pCurrentMonData->monitorMirrorFB.alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y, - m_renderData.pMonitor->m_output->state->state().drmFormat); + if (!m_renderData.pCurrentMonData->monitorMirrorFB->isAllocated()) + m_renderData.pCurrentMonData->monitorMirrorFB->alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y, + m_renderData.pMonitor->m_output->state->state().drmFormat); - m_renderData.pCurrentMonData->monitorMirrorFB.bind(); + m_renderData.pCurrentMonData->monitorMirrorFB->bind(); blend(false); renderTexture(m_renderData.currentFB->getTexture(), box, STextureRenderData{ - .a = 1.f, + .damage = &m_renderData.finalDamage, + .a = 1.F, .round = 0, .discardActive = false, .allowCustomUV = false, + .cmBackToSRGB = true, }); blend(true); @@ -2739,12 +2463,12 @@ void CHyprOpenGLImpl::renderMirrored() { 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.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 PFB = &m_monitorRenderResources[mirrored].monitorMirrorFB; + auto PFB = m_monitorRenderResources[mirrored].monitorMirrorFB; if (!PFB->isAllocated() || !PFB->getTexture()) return; @@ -2755,8 +2479,8 @@ void CHyprOpenGLImpl::renderMirrored() { data.box = monbox; data.replaceProjection = Mat3x3::identity() .translate(monitor->m_pixelSize / 2.0) - .transform(wlTransformToHyprutils(monitor->m_transform)) - .transform(wlTransformToHyprutils(invertTransform(mirrored->m_transform))) + .transform(Math::wlTransformToHyprutils(monitor->m_transform)) + .transform(Math::wlTransformToHyprutils(Math::invertTransform(mirrored->m_transform))) .translate(-monitor->m_transformedSize / 2.0); g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(data))); @@ -2806,19 +2530,19 @@ std::string CHyprOpenGLImpl::resolveAssetPath(const std::string& filename) { fullPath = p; break; } else - Debug::log(LOG, "resolveAssetPath: looking at {} unsuccessful: ec {}", filename, ec.message()); + Log::logger->log(Log::DEBUG, "resolveAssetPath: looking at {} unsuccessful: ec {}", filename, ec.message()); } if (fullPath.empty()) { m_failedAssetsNo++; - Debug::log(ERR, "resolveAssetPath: looking for {} failed (no provider found)", filename); + Log::logger->log(Log::ERR, "resolveAssetPath: looking for {} failed (no provider found)", filename); return ""; } return fullPath; } -SP CHyprOpenGLImpl::loadAsset(const std::string& filename) { +SP CHyprOpenGLImpl::loadAsset(const std::string& filename) { const std::string fullPath = resolveAssetPath(filename); @@ -2829,7 +2553,7 @@ SP CHyprOpenGLImpl::loadAsset(const std::string& filename) { if (!CAIROSURFACE) { m_failedAssetsNo++; - Debug::log(ERR, "loadAsset: failed to load {} (corrupt / inaccessible / not png)", fullPath); + Log::logger->log(Log::ERR, "loadAsset: failed to load {} (corrupt / inaccessible / not png)", fullPath); return m_missingAssetTexture; } @@ -2840,12 +2564,11 @@ SP CHyprOpenGLImpl::loadAsset(const std::string& filename) { return tex; } -SP CHyprOpenGLImpl::texFromCairo(cairo_surface_t* cairo) { +SP CHyprOpenGLImpl::texFromCairo(cairo_surface_t* cairo) { const auto CAIROFORMAT = cairo_image_surface_get_format(cairo); - auto tex = makeShared(); + auto tex = makeShared(); - tex->allocate(); - tex->m_size = {cairo_image_surface_get_width(cairo), cairo_image_surface_get_height(cairo)}; + tex->allocate({cairo_image_surface_get_width(cairo), cairo_image_surface_get_height(cairo)}); const GLint glIFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB32F : GL_RGBA; const GLint glFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB : GL_RGBA; @@ -2866,8 +2589,8 @@ SP CHyprOpenGLImpl::texFromCairo(cairo_surface_t* cairo) { return tex; } -SP CHyprOpenGLImpl::renderText(const std::string& text, CHyprColor col, int pt, bool italic, const std::string& fontFamily, int maxWidth, int weight) { - SP tex = makeShared(); +SP CHyprOpenGLImpl::renderText(const std::string& text, CHyprColor col, int pt, bool italic, const std::string& fontFamily, int maxWidth, int weight) { + SP tex = makeShared(); static auto FONT = CConfigValue("misc:font_family"); @@ -2929,8 +2652,7 @@ SP CHyprOpenGLImpl::renderText(const std::string& text, CHyprColor col cairo_surface_flush(CAIROSURFACE); - tex->allocate(); - tex->m_size = {cairo_image_surface_get_width(CAIROSURFACE), cairo_image_surface_get_height(CAIROSURFACE)}; + tex->allocate({cairo_image_surface_get_width(CAIROSURFACE), cairo_image_surface_get_height(CAIROSURFACE)}); const auto DATA = cairo_image_surface_get_data(CAIROSURFACE); tex->bind(); @@ -2947,8 +2669,8 @@ SP CHyprOpenGLImpl::renderText(const std::string& text, CHyprColor col } void CHyprOpenGLImpl::initMissingAssetTexture() { - SP tex = makeShared(); - tex->allocate(); + SP tex = makeShared(); + tex->allocate({512, 512}); const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 512, 512); const auto CAIRO = cairo_create(CAIROSURFACE); @@ -2987,12 +2709,14 @@ void CHyprOpenGLImpl::initMissingAssetTexture() { m_missingAssetTexture = tex; } -void CHyprOpenGLImpl::useProgram(GLuint prog) { - if (m_currentProgram == prog) - return; +WP CHyprOpenGLImpl::useShader(WP prog) { + if (m_currentProgram == prog->program()) + return prog; - glUseProgram(prog); - m_currentProgram = prog; + glUseProgram(prog->program()); + m_currentProgram = prog->program(); + + return prog; } void CHyprOpenGLImpl::initAssets() { @@ -3081,7 +2805,7 @@ void CHyprOpenGLImpl::requestBackgroundResource() { void CHyprOpenGLImpl::createBGTextureForMonitor(PHLMONITOR pMonitor) { RASSERT(m_renderData.pMonitor, "Tried to createBGTex without begin()!"); - Debug::log(LOG, "Creating a texture for BGTex"); + Log::logger->log(Log::DEBUG, "Creating a texture for BGTex"); static auto PRENDERTEX = CConfigValue("misc:disable_hyprland_logo"); static auto PNOSPLASH = CConfigValue("misc:disable_splash_rendering"); @@ -3098,16 +2822,19 @@ void CHyprOpenGLImpl::createBGTextureForMonitor(PHLMONITOR pMonitor) { if (!m_backgroundResource->m_ready) return; + if (!m_monitorBGFBs.contains(pMonitor)) + m_monitorBGFBs[pMonitor] = g_pHyprRenderer->createFB(); + // release the last tex if exists - const auto PFB = &m_monitorBGFBs[pMonitor]; + auto PFB = m_monitorBGFBs[pMonitor]; PFB->release(); PFB->alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, pMonitor->m_output->state->state().drmFormat); // create a new one with cairo - SP tex = makeShared(); + SP tex = makeShared(); - tex->allocate(); + tex->allocate(pMonitor->m_pixelSize); const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y); const auto CAIRO = cairo_create(CAIROSURFACE); @@ -3149,7 +2876,7 @@ void CHyprOpenGLImpl::createBGTextureForMonitor(PHLMONITOR pMonitor) { blend(true); clear(CHyprColor{0, 0, 0, 1}); - SP backgroundTexture = texFromCairo(m_backgroundResource->m_asset.cairoSurface->cairo()); + SP backgroundTexture = texFromCairo(m_backgroundResource->m_asset.cairoSurface->cairo()); // first render the background if (backgroundTexture) { @@ -3177,7 +2904,7 @@ void CHyprOpenGLImpl::createBGTextureForMonitor(PHLMONITOR pMonitor) { if (m_renderData.currentFB) m_renderData.currentFB->bind(); - Debug::log(LOG, "Background created for monitor {}", pMonitor->m_name); + 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(); }); @@ -3204,7 +2931,7 @@ void CHyprOpenGLImpl::clearWithTex() { data.box = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}; data.a = m_renderData.pMonitor->m_backgroundOpacity->value(); data.flipEndFrame = true; - data.tex = TEXIT->second.getTexture(); + data.tex = TEXIT->second->getTexture(); g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(data))); } } @@ -3217,24 +2944,24 @@ void CHyprOpenGLImpl::destroyMonitorResources(PHLMONITORREF pMonitor) { auto RESIT = g_pHyprOpenGL->m_monitorRenderResources.find(pMonitor); if (RESIT != g_pHyprOpenGL->m_monitorRenderResources.end()) { - RESIT->second.mirrorFB.release(); - RESIT->second.offloadFB.release(); - RESIT->second.mirrorSwapFB.release(); - RESIT->second.monitorMirrorFB.release(); - RESIT->second.blurFB.release(); - RESIT->second.offMainFB.release(); - RESIT->second.stencilTex->destroyTexture(); + RESIT->second.mirrorFB.reset(); + RESIT->second.offloadFB.reset(); + RESIT->second.mirrorSwapFB.reset(); + RESIT->second.monitorMirrorFB.reset(); + RESIT->second.blurFB.reset(); + RESIT->second.offMainFB.reset(); + RESIT->second.stencilTex.reset(); g_pHyprOpenGL->m_monitorRenderResources.erase(RESIT); } auto TEXIT = g_pHyprOpenGL->m_monitorBGFBs.find(pMonitor); if (TEXIT != g_pHyprOpenGL->m_monitorBGFBs.end()) { - TEXIT->second.release(); + TEXIT->second.reset(); g_pHyprOpenGL->m_monitorBGFBs.erase(TEXIT); } 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() { @@ -3250,19 +2977,21 @@ void CHyprOpenGLImpl::restoreMatrix() { } void CHyprOpenGLImpl::bindOffMain() { - if (!m_renderData.pCurrentMonData->offMainFB.isAllocated()) { - m_renderData.pCurrentMonData->offMainFB.alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y, - m_renderData.pMonitor->m_output->state->state().drmFormat); + if (!m_renderData.pCurrentMonData->offMainFB) + m_renderData.pCurrentMonData->offMainFB = g_pHyprRenderer->createFB(); - m_renderData.pCurrentMonData->offMainFB.addStencil(m_renderData.pCurrentMonData->stencilTex); + if (!m_renderData.pCurrentMonData->offMainFB->isAllocated()) { + m_renderData.pCurrentMonData->offMainFB->addStencil(m_renderData.pCurrentMonData->stencilTex); + m_renderData.pCurrentMonData->offMainFB->alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y, + m_renderData.pMonitor->m_output->state->state().drmFormat); } - m_renderData.pCurrentMonData->offMainFB.bind(); + m_renderData.pCurrentMonData->offMainFB->bind(); clear(CHyprColor(0, 0, 0, 0)); - m_renderData.currentFB = &m_renderData.pCurrentMonData->offMainFB; + m_renderData.currentFB = m_renderData.pCurrentMonData->offMainFB; } -void CHyprOpenGLImpl::renderOffToMain(CFramebuffer* off) { +void CHyprOpenGLImpl::renderOffToMain(CGLFramebuffer* off) { CBox monbox = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}; renderTexturePrimitive(off->getTexture(), monbox); } @@ -3308,9 +3037,9 @@ void CHyprOpenGLImpl::setCapStatus(int cap, bool status) { if (idx == CAP_STATUS_END) { if (status) - glEnable(cap); + GLCALL(glEnable(cap)) else - glDisable(cap); + GLCALL(glDisable(cap)); return; } @@ -3320,31 +3049,62 @@ void CHyprOpenGLImpl::setCapStatus(int cap, bool status) { if (status) { m_capStatus[idx] = status; - glEnable(cap); + GLCALL(glEnable(cap)); } else { m_capStatus[idx] = status; - glDisable(cap); + GLCALL(glDisable(cap)); } } -uint32_t CHyprOpenGLImpl::getPreferredReadFormat(PHLMONITOR pMonitor) { +DRMFormat CHyprOpenGLImpl::getPreferredReadFormat(PHLMONITOR pMonitor) { static const auto PFORCE8BIT = CConfigValue("misc:screencopy_force_8b"); - if (!*PFORCE8BIT) - return pMonitor->m_output->state->state().drmFormat; + auto monFmt = pMonitor->m_output->state->state().drmFormat; - auto fmt = pMonitor->m_output->state->state().drmFormat; + if (*PFORCE8BIT) + if (monFmt == DRM_FORMAT_BGRA1010102 || monFmt == DRM_FORMAT_ARGB2101010 || monFmt == DRM_FORMAT_XRGB2101010 || monFmt == DRM_FORMAT_BGRX1010102 || + monFmt == DRM_FORMAT_XBGR2101010) + monFmt = DRM_FORMAT_XRGB8888; - if (fmt == DRM_FORMAT_BGRA1010102 || fmt == DRM_FORMAT_ARGB2101010 || fmt == DRM_FORMAT_XRGB2101010 || fmt == DRM_FORMAT_BGRX1010102 || fmt == DRM_FORMAT_XBGR2101010) - return DRM_FORMAT_XRGB8888; + return monFmt; +} - return fmt; +std::vector CHyprOpenGLImpl::getDRMFormatModifiers(DRMFormat drmFormat) { + SDRMFormat format; + + for (const auto& fmt : m_drmFormats) { + if (fmt.drmFormat == drmFormat) { + format = fmt; + break; + } + } + + return format.modifiers; } bool CHyprOpenGLImpl::explicitSyncSupported() { return m_exts.EGL_ANDROID_native_fence_sync_ext; } +WP CHyprOpenGLImpl::getShaderVariant(ePreparedFragmentShader frag, ShaderFeatureFlags features) { + if (!m_shaders->fragVariants[frag].contains(features)) { + auto shader = makeShared(); + + Log::logger->log(Log::INFO, "compiling feature set {} for {}", features, FRAG_SHADERS[frag]); + + const auto fragSrc = g_pShaderLoader->getVariantSource(frag, features); + + if (!shader->createProgram(m_shaders->TEXVERTSRC, fragSrc, true, true)) + Log::logger->log(Log::ERR, "shader features {} failed for {}", features, FRAG_SHADERS[frag]); + + m_shaders->fragVariants[frag][features] = shader; + return shader; + } + + ASSERT(m_shaders->fragVariants[frag][features]); + return m_shaders->fragVariants[frag][features]; +} + std::vector CHyprOpenGLImpl::getDRMFormats() { return m_drmFormats; } @@ -3370,7 +3130,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!"); } } } @@ -3387,7 +3147,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!"); } } } @@ -3405,7 +3165,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; } @@ -3415,8 +3175,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; } @@ -3424,8 +3184,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; } @@ -3438,11 +3198,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"); + if UNLIKELY (g_pHyprOpenGL && g_pHyprOpenGL->m_proc.eglDestroySyncKHR(g_pHyprOpenGL->m_eglDisplay, m_sync) != EGL_TRUE) + Log::logger->log(Log::ERR, "eglDestroySyncKHR failed"); } CFileDescriptor& CEGLSync::fd() { diff --git a/src/render/OpenGL.hpp b/src/render/OpenGL.hpp index 45ed28dd..c9008447 100644 --- a/src/render/OpenGL.hpp +++ b/src/render/OpenGL.hpp @@ -20,6 +20,7 @@ #include "Texture.hpp" #include "Framebuffer.hpp" #include "Renderbuffer.hpp" +#include "desktop/DesktopTypes.hpp" #include "pass/Pass.hpp" #include @@ -31,17 +32,29 @@ #include "../debug/TracyDefines.hpp" #include "../protocols/core/Compositor.hpp" +#include "render/ShaderLoader.hpp" +#include "render/gl/GLFramebuffer.hpp" +#include "render/gl/GLRenderbuffer.hpp" +#include "render/gl/GLTexture.hpp" + +#define GLFB(ifb) dc(ifb.get()) struct gbm_device; -class CHyprRenderer; +class IHyprRenderer; -inline const float fullVerts[] = { - 1, 0, // top right - 0, 0, // top left - 1, 1, // bottom right - 0, 1, // bottom left +struct SVertex { + float x, y; // position + float u, v; // uv }; -inline const float fanVertsFull[] = {-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f}; + +constexpr std::array fullVerts = {{ + {0.0f, 0.0f, 0.0f, 0.0f}, // top-left + {0.0f, 1.0f, 0.0f, 1.0f}, // bottom-left + {1.0f, 0.0f, 1.0f, 0.0f}, // top-right + {1.0f, 1.0f, 1.0f, 1.0f}, // bottom-right +}}; + +inline const float fanVertsFull[] = {-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f}; enum eDiscardMode : uint8_t { DISCARD_OPAQUE = 1, @@ -81,37 +94,37 @@ enum eMonitorExtraRenderFBs : uint8_t { FB_MONITOR_RENDER_EXTRA_BLUR, }; +struct SFragShaderDesc { + Render::ePreparedFragmentShader id; + const char* file; +}; + struct SPreparedShaders { + // SPreparedShaders() { + // for (auto& f : frag) { + // f = makeShared(); + // } + // } + std::string TEXVERTSRC; std::string TEXVERTSRC320; - SShader m_shQUAD; - SShader m_shRGBA; - SShader m_shPASSTHRURGBA; - SShader m_shMATTE; - SShader m_shRGBX; - SShader m_shEXT; - SShader m_shBLUR1; - SShader m_shBLUR2; - SShader m_shBLURPREPARE; - SShader m_shBLURFINISH; - SShader m_shSHADOW; - SShader m_shBORDER1; - SShader m_shGLITCH; - SShader m_shCM; + // std::array, SH_FRAG_LAST> frag; + // std::map> fragVariants; + std::array>, Render::SH_FRAG_LAST> fragVariants; }; struct SMonitorRenderData { - CFramebuffer offloadFB; - CFramebuffer mirrorFB; // these are used for some effects, - CFramebuffer mirrorSwapFB; // etc - CFramebuffer offMainFB; - CFramebuffer monitorMirrorFB; // used for mirroring outputs, does not contain artifacts like offloadFB - CFramebuffer blurFB; + SP offloadFB; + SP mirrorFB; // these are used for some effects, + SP mirrorSwapFB; // etc + SP offMainFB; + SP monitorMirrorFB; // used for mirroring outputs, does not contain artifacts like offloadFB + SP blurFB; - SP stencilTex = makeShared(); + SP stencilTex = makeShared(); - bool blurFBDirty = true; - bool blurFBShouldRender = false; + bool blurFBDirty = true; + bool blurFBShouldRender = false; }; struct SCurrentRenderData { @@ -122,9 +135,9 @@ struct SCurrentRenderData { // FIXME: raw pointer galore! SMonitorRenderData* pCurrentMonData = nullptr; - CFramebuffer* currentFB = nullptr; // current rendering to - CFramebuffer* mainFB = nullptr; // main to render to - CFramebuffer* outFB = nullptr; // out to render to (if offloaded, etc) + SP currentFB = nullptr; // current rendering to + SP mainFB = nullptr; // main to render to + SP outFB = nullptr; // out to render to (if offloaded, etc) CRegion damage; CRegion finalDamage; // damage used for funal off -> main @@ -135,6 +148,8 @@ struct SCurrentRenderData { bool useNearestNeighbor = false; bool blockScreenShader = false; bool simplePass = false; + bool transformDamage = true; + bool noSimplify = false; Vector2D primarySurfaceUVTopLeft = Vector2D(-1, -1); Vector2D primarySurfaceUVBottomRight = Vector2D(-1, -1); @@ -200,8 +215,11 @@ class CHyprOpenGLImpl { bool noAA = false; bool blockBlurOptimization = false; GLenum wrapX = GL_CLAMP_TO_EDGE, wrapY = GL_CLAMP_TO_EDGE; - bool cmBackToSRGB = false; + bool cmBackToSRGB = false; + bool noCM = false; + bool finalMonitorCM = false; SP cmBackToSRGBSource; + SP blurredBG; }; struct SBorderRenderData { @@ -212,16 +230,17 @@ class CHyprOpenGLImpl { int outerRound = -1; /* use round */ }; - void begin(PHLMONITOR, const CRegion& damage, CFramebuffer* fb = nullptr, std::optional finalDamage = {}); - void beginSimple(PHLMONITOR, const CRegion& damage, SP rb = nullptr, CFramebuffer* fb = nullptr); + void begin(PHLMONITOR, const CRegion& damage, SP fb = nullptr, std::optional finalDamage = {}); + void beginSimple(PHLMONITOR, const CRegion& damage, SP rb = nullptr, SP fb = nullptr); void end(); void renderRect(const CBox&, const CHyprColor&, SRectRenderData data); - void renderTexture(SP, const CBox&, STextureRenderData data); + void renderTexture(SP, const CBox&, STextureRenderData data); void renderRoundedShadow(const CBox&, int round, float roundingPower, int range, const CHyprColor& color, float a = 1.0); void renderBorder(const CBox&, const CGradientValueData&, SBorderRenderData data); void renderBorder(const CBox&, const CGradientValueData&, const CGradientValueData&, float lerp, SBorderRenderData data); - void renderTextureMatte(SP tex, const CBox& pBox, CFramebuffer& matte); + void renderTextureMatte(SP tex, const CBox& pBox, SP matte); + void renderTexturePrimitive(SP tex, const CBox& box); void pushMonitorTransformEnabled(bool enabled); void popMonitorTransformEnabled(); @@ -258,49 +277,49 @@ class CHyprOpenGLImpl { void applyScreenShader(const std::string& path); void bindOffMain(); - void renderOffToMain(CFramebuffer* off); + void renderOffToMain(CGLFramebuffer* off); void bindBackOnMain(); + bool needsACopyFB(PHLMONITOR mon); + std::string resolveAssetPath(const std::string& file); - SP loadAsset(const std::string& file); - SP texFromCairo(cairo_surface_t* cairo); - SP renderText(const std::string& text, CHyprColor col, int pt, bool italic = false, const std::string& fontFamily = "", int maxWidth = 0, int weight = 400); + SP loadAsset(const std::string& file); + SP texFromCairo(cairo_surface_t* cairo); + SP renderText(const std::string& text, CHyprColor col, int pt, bool italic = false, const std::string& fontFamily = "", int maxWidth = 0, int weight = 400); void setDamage(const CRegion& damage, std::optional finalDamage = {}); - uint32_t getPreferredReadFormat(PHLMONITOR pMonitor); - std::vector getDRMFormats(); - EGLImageKHR createEGLImage(const Aquamarine::SDMABUFAttrs& attrs); + DRMFormat getPreferredReadFormat(PHLMONITOR pMonitor); + std::vector getDRMFormats(); + std::vector getDRMFormatModifiers(DRMFormat format); + EGLImageKHR createEGLImage(const Aquamarine::SDMABUFAttrs& attrs); - bool initShaders(); + bool initShaders(const std::string& path = ""); - GLuint createProgram(const std::string&, const std::string&, bool dynamic = false, bool silent = false); - GLuint compileShader(const GLuint&, std::string, bool dynamic = false, bool silent = false); - void useProgram(GLuint prog); + WP useShader(WP prog); - void ensureLockTexturesRendered(bool load); + bool explicitSyncSupported(); + WP getShaderVariant(Render::ePreparedFragmentShader frag, Render::ShaderFeatureFlags features = 0); - bool explicitSyncSupported(); + bool m_shadersInitialized = false; + SP m_shaders; - bool m_shadersInitialized = false; - SP m_shaders; + SCurrentRenderData m_renderData; - SCurrentRenderData m_renderData; + Hyprutils::OS::CFileDescriptor m_gbmFD; + gbm_device* m_gbmDevice = nullptr; + EGLContext m_eglContext = nullptr; + EGLDisplay m_eglDisplay = nullptr; + EGLDeviceEXT m_eglDevice = nullptr; + uint m_failedAssetsNo = 0; - Hyprutils::OS::CFileDescriptor m_gbmFD; - gbm_device* m_gbmDevice = nullptr; - EGLContext m_eglContext = nullptr; - EGLDisplay m_eglDisplay = nullptr; - EGLDeviceEXT m_eglDevice = nullptr; - uint m_failedAssetsNo = 0; + bool m_reloadScreenShader = true; // at launch it can be set - bool m_reloadScreenShader = true; // at launch it can be set - - std::map m_windowFramebuffers; - std::map m_layerFramebuffers; - std::map, CFramebuffer> m_popupFramebuffers; - std::map m_monitorRenderResources; - std::map m_monitorBGFBs; + std::map> m_windowFramebuffers; + std::map> m_layerFramebuffers; + std::map, SP> m_popupFramebuffers; + std::map m_monitorRenderResources; + std::map> m_monitorBGFBs; struct { PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES = nullptr; @@ -324,13 +343,14 @@ class CHyprOpenGLImpl { bool EXT_read_format_bgra = false; bool EXT_image_dma_buf_import = false; bool EXT_image_dma_buf_import_modifiers = false; + bool KHR_context_flush_control = false; bool KHR_display_reference = false; bool IMG_context_priority = false; bool EXT_create_context_robustness = false; bool EGL_ANDROID_native_fence_sync_ext = false; } m_exts; - SP m_screencopyDeniedTexture; + SP m_screencopyDeniedTexture; enum eEGLContextVersion : uint8_t { EGL_CONTEXT_GLES_2_0 = 0, @@ -355,7 +375,7 @@ class CHyprOpenGLImpl { GLsizei height = 0; } m_lastViewport; - std::array m_capStatus; + std::array m_capStatus = {}; std::vector m_drmFormats; bool m_hasModifiers = false; @@ -371,22 +391,22 @@ class CHyprOpenGLImpl { bool m_monitorTransformEnabled = false; // do not modify directly std::stack m_monitorTransformStack; - SP m_missingAssetTexture; - SP m_lockDeadTexture; - SP m_lockDead2Texture; - SP m_lockTtyTextTexture; - SShader m_finalScreenShader; + SP m_missingAssetTexture; + SP m_lockDeadTexture; + SP m_lockDead2Texture; + SP m_lockTtyTextTexture; + SP m_finalScreenShader; CTimer m_globalTimer; GLuint m_currentProgram; ASP m_backgroundResource; bool m_backgroundResourceFailed = false; - void logShaderError(const GLuint&, bool program = false, bool silent = false); void createBGTextureForMonitor(PHLMONITOR); void initDRMFormats(); void initEGL(bool gbm); EGLDeviceEXT eglDeviceFromDRMFD(int drmFD); void initAssets(); + void ensureLockTexturesRendered(bool load); void initMissingAssetTexture(); void requestBackgroundResource(); @@ -400,21 +420,22 @@ class CHyprOpenGLImpl { std::optional> getModsForFormat(EGLint format); // returns the out FB, can be either Mirror or MirrorSwap - CFramebuffer* blurMainFramebufferWithDamage(float a, CRegion* damage); - CFramebuffer* blurFramebufferWithDamage(float a, CRegion* damage, CFramebuffer& source); + SP blurMainFramebufferWithDamage(float a, CRegion* damage); + SP blurFramebufferWithDamage(float a, CRegion* damage, CGLFramebuffer& source); - void passCMUniforms(SShader&, const NColorManagement::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); + void passCMUniforms(WP, const NColorManagement::PImageDescription imageDescription, const NColorManagement::PImageDescription targetImageDescription, + bool modifySDR = false, float sdrMinLuminance = -1.0f, int sdrMaxLuminance = -1); + void passCMUniforms(WP, const NColorManagement::PImageDescription imageDescription); + void renderSplash(cairo_t* const, cairo_surface_t* const, double offset, const Vector2D& size); + void renderRectInternal(const CBox&, const CHyprColor&, const SRectRenderData& data); + void renderRectWithBlurInternal(const CBox&, const CHyprColor&, const SRectRenderData& data); + void renderRectWithDamageInternal(const CBox&, const CHyprColor&, const SRectRenderData& data); + WP renderToOutputInternal(); + WP renderToFBInternal(const STextureRenderData& data, eTextureType texType, const CBox& newBox); + void renderTextureInternal(SP, const CBox&, const STextureRenderData& data); + void renderTextureWithBlurInternal(SP, const CBox&, const STextureRenderData& data); - void preBlurForCurrentMonitor(); + void preBlurForCurrentMonitor(); friend class CHyprRenderer; friend class CTexPassElement; diff --git a/src/render/Renderbuffer.cpp b/src/render/Renderbuffer.cpp index d47a5195..bab4f73e 100644 --- a/src/render/Renderbuffer.cpp +++ b/src/render/Renderbuffer.cpp @@ -1,76 +1,21 @@ #include "Renderbuffer.hpp" -#include "Renderer.hpp" -#include "OpenGL.hpp" -#include "../Compositor.hpp" -#include "../protocols/types/Buffer.hpp" +#include "Framebuffer.hpp" +#include "render/Renderer.hpp" +#include "render/gl/GLRenderbuffer.hpp" +#include #include #include #include -CRenderbuffer::~CRenderbuffer() { - if (!g_pCompositor || g_pCompositor->m_isShuttingDown || !g_pHyprRenderer) - return; - - g_pHyprRenderer->makeEGLCurrent(); - - unbind(); - m_framebuffer.release(); - glDeleteRenderbuffers(1, &m_rbo); - - g_pHyprOpenGL->m_proc.eglDestroyImageKHR(g_pHyprOpenGL->m_eglDisplay, m_image); +IRenderbuffer::IRenderbuffer(SP buffer, uint32_t format) : m_hlBuffer(buffer) { + m_listeners.destroyBuffer = buffer->events.destroy.listen([this] { g_pHyprRenderer->onRenderbufferDestroy(dc(this)); }); } -CRenderbuffer::CRenderbuffer(SP buffer, uint32_t format) : m_hlBuffer(buffer), m_drmFormat(format) { - auto dma = buffer->dmabuf(); - - m_image = g_pHyprOpenGL->createEGLImage(dma); - if (m_image == EGL_NO_IMAGE_KHR) { - 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(); - - m_listeners.destroyBuffer = buffer->events.destroy.listen([this] { g_pHyprRenderer->onRenderbufferDestroy(this); }); - - m_good = true; -} - -bool CRenderbuffer::good() { +bool IRenderbuffer::good() { return m_good; } -void CRenderbuffer::bind() { - glBindRenderbuffer(GL_RENDERBUFFER, m_rbo); - bindFB(); -} - -void CRenderbuffer::bindFB() { - m_framebuffer.bind(); -} - -void CRenderbuffer::unbind() { - glBindRenderbuffer(GL_RENDERBUFFER, 0); - m_framebuffer.unbind(); -} - -CFramebuffer* CRenderbuffer::getFB() { - return &m_framebuffer; +SP IRenderbuffer::getFB() { + return m_framebuffer; } diff --git a/src/render/Renderbuffer.hpp b/src/render/Renderbuffer.hpp index c0924141..c33144d3 100644 --- a/src/render/Renderbuffer.hpp +++ b/src/render/Renderbuffer.hpp @@ -5,30 +5,24 @@ #include "Framebuffer.hpp" #include -class CMonitor; - -class CRenderbuffer { +class IRenderbuffer { public: - CRenderbuffer(SP buffer, uint32_t format); - ~CRenderbuffer(); + IRenderbuffer(SP buffer, uint32_t format); + virtual ~IRenderbuffer() = default; bool good(); - void bind(); - void bindFB(); - void unbind(); - CFramebuffer* getFB(); - uint32_t getFormat(); + SP getFB(); + + virtual void bind() = 0; + virtual void unbind() = 0; WP m_hlBuffer; - private: - void* m_image = nullptr; - GLuint m_rbo = 0; - CFramebuffer m_framebuffer; - uint32_t m_drmFormat = 0; - bool m_good = false; + protected: + SP m_framebuffer; + bool m_good = false; struct { CHyprSignalListener destroyBuffer; } m_listeners; -}; \ No newline at end of file +}; diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index c13a54d1..165f580a 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -3,17 +3,18 @@ #include "../helpers/math/Math.hpp" #include #include +#include #include #include "../config/ConfigValue.hpp" #include "../config/ConfigManager.hpp" #include "../managers/CursorManager.hpp" #include "../managers/PointerManager.hpp" #include "../managers/input/InputManager.hpp" -#include "../managers/HookSystemManager.hpp" #include "../managers/animation/AnimationManager.hpp" -#include "../managers/LayoutManager.hpp" -#include "../desktop/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" #include "../protocols/XDGShell.hpp" @@ -26,18 +27,36 @@ #include "../hyprerror/HyprError.hpp" #include "../debug/HyprDebugOverlay.hpp" #include "../debug/HyprNotificationOverlay.hpp" +#include "../layout/LayoutManager.hpp" +#include "../layout/space/Space.hpp" +#include "../i18n/Engine.hpp" +#include "desktop/DesktopTypes.hpp" +#include "../event/EventBus.hpp" #include "helpers/CursorShapes.hpp" +#include "helpers/MainLoopExecutor.hpp" #include "helpers/Monitor.hpp" +#include "macros.hpp" #include "pass/TexPassElement.hpp" #include "pass/ClearPassElement.hpp" #include "pass/RectPassElement.hpp" #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/AsyncResourceGatherer.hpp" +#include "render/Framebuffer.hpp" #include "render/OpenGL.hpp" +#include "render/Texture.hpp" +#include "render/gl/GLFramebuffer.hpp" +#include "render/gl/GLTexture.hpp" +#include +#include +#include +#include +#include +#include #include using namespace Hyprutils::Utils; @@ -73,14 +92,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); @@ -95,21 +114,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; @@ -117,17 +136,19 @@ 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()) return; @@ -137,6 +158,11 @@ CHyprRenderer::CHyprRenderer() { }); }); + 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); wl_event_source_timer_update(m_cursorTicker, 500); @@ -155,18 +181,18 @@ 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()); - FEEDBACK->attachMonitor(g_pCompositor->m_lastMonitor.lock()); + 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)); } if (dirty) - std::erase_if(m_renderUnfocused, [](const auto& e) { return !e || !e->m_windowData.renderUnfocused.valueOr(false); }); + std::erase_if(m_renderUnfocused, [](const auto& e) { return !e || !e->m_ruleApplicator->renderUnfocused().valueOr(false); }); if (!m_renderUnfocused.empty()) m_renderUnfocusedTimer->updateTimeout(std::chrono::milliseconds(1000 / *PFPS)); @@ -277,7 +303,7 @@ bool CHyprRenderer::shouldRenderWindow(PHLWINDOW pWindow) { void CHyprRenderer::renderWorkspaceWindowsFullscreen(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& time) { PHLWINDOW pWorkspaceWindow = nullptr; - EMIT_HOOK_EVENT("render", RENDER_PRE_WINDOWS); + Event::bus()->m_events.render.stage.emit(RENDER_PRE_WINDOWS); // loop over the tiled windows that are fading out for (auto const& w : g_pCompositor->m_windows) { @@ -368,7 +394,7 @@ void CHyprRenderer::renderWorkspaceWindowsFullscreen(PHLMONITOR pMonitor, PHLWOR void CHyprRenderer::renderWorkspaceWindows(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& time) { PHLWINDOW lastWindow; - EMIT_HOOK_EVENT("render", RENDER_PRE_WINDOWS); + Event::bus()->m_events.render.stage.emit(RENDER_PRE_WINDOWS); std::vector windows, tiledFadingOut; windows.reserve(g_pCompositor->m_windows.size()); @@ -395,7 +421,7 @@ void CHyprRenderer::renderWorkspaceWindows(PHLMONITOR pMonitor, PHLWORKSPACE pWo continue; // render active window after all others of this pass - if (w == g_pCompositor->m_lastWindow) { + if (w == Desktop::focusState()->window()) { lastWindow = w.lock(); continue; } @@ -507,8 +533,8 @@ 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.dontRound = pWindow->isEffectiveInternalFSMode(FSMODE_FULLSCREEN) || pWindow->m_windowData.noRounding.valueOrDefault(); + 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(); renderdata.alpha = pWindow->m_activeInactiveAlpha->value(); @@ -524,7 +550,7 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T } // apply opaque - if (pWindow->m_windowData.opaque.valueOrDefault()) + if (pWindow->m_ruleApplicator->opaque().valueOrDefault()) renderdata.alpha = 1.f; renderdata.pWindow = pWindow; @@ -532,11 +558,11 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T // for plugins g_pHyprOpenGL->m_renderData.currentWindow = pWindow; - EMIT_HOOK_EVENT("render", RENDER_PRE_WINDOW); + Event::bus()->m_events.render.stage.emit(RENDER_PRE_WINDOW); const auto fullAlpha = renderdata.alpha * renderdata.fadeAlpha; - if (*PDIMAROUND && pWindow->m_windowData.dimAround.valueOrDefault() && !m_bRenderingSnapshot && mode != RENDER_PASS_POPUP) { + 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}; CRectPassElement::SRectData data; data.color = CHyprColor(0, 0, 0, *PDIMAROUND * fullAlpha); @@ -584,10 +610,10 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T } static auto PXWLUSENN = CConfigValue("xwayland:use_nearest_neighbor"); - if ((pWindow->m_isX11 && *PXWLUSENN) || pWindow->m_windowData.nearestNeighbor.valueOrDefault()) + 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; @@ -602,12 +628,18 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T } 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; + + if (s->m_current.size.x < 1 || s->m_current.size.y < 1) + return; + 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++; }, @@ -625,13 +657,13 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T } if (TRANSFORMERSPRESENT) { - CFramebuffer* last = g_pHyprOpenGL->m_renderData.currentFB; + IFramebuffer* last = g_pHyprOpenGL->m_renderData.currentFB.get(); for (auto const& t : pWindow->m_transformers) { last = t->transform(last); } g_pHyprOpenGL->bindBackOnMain(); - g_pHyprOpenGL->renderOffToMain(last); + g_pHyprOpenGL->renderOffToMain(dc(last)); } } @@ -656,27 +688,34 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T renderdata.discardOpacity = *PBLURIGNOREA; } - if (pWindow->m_windowData.nearestNeighbor.valueOrDefault()) + if (pWindow->m_ruleApplicator->nearestNeighbor().valueOrDefault()) renderdata.useNearestNeighbor = true; 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; + + if (s->m_current.size.x < 1 || s->m_current.size.y < 1) + return; + renderdata.localPos = offset; renderdata.texture = s->m_current.texture; renderdata.surface = s; @@ -703,22 +742,53 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T } } - EMIT_HOOK_EVENT("render", RENDER_POST_WINDOW); + Event::bus()->m_events.render.stage.emit(RENDER_POST_WINDOW); g_pHyprOpenGL->m_renderData.currentWindow.reset(); } +SP CHyprRenderer::createTexture(const SP buffer, bool keepDataCopy) { + if (!buffer) + return createTexture(); + + auto attrs = buffer->dmabuf(); + + if (!attrs.success) { + // attempt shm + auto shm = buffer->shm(); + + if (!shm.success) { + Log::logger->log(Log::ERR, "Cannot create a texture: buffer has no dmabuf or shm"); + return createTexture(buffer->opaque); + } + + auto [pixelData, fmt, bufLen] = buffer->beginDataPtr(0); + + return createTexture(fmt, pixelData, bufLen, shm.size, keepDataCopy, buffer->opaque); + } + + auto tex = createTexture(attrs, buffer->opaque); + + if (!tex) { + Log::logger->log(Log::ERR, "Cannot create a texture: failed to create an Image"); + return createTexture(buffer->opaque); + } + + return tex; +} + void CHyprRenderer::renderLayer(PHLLS pLayer, PHLMONITOR pMonitor, const Time::steady_tp& time, bool popups, bool lockscreen) { if (!pLayer) return; // skip rendering based on abovelock rule and make sure to not render abovelock layers twice - if ((pLayer->m_aboveLockscreen && !lockscreen && g_pSessionLockManager->isSessionLocked()) || (lockscreen && !pLayer->m_aboveLockscreen)) + if ((pLayer->m_ruleApplicator->aboveLock().valueOrDefault() && !lockscreen && g_pSessionLockManager->isSessionLocked()) || + (lockscreen && !pLayer->m_ruleApplicator->aboveLock().valueOrDefault())) return; static auto PDIMAROUND = CConfigValue("decoration:dim_around"); - if (*PDIMAROUND && pLayer->m_dimAround && !m_bRenderingSnapshot && !popups) { + 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.color = CHyprColor(0, 0, 0, *PDIMAROUND * pLayer->m_alpha->value()); @@ -739,7 +809,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; @@ -748,18 +818,24 @@ void CHyprRenderer::renderLayer(PHLLS pLayer, PHLMONITOR pMonitor, const Time::s renderdata.clipBox = CBox{0, 0, pMonitor->m_size.x, pMonitor->m_size.y}.scale(pMonitor->m_scale); - if (renderdata.blur && pLayer->m_ignoreAlpha) { + if (renderdata.blur && pLayer->m_ruleApplicator->ignoreAlpha().hasValue()) { renderdata.discardMode |= DISCARD_ALPHA; - renderdata.discardOpacity = pLayer->m_ignoreAlphaValue; + 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; + + if (s->m_current.size.x < 1 || s->m_current.size.y < 1) + return; + 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++; }, @@ -768,18 +844,26 @@ void CHyprRenderer::renderLayer(PHLLS pLayer, PHLMONITOR pMonitor, const Time::s renderdata.squishOversized = false; // don't squish popups renderdata.dontRound = true; renderdata.popup = true; - renderdata.blur = pLayer->m_forceBlurPopups; + renderdata.blur = pLayer->m_ruleApplicator->blurPopups().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->wlSurface()->resource(); + + if (!SURF->m_current.texture) + return; + + if (SURF->m_current.size.x < 1 || SURF->m_current.size.y < 1) return; Vector2D pos = popup->coordsRelativeToParent(); renderdata.localPos = pos; - renderdata.texture = popup->m_wlSurface->resource()->m_current.texture; - renderdata.surface = popup->m_wlSurface->resource(); + renderdata.texture = SURF->m_current.texture; + renderdata.surface = SURF; renderdata.mainSurface = false; m_renderPass.add(makeUnique(renderdata)); renderdata.surfaceCounter++; @@ -812,6 +896,12 @@ void CHyprRenderer::renderIMEPopup(CInputPopup* pPopup, PHLMONITOR pMonitor, con SURF->breadthfirst( [this, &renderdata, &SURF](SP s, const Vector2D& offset, void* data) { + if (!s->m_current.texture) + return; + + if (s->m_current.size.x < 1 || s->m_current.size.y < 1) + return; + renderdata.localPos = offset; renderdata.texture = s->m_current.texture; renderdata.surface = s; @@ -833,6 +923,12 @@ void CHyprRenderer::renderSessionLockSurface(WP pSurface, P renderdata.surface->breadthfirst( [this, &renderdata, &pSurface](SP s, const Vector2D& offset, void* data) { + if (!s->m_current.texture) + return; + + if (s->m_current.size.x < 1 || s->m_current.size.y < 1) + return; + renderdata.localPos = offset; renderdata.texture = s->m_current.texture; renderdata.surface = s; @@ -850,10 +946,10 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA static auto PXPMODE = CConfigValue("render:xp_mode"); static auto PSESSIONLOCKXRAY = CConfigValue("misc:session_lock_xray"); - if (!pMonitor) + if UNLIKELY (!pMonitor) return; - if (g_pSessionLockManager->isSessionLocked() && !*PSESSIONLOCKXRAY) { + if UNLIKELY (g_pSessionLockManager->isSessionLocked() && !*PSESSIONLOCKXRAY) { // We stop to render workspaces as soon as the lockscreen was sent the "locked" or "finished" (aka denied) event. // In addition we make sure to stop rendering workspaces after misc:lockdead_screen_delay has passed. if (g_pSessionLockManager->shallConsiderLockMissing() || g_pSessionLockManager->clientLocked() || g_pSessionLockManager->clientDenied()) @@ -867,10 +963,10 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA SRenderModifData RENDERMODIFDATA; if (translate != Vector2D{0, 0}) RENDERMODIFDATA.modifs.emplace_back(std::make_pair<>(SRenderModifData::eRenderModifType::RMOD_TYPE_TRANSLATE, translate)); - if (scale != 1.f) + if UNLIKELY (scale != 1.f) RENDERMODIFDATA.modifs.emplace_back(std::make_pair<>(SRenderModifData::eRenderModifType::RMOD_TYPE_SCALE, scale)); - if (!RENDERMODIFDATA.modifs.empty()) + if UNLIKELY (!RENDERMODIFDATA.modifs.empty()) g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{RENDERMODIFDATA})); CScopeGuard x([&RENDERMODIFDATA] { @@ -879,7 +975,7 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA } }); - if (!pWorkspace) { + if UNLIKELY (!pWorkspace) { // allow rendering without a workspace. In this case, just render layers. renderBackground(pMonitor); @@ -888,7 +984,7 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA renderLayer(ls.lock(), pMonitor, time); } - EMIT_HOOK_EVENT("render", RENDER_POST_WALLPAPER); + Event::bus()->m_events.render.stage.emit(RENDER_POST_WALLPAPER); for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]) { renderLayer(ls.lock(), pMonitor, time); @@ -905,14 +1001,14 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA return; } - if (!*PXPMODE) { + if LIKELY (!*PXPMODE) { renderBackground(pMonitor); for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]) { renderLayer(ls.lock(), pMonitor, time); } - EMIT_HOOK_EVENT("render", RENDER_POST_WALLPAPER); + Event::bus()->m_events.render.stage.emit(RENDER_POST_WALLPAPER); for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]) { renderLayer(ls.lock(), pMonitor, time); @@ -922,13 +1018,13 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA // pre window pass g_pHyprOpenGL->preWindowPass(); - if (pWorkspace->m_hasFullscreenWindow) + if UNLIKELY /* subjective? */ (pWorkspace->m_hasFullscreenWindow) renderWorkspaceWindowsFullscreen(pMonitor, pWorkspace, time); else renderWorkspaceWindows(pMonitor, pWorkspace, time); // and then special - if (pMonitor->m_specialFade->value() != 0.F) { + if UNLIKELY (pMonitor->m_specialFade->value() != 0.F) { const auto SPECIALANIMPROGRS = pMonitor->m_specialFade->getCurveValue(); const bool ANIMOUT = !pMonitor->m_activeSpecialWorkspace; @@ -977,7 +1073,7 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA renderWindow(w, pMonitor, time, true, RENDER_PASS_ALL); } - EMIT_HOOK_EVENT("render", RENDER_POST_WINDOWS); + Event::bus()->m_events.render.stage.emit(RENDER_POST_WINDOWS); // Render surfaces above windows for monitor for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { @@ -1090,7 +1186,7 @@ void CHyprRenderer::renderSessionLockMissing(PHLMONITOR pMonitor) { 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; + 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(); @@ -1133,8 +1229,12 @@ void CHyprRenderer::calculateUVForSurface(PHLWINDOW pWindow, SPm_current.bufferSize; - const Vector2D MISALIGNMENT = pSurface->m_current.bufferSize - projSize; + 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 { @@ -1143,7 +1243,7 @@ void CHyprRenderer::calculateUVForSurface(PHLWINDOW pWindow, SPm_scale); - const bool SCALE_UNAWARE = MONITOR_WL_SCALE > 1 && (MONITOR_WL_SCALE == pSurface->m_current.scale || !pSurface->m_current.viewport.hasDestination); + 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; @@ -1204,6 +1304,79 @@ void CHyprRenderer::calculateUVForSurface(PHLWINDOW pWindow, SP surface, bool modifySDR, float sdrMinLuminance, int sdrMaxLuminance) { + const auto sdrEOTF = NTransferFunction::fromConfig(); + NColorManagement::eTransferFunction srcTF; + + auto& m_renderData = g_pHyprOpenGL->m_renderData; + if (m_renderData.surface.valid()) { + if (m_renderData.surface->m_colorManagement.valid()) { + if (sdrEOTF == NTransferFunction::TF_FORCED_GAMMA22 && imageDescription->value().transferFunction == NColorManagement::eTransferFunction::CM_TRANSFER_FUNCTION_SRGB) + srcTF = NColorManagement::eTransferFunction::CM_TRANSFER_FUNCTION_GAMMA22; + else + srcTF = imageDescription->value().transferFunction; + } else if (sdrEOTF == NTransferFunction::TF_SRGB) + srcTF = NColorManagement::eTransferFunction::CM_TRANSFER_FUNCTION_SRGB; + else if (sdrEOTF == NTransferFunction::TF_GAMMA22 || sdrEOTF == NTransferFunction::TF_FORCED_GAMMA22) + srcTF = NColorManagement::eTransferFunction::CM_TRANSFER_FUNCTION_GAMMA22; + else + srcTF = imageDescription->value().transferFunction; + } else + srcTF = imageDescription->value().transferFunction; + + const bool needsSDRmod = modifySDR && isSDR2HDR(imageDescription->value(), targetImageDescription->value()); + const bool needsHDRmod = !needsSDRmod && isHDR2SDR(imageDescription->value(), targetImageDescription->value()); + const float maxLuminance = needsHDRmod ? + imageDescription->value().getTFMaxLuminance(-1) : + (imageDescription->value().luminances.max > 0 ? imageDescription->value().luminances.max : imageDescription->value().luminances.reference); + const auto dstMaxLuminance = targetImageDescription->value().luminances.max > 0 ? targetImageDescription->value().luminances.max : 10000; + + auto matrix = imageDescription->getPrimaries()->convertMatrix(targetImageDescription->getPrimaries()); + auto toXYZ = targetImageDescription->getPrimaries()->value().toXYZ(); + + const bool needsMod = (imageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_SRGB || imageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_GAMMA22) && + targetImageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ && + ((m_renderData.pMonitor->m_sdrSaturation > 0 && m_renderData.pMonitor->m_sdrSaturation != 1.0f) || + (m_renderData.pMonitor->m_sdrBrightness > 0 && m_renderData.pMonitor->m_sdrBrightness != 1.0f)); + + return { + .sourceTF = srcTF, + .targetTF = targetImageDescription->value().transferFunction, + .srcTFRange = {.min = imageDescription->value().getTFMinLuminance(needsSDRmod ? sdrMinLuminance : -1), + .max = imageDescription->value().getTFMaxLuminance(needsSDRmod ? sdrMaxLuminance : -1)}, + .dstTFRange = {.min = targetImageDescription->value().getTFMinLuminance(needsSDRmod ? sdrMinLuminance : -1), + .max = targetImageDescription->value().getTFMaxLuminance(needsSDRmod ? sdrMaxLuminance : -1)}, + .srcRefLuminance = imageDescription->value().luminances.reference, + .dstRefLuminance = targetImageDescription->value().luminances.reference, + .convertMatrix = matrix.mat(), + + .needsTonemap = maxLuminance >= dstMaxLuminance * 1.01, + .maxLuminance = maxLuminance * targetImageDescription->value().luminances.reference / imageDescription->value().luminances.reference, + .dstMaxLuminance = dstMaxLuminance, + .dstPrimaries2XYZ = toXYZ.mat(), + .needsSDRmod = needsMod, + .sdrSaturation = needsSDRmod && m_renderData.pMonitor->m_sdrSaturation > 0 ? m_renderData.pMonitor->m_sdrSaturation : 1.0f, + .sdrBrightnessMultiplier = needsSDRmod && m_renderData.pMonitor->m_sdrBrightness > 0 ? m_renderData.pMonitor->m_sdrBrightness : 1.0f, + }; +} + void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { static std::chrono::high_resolution_clock::time_point renderStart = std::chrono::high_resolution_clock::now(); static std::chrono::high_resolution_clock::time_point renderStartOverlay = std::chrono::high_resolution_clock::now(); @@ -1212,6 +1385,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { static auto PDEBUGOVERLAY = CConfigValue("debug:overlay"); static auto PDAMAGETRACKINGMODE = CConfigValue("debug:damage_tracking"); static auto PDAMAGEBLINK = CConfigValue("debug:damage_blink"); + static auto PSOLDAMAGE = CConfigValue("debug:render_solitary_wo_damage"); static auto PVFR = CConfigValue("misc:vfr"); static int damageBlinkCleanup = 0; // because double-buffered @@ -1219,7 +1393,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { 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; } @@ -1234,6 +1408,9 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { if (!g_pCompositor->m_sessionActive) return; + 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 @@ -1245,7 +1422,8 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { if (pMonitor->m_scheduledRecalc) { pMonitor->m_scheduledRecalc = false; - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitor->m_id); + if (pMonitor->m_activeWorkspace) // might be missing (mirror) + pMonitor->m_activeWorkspace->m_space->recalculate(); } if (!pMonitor->m_output->needsFrame && pMonitor->m_forceFullFrames == 0) @@ -1255,10 +1433,12 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { bool shouldTear = pMonitor->updateTearing(); if (pMonitor->attemptDirectScanout()) { + pMonitor->m_directScanoutIsActive = true; return; - } else if (!pMonitor->m_lastScanout.expired()) { - Debug::log(LOG, "Left a direct scanout."); + } else if (!pMonitor->m_lastScanout.expired() || pMonitor->m_directScanoutIsActive) { + Log::logger->log(Log::DEBUG, "Left a direct scanout."); pMonitor->m_lastScanout.reset(); + pMonitor->m_directScanoutIsActive = false; // reset DRM format, but only if needed since it might modeset if (pMonitor->m_output->state->state().drmFormat != pMonitor->m_prevDrmFormat) @@ -1267,7 +1447,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { pMonitor->m_drmFormat = pMonitor->m_prevDrmFormat; } - EMIT_HOOK_EVENT("preRender", pMonitor); + Event::bus()->m_events.render.pre.emit(pMonitor); const auto NOW = Time::steadyNow(); @@ -1278,11 +1458,11 @@ 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; @@ -1316,7 +1496,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { 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; } @@ -1335,50 +1515,49 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { pMonitor->m_forceFullFrames = 0; } - EMIT_HOOK_EVENT("render", RENDER_BEGIN); + Event::bus()->m_events.render.stage.emit(RENDER_BEGIN); bool renderCursor = true; - if (!finalDamage.empty()) { - if (pMonitor->m_solitaryClient.expired()) { - if (pMonitor->isMirror()) { - g_pHyprOpenGL->blend(false); - g_pHyprOpenGL->renderMirrored(); - g_pHyprOpenGL->blend(true); - EMIT_HOOK_EVENT("render", RENDER_POST_MIRROR); - renderCursor = false; - } else { - CBox renderBox = {0, 0, sc(pMonitor->m_pixelSize.x), sc(pMonitor->m_pixelSize.y)}; - renderWorkspace(pMonitor, pMonitor->m_activeWorkspace, NOW, renderBox); + if (pMonitor->m_solitaryClient && (!finalDamage.empty() || *PSOLDAMAGE)) + renderWindow(pMonitor->m_solitaryClient.lock(), pMonitor, NOW, false, RENDER_PASS_MAIN /* solitary = no popups */); + else if (!finalDamage.empty()) { + if (pMonitor->isMirror()) { + g_pHyprOpenGL->blend(false); + g_pHyprOpenGL->renderMirrored(); + g_pHyprOpenGL->blend(true); + Event::bus()->m_events.render.stage.emit(RENDER_POST_MIRROR); + renderCursor = false; + } else { + CBox renderBox = {0, 0, sc(pMonitor->m_pixelSize.x), sc(pMonitor->m_pixelSize.y)}; + renderWorkspace(pMonitor, pMonitor->m_activeWorkspace, NOW, renderBox); - renderLockscreen(pMonitor, NOW, renderBox); + renderLockscreen(pMonitor, NOW, renderBox); - if (pMonitor == g_pCompositor->m_lastMonitor) { - g_pHyprNotificationOverlay->draw(pMonitor); - g_pHyprError->draw(); - } - - // for drawing the debug overlay - if (pMonitor == g_pCompositor->m_monitors.front() && *PDEBUGOVERLAY == 1) { - renderStartOverlay = std::chrono::high_resolution_clock::now(); - g_pDebugOverlay->draw(); - endRenderOverlay = std::chrono::high_resolution_clock::now(); - } - - if (*PDAMAGEBLINK && damageBlinkCleanup == 0) { - CRectPassElement::SRectData data; - data.box = {0, 0, pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y}; - data.color = CHyprColor(1.0, 0.0, 1.0, 100.0 / 255.0); - m_renderPass.add(makeUnique(data)); - damageBlinkCleanup = 1; - } else if (*PDAMAGEBLINK) { - damageBlinkCleanup++; - if (damageBlinkCleanup > 3) - damageBlinkCleanup = 0; - } + if (pMonitor == Desktop::focusState()->monitor()) { + g_pHyprNotificationOverlay->draw(pMonitor); + g_pHyprError->draw(); } - } else - renderWindow(pMonitor->m_solitaryClient.lock(), pMonitor, NOW, false, RENDER_PASS_MAIN /* solitary = no popups */); + + // for drawing the debug overlay + if (pMonitor == g_pCompositor->m_monitors.front() && *PDEBUGOVERLAY == 1) { + renderStartOverlay = std::chrono::high_resolution_clock::now(); + g_pDebugOverlay->draw(); + endRenderOverlay = std::chrono::high_resolution_clock::now(); + } + + if (*PDAMAGEBLINK && damageBlinkCleanup == 0) { + CRectPassElement::SRectData data; + data.box = {0, 0, pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y}; + data.color = CHyprColor(1.0, 0.0, 1.0, 100.0 / 255.0); + m_renderPass.add(makeUnique(data)); + damageBlinkCleanup = 1; + } else if (*PDAMAGEBLINK) { + damageBlinkCleanup++; + if (damageBlinkCleanup > 3) + damageBlinkCleanup = 0; + } + } } else if (!pMonitor->isMirror()) { sendFrameEventsToWorkspace(pMonitor, pMonitor->m_activeWorkspace, NOW); if (pMonitor->m_activeSpecialWorkspace) @@ -1400,7 +1579,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { m_renderPass.add(makeUnique(data)); } - EMIT_HOOK_EVENT("render", RENDER_LAST_MOMENT); + Event::bus()->m_events.render.stage.emit(RENDER_LAST_MOMENT); endRender(); @@ -1408,8 +1587,8 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { CRegion frameDamage{g_pHyprOpenGL->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)); @@ -1422,7 +1601,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { pMonitor->m_renderingActive = false; - EMIT_HOOK_EVENT("render", RENDER_POST); + Event::bus()->m_events.render.stage.emit(RENDER_POST); pMonitor->m_output->state->addDamage(frameDamage); pMonitor->m_output->state->setPresentationMode(shouldTear ? Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE : @@ -1456,6 +1635,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: @@ -1468,13 +1648,15 @@ 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 = @@ -1490,8 +1672,8 @@ static hdr_output_metadata createHDRMetadata(SImageDescription settings, S .white_point = {.x = to16Bit(colorimetry.white.x), .y = to16Bit(colorimetry.white.y)}, .max_display_mastering_luminance = toNits(luminances.max), .min_display_mastering_luminance = toNits(luminances.min * 10000), - .max_cll = toNits(settings.maxCLL), - .max_fall = toNits(settings.maxFALL), + .max_cll = toNits(settings.maxCLL > 0 ? settings.maxCLL : monitor->maxCLL()), + .max_fall = toNits(settings.maxFALL > 0 ? settings.maxFALL : monitor->maxFALL()), }, }; } @@ -1502,12 +1684,10 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { static auto PAUTOHDR = CConfigValue("render:cm_auto_hdr"); static auto PNONSHADER = CConfigValue("render:non_shader_cm"); - static bool needsHDRupdate = false; - 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 @@ -1524,7 +1704,7 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { 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 @@ -1532,17 +1712,17 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { 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; + 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; + hdrIsHandled = true; + pMonitor->m_needsHDRupdate = false; } else if (*PAUTOHDR && surfaceIsHDR) wantHDR = true; // auto-hdr: hdr on } @@ -1555,28 +1735,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("Wide color gamut is enabled but the display is not in 10bit mode", CHyprColor{}, 15000, ICON_WARNING); + g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, {{"name", pMonitor->m_name}}), CHyprColor{}, 15000, + ICON_WARNING); shown = true; } } @@ -1585,21 +1766,22 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { if (*PCT) pMonitor->m_output->state->setContentType(NContentType::toDRM(FS_WINDOW ? FS_WINDOW->getContentType() : CONTENT_TYPE_NONE)); - if (FS_WINDOW != pMonitor->m_previousFSWindow) { + if (FS_WINDOW != pMonitor->m_previousFSWindow || (!FS_WINDOW && pMonitor->m_noShaderCTM)) { if (*PNONSHADER == CM_NS_IGNORE || !FS_WINDOW || !pMonitor->needsCM() || !pMonitor->canNoShaderCM() || (*PNONSHADER == CM_NS_ONDEMAND && pMonitor->m_lastScanout.expired() && *PPASS != 1)) { if (pMonitor->m_noShaderCTM) { - Debug::log(INFO, "[CM] No fullscreen CTM, restoring previous one"); + Log::logger->log(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(); - const std::array CTM = { + Log::logger->log(Log::INFO, "[CM] Updating fullscreen CTM"); + pMonitor->m_noShaderCTM = true; + auto conversion = FS_DESC.value()->getPrimaries()->convertMatrix(pMonitor->m_imageDescription->getPrimaries()); + 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], // @@ -1619,13 +1801,13 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR 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(); @@ -1643,7 +1825,7 @@ void CHyprRenderer::renderWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace 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{}; } @@ -1652,23 +1834,11 @@ void CHyprRenderer::renderWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace } 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()) + 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); } } @@ -1808,7 +1978,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; } @@ -1827,33 +1997,20 @@ void CHyprRenderer::arrangeLayerArray(PHLMONITOR pMonitor, const std::vectorgetMonitorFromID(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() && g_pCompositor->m_lastMonitor == 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(la, [](const PHLLSREF& a, const PHLLSREF& b) { return a->m_order > b->m_order; }); + std::ranges::stable_sort( + la, [](const PHLLSREF& a, const PHLLSREF& b) { return a->m_ruleApplicator->order().valueOrDefault() > b->m_ruleApplicator->order().valueOrDefault(); }); } for (auto const& la : PMONITOR->m_layerSurfaceLayers) @@ -1862,23 +2019,12 @@ 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) { @@ -1888,22 +2034,33 @@ void CHyprRenderer::damageSurface(SP pSurface, double x, dou 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; } - if (scale != 1.0) - damageBox.scale(scale); + // hack: schedule frame events + if (!WLSURF->resource()->m_current.callbacks.empty() && pSurface->m_hlSurface) { + const auto BOX = pSurface->m_hlSurface->getSurfaceBoxGlobal(); + if (BOX && !BOX->empty()) { + for (auto const& m : g_pCompositor->m_monitors) { + if (!m->m_output) + continue; - // schedule frame events - g_pCompositor->scheduleFrameForMonitor(g_pCompositor->getMonitorFromVector(Vector2D(x, y)), Aquamarine::IOutput::AQ_SCHEDULE_DAMAGE); + if (BOX->overlaps(m->logicalBox())) + g_pCompositor->scheduleFrameForMonitor(m, Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); + } + } + } + CRegion damageBox = WLSURF->computeDamage(); if (damageBox.empty()) return; + if (scale != 1.0) + damageBox.scale(scale); + damageBox.translate({x, y}); CRegion damageBoxForEach; @@ -1921,8 +2078,8 @@ 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) { @@ -1949,7 +2106,7 @@ void CHyprRenderer::damageWindow(PHLWINDOW pWindow, bool forceFull) { 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) { @@ -1962,7 +2119,7 @@ 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) { @@ -1982,7 +2139,7 @@ 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) { @@ -2009,7 +2166,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); @@ -2022,8 +2179,8 @@ void CHyprRenderer::renderDragIcon(PHLMONITOR pMonitor, const Time::steady_tp& t PROTO::data->renderDND(pMonitor, time); } -void CHyprRenderer::setCursorSurface(SP surf, int hotspotX, int hotspotY, bool force) { - m_cursorHasSurface = surf; +void CHyprRenderer::setCursorSurface(SP surf, int hotspotX, int hotspotY, bool force) { + m_cursorHasSurface = surf && surf->resource(); m_lastCursorData.name = ""; m_lastCursorData.surf = surf; @@ -2091,49 +2248,42 @@ void CHyprRenderer::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) { @@ -2167,10 +2317,8 @@ std::tuple CHyprRenderer::getRenderTimes(PHLMONITOR pMonito float maxRenderTime = 0; float minRenderTime = 9999; for (auto const& rt : POVERLAY->m_lastRenderTimes) { - if (rt > maxRenderTime) - maxRenderTime = rt; - if (rt < minRenderTime) - minRenderTime = rt; + maxRenderTime = std::max(rt, maxRenderTime); + minRenderTime = std::min(rt, minRenderTime); avgRenderTime += rt; } avgRenderTime /= POVERLAY->m_lastRenderTimes.empty() ? 1 : POVERLAY->m_lastRenderTimes.size(); @@ -2209,13 +2357,13 @@ void CHyprRenderer::initiateManualCrash() { **PDT = 0; } -SP CHyprRenderer::getOrCreateRenderbuffer(SP buffer, uint32_t fmt) { +SP CHyprRenderer::getOrCreateRenderbuffer(SP buffer, uint32_t fmt) { auto it = std::ranges::find_if(m_renderbuffers, [&](const auto& other) { return other->m_hlBuffer == buffer; }); if (it != m_renderbuffers.end()) return *it; - auto buf = makeShared(buffer, fmt); + auto buf = makeShared(buffer, fmt); if (!buf->good()) return nullptr; @@ -2239,7 +2387,7 @@ void CHyprRenderer::unsetEGL() { eglMakeCurrent(g_pHyprOpenGL->m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } -bool CHyprRenderer::beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMode mode, SP buffer, CFramebuffer* fb, bool simple) { +bool CHyprRenderer::beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMode mode, SP buffer, SP fb, bool simple) { makeEGLCurrent(); @@ -2264,7 +2412,7 @@ bool CHyprRenderer::beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMod 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); + Log::logger->log(Log::ERR, "Failed to acquire swapchain buffer for {}", pMonitor->m_name); return false; } } else @@ -2273,12 +2421,12 @@ bool CHyprRenderer::beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMod 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); + Log::logger->log(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); + Log::logger->log(Log::ERR, "failed to start a render pass for output {}, no RBO could be obtained", pMonitor->m_name); return false; } @@ -2324,7 +2472,7 @@ void CHyprRenderer::endRender(const std::function& renderingDoneCallback PMONITOR->m_output->state->setBuffer(m_currentBuffer); if (!g_pHyprOpenGL->explicitSyncSupported()) { - Debug::log(TRACE, "renderer: Explicit sync unsupported, falling back to implicit in endRender"); + 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()) @@ -2340,7 +2488,7 @@ void CHyprRenderer::endRender(const std::function& renderingDoneCallback } UP eglSync = CEGLSync::create(); - if (eglSync && eglSync->isValid()) { + if LIKELY (eglSync && eglSync->isValid()) { for (auto const& buf : m_usedAsyncBuffers) { for (const auto& releaser : buf->m_syncReleasers) { releaser->addSyncFileFd(eglSync->fd()); @@ -2363,7 +2511,7 @@ void CHyprRenderer::endRender(const std::function& renderingDoneCallback PMONITOR->m_output->state->setExplicitInFence(PMONITOR->m_inFence.get()); } } else { - Debug::log(ERR, "renderer: Explicit sync failed, releasing resources"); + 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) @@ -2371,11 +2519,11 @@ void CHyprRenderer::endRender(const std::function& renderingDoneCallback } } -void CHyprRenderer::onRenderbufferDestroy(CRenderbuffer* rb) { +void CHyprRenderer::onRenderbufferDestroy(CGLRenderbuffer* rb) { std::erase_if(m_renderbuffers, [&](const auto& rbo) { return rbo.get() == rb; }); } -SP CHyprRenderer::getCurrentRBO() { +SP CHyprRenderer::getCurrentRBO() { return m_currentRenderbuffer; } @@ -2417,7 +2565,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 @@ -2428,7 +2576,10 @@ void CHyprRenderer::makeSnapshot(PHLWINDOW pWindow) { makeEGLCurrent(); - const auto PFRAMEBUFFER = &g_pHyprOpenGL->m_windowFramebuffers[ref]; + if (!g_pHyprOpenGL->m_windowFramebuffers.contains(ref)) + g_pHyprOpenGL->m_windowFramebuffers[ref] = g_pHyprRenderer->createFB(); + + const auto PFRAMEBUFFER = g_pHyprOpenGL->m_windowFramebuffers[ref]; PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888); @@ -2452,7 +2603,7 @@ void CHyprRenderer::makeSnapshot(PHLLS pLayer) { 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 {:x}", rc(pLayer.get())); // we need to "damage" the entire monitor // so that we render the entire window @@ -2461,7 +2612,10 @@ void CHyprRenderer::makeSnapshot(PHLLS pLayer) { makeEGLCurrent(); - const auto PFRAMEBUFFER = &g_pHyprOpenGL->m_layerFramebuffers[pLayer]; + if (!g_pHyprOpenGL->m_layerFramebuffers.contains(pLayer)) + g_pHyprOpenGL->m_layerFramebuffers[pLayer] = g_pHyprRenderer->createFB(); + + const auto PFRAMEBUFFER = g_pHyprOpenGL->m_layerFramebuffers[pLayer]; PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888); @@ -2479,23 +2633,26 @@ void CHyprRenderer::makeSnapshot(PHLLS pLayer) { m_bRenderingSnapshot = false; } -void CHyprRenderer::makeSnapshot(WP popup) { +void CHyprRenderer::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(); - const auto PFRAMEBUFFER = &g_pHyprOpenGL->m_popupFramebuffers[popup]; + if (!g_pHyprOpenGL->m_popupFramebuffers.contains(popup)) + g_pHyprOpenGL->m_popupFramebuffers[popup] = g_pHyprRenderer->createFB(); + + const auto PFRAMEBUFFER = g_pHyprOpenGL->m_popupFramebuffers[popup]; PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888); @@ -2514,8 +2671,14 @@ 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; + + if (s->m_current.size.x < 1 || s->m_current.size.y < 1) + return; + renderdata.localPos = offset; renderdata.texture = s->m_current.texture; renderdata.surface = s; @@ -2538,7 +2701,7 @@ void CHyprRenderer::renderSnapshot(PHLWINDOW pWindow) { if (!g_pHyprOpenGL->m_windowFramebuffers.contains(ref)) return; - const auto FBDATA = &g_pHyprOpenGL->m_windowFramebuffers.at(ref); + const auto FBDATA = g_pHyprOpenGL->m_windowFramebuffers.at(ref); if (!FBDATA->getTexture()) return; @@ -2558,7 +2721,7 @@ void CHyprRenderer::renderSnapshot(PHLWINDOW pWindow) { CRegion fakeDamage{0, 0, PMONITOR->m_transformedSize.x, PMONITOR->m_transformedSize.y}; - if (*PDIMAROUND && pWindow->m_windowData.dimAround.valueOrDefault()) { + 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}; @@ -2575,7 +2738,7 @@ void CHyprRenderer::renderSnapshot(PHLWINDOW pWindow) { data.blurA = sqrt(pWindow->m_alpha->value()); // sqrt makes the blur fadeout more realistic. data.round = pWindow->rounding(); data.roundingPower = pWindow->roundingPower(); - data.xray = pWindow->m_windowData.xray.valueOr(false); + data.xray = pWindow->m_ruleApplicator->xray().valueOr(false); m_renderPass.add(makeUnique(std::move(data))); } @@ -2594,7 +2757,7 @@ void CHyprRenderer::renderSnapshot(PHLLS pLayer) { if (!g_pHyprOpenGL->m_layerFramebuffers.contains(pLayer)) return; - const auto FBDATA = &g_pHyprOpenGL->m_layerFramebuffers.at(pLayer); + const auto FBDATA = g_pHyprOpenGL->m_layerFramebuffers.at(pLayer); if (!FBDATA->getTexture()) return; @@ -2627,18 +2790,18 @@ void CHyprRenderer::renderSnapshot(PHLLS pLayer) { data.blur = SHOULD_BLUR; data.blurA = sqrt(pLayer->m_alpha->value()); // sqrt makes the blur fadeout more realistic. if (SHOULD_BLUR) - data.ignoreAlpha = pLayer->m_ignoreAlpha ? pLayer->m_ignoreAlphaValue : 0.01F /* ignore the alpha 0 regions */; + data.ignoreAlpha = pLayer->m_ruleApplicator->ignoreAlpha().valueOr(0.01F) /* ignore the alpha 0 regions */; m_renderPass.add(makeUnique(std::move(data))); } -void CHyprRenderer::renderSnapshot(WP popup) { +void CHyprRenderer::renderSnapshot(WP popup) { if (!g_pHyprOpenGL->m_popupFramebuffers.contains(popup)) return; static CConfigValue PBLURIGNOREA = CConfigValue("decoration:blur:popups_ignorealpha"); - const auto FBDATA = &g_pHyprOpenGL->m_popupFramebuffers.at(popup); + const auto FBDATA = g_pHyprOpenGL->m_popupFramebuffers.at(popup); if (!FBDATA->getTexture()) return; @@ -2667,12 +2830,22 @@ void CHyprRenderer::renderSnapshot(WP popup) { m_renderPass.add(makeUnique(std::move(data))); } +NColorManagement::PImageDescription CHyprRenderer::workBufferImageDescription() { + const auto& m_renderData = g_pHyprOpenGL->m_renderData; + // TODO + // const bool IS_MONITOR_ICC = m_renderData.pMonitor->m_imageDescription.valid() && m_renderData.pMonitor->m_imageDescription->value().icc.present; + // const auto sdrEOTF = NTransferFunction::fromConfig(IS_MONITOR_ICC); + // const auto CHOSEN_SDR_EOTF = sdrEOTF != NTransferFunction::TF_SRGB ? NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22 : NColorManagement::CM_TRANSFER_FUNCTION_SRGB; + + return m_renderData.pMonitor->m_imageDescription; //CImageDescription::from(NColorManagement::SImageDescription{.transferFunction = CHOSEN_SDR_EOTF}); +} + bool CHyprRenderer::shouldBlur(PHLLS ls) { if (m_bRenderingSnapshot) return false; static auto PBLUR = CConfigValue("decoration:blur:enabled"); - return *PBLUR && ls->m_forceBlur; + return *PBLUR && ls->m_ruleApplicator->blur().valueOrDefault(); } bool CHyprRenderer::shouldBlur(PHLWINDOW w) { @@ -2680,13 +2853,102 @@ bool CHyprRenderer::shouldBlur(PHLWINDOW w) { return false; static auto PBLUR = CConfigValue("decoration:blur:enabled"); - const bool DONT_BLUR = w->m_windowData.noBlur.valueOrDefault() || w->m_windowData.RGBX.valueOrDefault() || w->opaque(); + const bool DONT_BLUR = w->m_ruleApplicator->noBlur().valueOrDefault() || w->m_ruleApplicator->RGBX().valueOrDefault() || w->opaque(); return *PBLUR && !DONT_BLUR; } -bool CHyprRenderer::shouldBlur(WP p) { +bool CHyprRenderer::shouldBlur(WP p) { static CConfigValue PBLURPOPUPS = CConfigValue("decoration:blur:popups"); static CConfigValue PBLUR = CConfigValue("decoration:blur:enabled"); return *PBLURPOPUPS && *PBLUR; } + +bool CHyprRenderer::reloadShaders(const std::string& path) { + return g_pHyprOpenGL->initShaders(path); +} + +SP CHyprRenderer::createStencilTexture(const int width, const int height) { + makeEGLCurrent(); + auto tex = makeShared(); + tex->allocate({width, height}); + + return tex; +} + +SP CHyprRenderer::createTexture(bool opaque) { + makeEGLCurrent(); + return makeShared(opaque); +} + +SP CHyprRenderer::createTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy, bool opaque) { + makeEGLCurrent(); + return makeShared(drmFormat, pixels, stride, size, keepDataCopy, opaque); +} + +SP CHyprRenderer::createTexture(const Aquamarine::SDMABUFAttrs& attrs, bool opaque) { + makeEGLCurrent(); + const auto image = g_pHyprOpenGL->createEGLImage(attrs); + if (!image) + return nullptr; + return makeShared(attrs, image, opaque); +} + +SP CHyprRenderer::createTexture(const int width, const int height, unsigned char* const data) { + makeEGLCurrent(); + SP tex = makeShared(); + + tex->allocate({width, height}); + + tex->m_size = {width, height}; + // copy the data to an OpenGL texture we have + const GLint glFormat = GL_RGBA; + const GLint glType = GL_UNSIGNED_BYTE; + + tex->bind(); + tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); + tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); + tex->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); + tex->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); + + glTexImage2D(GL_TEXTURE_2D, 0, glFormat, tex->m_size.x, tex->m_size.y, 0, glFormat, glType, data); + tex->unbind(); + + return tex; +} + +SP CHyprRenderer::createTexture(cairo_surface_t* cairo) { + makeEGLCurrent(); + const auto CAIROFORMAT = cairo_image_surface_get_format(cairo); + auto tex = makeShared(); + + tex->allocate({cairo_image_surface_get_width(cairo), cairo_image_surface_get_height(cairo)}); + + const GLint glIFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB32F : GL_RGBA; + const GLint glFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB : GL_RGBA; + const GLint glType = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_FLOAT : GL_UNSIGNED_BYTE; + + const auto DATA = cairo_image_surface_get_data(cairo); + tex->bind(); + tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); + tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + if (CAIROFORMAT != CAIRO_FORMAT_RGB96F) { + tex->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); + tex->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); + } + + glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, tex->m_size.x, tex->m_size.y, 0, glFormat, glType, DATA); + + return tex; +} + +SP CHyprRenderer::createTexture(std::span lut3D, size_t N) { + makeEGLCurrent(); + return makeShared(lut3D, N); +} + +SP CHyprRenderer::createFB(const std::string& name) { + makeEGLCurrent(); + return makeShared(name); +} \ No newline at end of file diff --git a/src/render/Renderer.hpp b/src/render/Renderer.hpp index 1980984d..bd14c219 100644 --- a/src/render/Renderer.hpp +++ b/src/render/Renderer.hpp @@ -1,23 +1,41 @@ #pragma once #include "../defines.hpp" +#include +#include #include +#include #include "../helpers/Monitor.hpp" -#include "../desktop/LayerSurface.hpp" +#include "../desktop/view/LayerSurface.hpp" #include "OpenGL.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 "render/Framebuffer.hpp" +#include "render/Texture.hpp" struct SMonitorRule; class CWorkspace; -class CWindow; class CInputPopup; class IHLBuffer; class CEventLoopTimer; +const std::vector ASSET_PATHS = { +#ifdef DATAROOTDIR + DATAROOTDIR, +#endif + "/usr/share", + "/usr/local/share", +}; +class CToplevelExportProtocolManager; +class CInputManager; +struct SSessionLockSurface; +namespace Screenshare { + class CScreenshareFrame; +}; + enum eDamageTrackingModes : int8_t { DAMAGE_TRACKING_INVALID = -1, DAMAGE_TRACKING_NONE = 0, @@ -38,15 +56,34 @@ enum eRenderMode : uint8_t { RENDER_MODE_TO_BUFFER_READ_ONLY = 3, }; -class CToplevelExportProtocolManager; -class CInputManager; -struct SSessionLockSurface; - struct SRenderWorkspaceUntilData { PHLLS ls; PHLWINDOW w; }; +struct STFRange { + float min = 0; + float max = 80; +}; + +struct SCMSettings { + NColorManagement::eTransferFunction sourceTF = NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22; + NColorManagement::eTransferFunction targetTF = NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22; + STFRange srcTFRange; + STFRange dstTFRange; + float srcRefLuminance = 80; + float dstRefLuminance = 80; + std::array, 3> convertMatrix; + + bool needsTonemap = false; + float maxLuminance = 80; + float dstMaxLuminance = 80; + std::array, 3> dstPrimaries2XYZ; + bool needsSDRmod = false; + float sdrSaturation = 1.0; + float sdrBrightnessMultiplier = 1.0; +}; + class CHyprRenderer { public: CHyprRenderer(); @@ -70,10 +107,10 @@ class CHyprRenderer { 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 setCursorSurface(SP surf, int hotspotX, int hotspotY, bool force = false); void setCursorFromName(const std::string& name, bool force = false); - void onRenderbufferDestroy(CRenderbuffer* rb); - SP getCurrentRBO(); + void onRenderbufferDestroy(CGLRenderbuffer* rb); + SP getCurrentRBO(); bool isNvidia(); bool isIntel(); bool isSoftware(); @@ -83,14 +120,17 @@ class CHyprRenderer { void addWindowToRenderUnfocused(PHLWINDOW window); void makeSnapshot(PHLWINDOW); void makeSnapshot(PHLLS); - void makeSnapshot(WP); + void makeSnapshot(WP); void renderSnapshot(PHLWINDOW); void renderSnapshot(PHLLS); - void renderSnapshot(WP); + void renderSnapshot(WP); + + // + NColorManagement::PImageDescription workBufferImageDescription(); // if RENDER_MODE_NORMAL, provided damage will be written to. // otherwise, it will be the one used. - bool beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMode mode = RENDER_MODE_NORMAL, SP buffer = {}, CFramebuffer* fb = nullptr, bool simple = false); + bool beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMode mode = RENDER_MODE_NORMAL, SP buffer = {}, SP fb = nullptr, bool simple = false); void endRender(const std::function& renderingDoneCallback = {}); bool m_bBlockSurfaceFeedback = false; @@ -109,16 +149,30 @@ class CHyprRenderer { std::vector m_usedAsyncBuffers; 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; + 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; - CRenderPass m_renderPass = {}; + CRenderPass m_renderPass = {}; + + SP createStencilTexture(const int width, const int height); + SP createTexture(bool opaque = false); + SP createTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy = false, bool opaque = false); + SP createTexture(const Aquamarine::SDMABUFAttrs&, bool opaque = false); + SP createTexture(const int width, const int height, unsigned char* const); + SP createTexture(cairo_surface_t* cairo); + SP createTexture(const SP buffer, bool keepDataCopy = false); + SP createTexture(std::span lut3D, size_t N); + SP createFB(const std::string& name = ""); + + SCMSettings getCMSettings(const NColorManagement::PImageDescription imageDescription, const NColorManagement::PImageDescription targetImageDescription, + SP surface = nullptr, bool modifySDR = false, float sdrMinLuminance = -1.0f, int sdrMaxLuminance = -1); + bool reloadShaders(const std::string& path = ""); private: void arrangeLayerArray(PHLMONITOR, const std::vector&, bool, CBox*); @@ -140,12 +194,12 @@ class CHyprRenderer { bool shouldBlur(PHLLS ls); bool shouldBlur(PHLWINDOW w); - bool shouldBlur(WP p); + bool shouldBlur(WP p); bool m_cursorHidden = false; bool m_cursorHiddenByCondition = false; bool m_cursorHasSurface = false; - SP m_currentRenderbuffer = nullptr; + SP m_currentRenderbuffer = nullptr; SP m_currentBuffer = nullptr; eRenderMode m_renderMode = RENDER_MODE_NORMAL; bool m_nvidia = false; @@ -155,17 +209,19 @@ class CHyprRenderer { struct { bool hiddenOnTouch = false; + bool hiddenOnTablet = false; bool hiddenOnTimeout = false; bool hiddenOnKeyboard = false; } m_cursorHiddenConditions; - SP getOrCreateRenderbuffer(SP buffer, uint32_t fmt); - std::vector> m_renderbuffers; + SP getOrCreateRenderbuffer(SP buffer, uint32_t fmt); + std::vector> m_renderbuffers; std::vector m_renderUnfocused; SP m_renderUnfocusedTimer; friend class CHyprOpenGLImpl; friend class CToplevelExportFrame; + friend class Screenshare::CScreenshareFrame; friend class CInputManager; friend class CPointerManager; friend class CMonitor; diff --git a/src/render/Shader.cpp b/src/render/Shader.cpp index 5081d4c4..ead841a5 100644 --- a/src/render/Shader.cpp +++ b/src/render/Shader.cpp @@ -1,4 +1,5 @@ #include "Shader.hpp" +#include "../config/ConfigManager.hpp" #include "render/OpenGL.hpp" #define EPSILON(x, y) (std::abs((x) - (y)) < 1e-5f) @@ -14,51 +15,235 @@ static bool compareFloat(auto a, auto b) { return true; } -SShader::SShader() { - uniformLocations.fill(-1); +CShader::CShader() { + m_uniformLocations.fill(-1); } -SShader::~SShader() { +CShader::~CShader() { destroy(); } -void SShader::createVao() { - GLuint shaderVao = 0, shaderVbo = 0, shaderVboUv = 0; +void CShader::logShaderError(const GLuint& shader, bool program, bool silent) { + GLint maxLength = 0; + if (program) + glGetProgramiv(shader, GL_INFO_LOG_LENGTH, &maxLength); + else + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); + + std::vector errorLog(maxLength); + if (program) + glGetProgramInfoLog(shader, maxLength, &maxLength, errorLog.data()); + else + glGetShaderInfoLog(shader, maxLength, &maxLength, errorLog.data()); + std::string errorStr(errorLog.begin(), errorLog.end()); + + const auto FULLERROR = (program ? "Screen shader parser: Error linking program:" : "Screen shader parser: Error compiling shader: ") + errorStr; + + Log::logger->log(Log::ERR, "Failed to link shader: {}", FULLERROR); + + if (!silent) + g_pConfigManager->addParseError(FULLERROR); +} + +GLuint CShader::compileShader(const GLuint& type, std::string src, bool dynamic, bool silent) { + auto shader = glCreateShader(type); + + auto shaderSource = src.c_str(); + + glShaderSource(shader, 1, &shaderSource, nullptr); + glCompileShader(shader); + + GLint ok; + glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); + + if (dynamic) { + if (ok == GL_FALSE) { + logShaderError(shader, false, silent); + return 0; + } + } else { + if (ok != GL_TRUE) + logShaderError(shader, false); + RASSERT(ok != GL_FALSE, "compileShader() failed! GL_COMPILE_STATUS not OK!"); + } + + return shader; +} + +bool CShader::createProgram(const std::string& vert, const std::string& frag, bool dynamic, bool silent) { + auto vertCompiled = compileShader(GL_VERTEX_SHADER, vert, dynamic, silent); + if (dynamic) { + if (vertCompiled == 0) + return false; + } else + RASSERT(vertCompiled, "Compiling shader failed. VERTEX nullptr! Shader source:\n\n{}", vert); + + auto fragCompiled = compileShader(GL_FRAGMENT_SHADER, frag, dynamic, silent); + if (dynamic) { + if (fragCompiled == 0) + return false; + } else + RASSERT(fragCompiled, "Compiling shader failed. FRAGMENT nullptr! Shader source:\n\n{}", frag); + + auto prog = glCreateProgram(); + glAttachShader(prog, vertCompiled); + glAttachShader(prog, fragCompiled); + glLinkProgram(prog); + + glDetachShader(prog, vertCompiled); + glDetachShader(prog, fragCompiled); + glDeleteShader(vertCompiled); + glDeleteShader(fragCompiled); + + GLint ok; + glGetProgramiv(prog, GL_LINK_STATUS, &ok); + if (dynamic) { + if (ok == GL_FALSE) { + logShaderError(prog, true, silent); + return false; + } + } else { + if (ok != GL_TRUE) + logShaderError(prog, true); + RASSERT(ok != GL_FALSE, "createProgram() failed! GL_LINK_STATUS not OK!"); + } + + m_program = prog; + + getUniformLocations(); + createVao(); + return true; +} + +// its fine to call glGet on shaders that dont have the uniform +// this however hardcodes the name now. #TODO maybe dont +void CShader::getUniformLocations() { + auto getUniform = [this](const GLchar* name) { return glGetUniformLocation(m_program, name); }; + auto getAttrib = [this](const GLchar* name) { return glGetAttribLocation(m_program, name); }; + + m_uniformLocations[SHADER_PROJ] = getUniform("proj"); + m_uniformLocations[SHADER_COLOR] = getUniform("color"); + m_uniformLocations[SHADER_ALPHA_MATTE] = getUniform("texMatte"); + m_uniformLocations[SHADER_TEX_TYPE] = getUniform("texType"); + + // shader has #include "CM.glsl" + m_uniformLocations[SHADER_SOURCE_TF] = getUniform("sourceTF"); + m_uniformLocations[SHADER_TARGET_TF] = getUniform("targetTF"); + m_uniformLocations[SHADER_SRC_TF_RANGE] = getUniform("srcTFRange"); + m_uniformLocations[SHADER_DST_TF_RANGE] = getUniform("dstTFRange"); + m_uniformLocations[SHADER_TARGET_PRIMARIES_XYZ] = getUniform("targetPrimariesXYZ"); + m_uniformLocations[SHADER_MAX_LUMINANCE] = getUniform("maxLuminance"); + m_uniformLocations[SHADER_SRC_REF_LUMINANCE] = getUniform("srcRefLuminance"); + m_uniformLocations[SHADER_DST_MAX_LUMINANCE] = getUniform("dstMaxLuminance"); + m_uniformLocations[SHADER_DST_REF_LUMINANCE] = getUniform("dstRefLuminance"); + m_uniformLocations[SHADER_SDR_SATURATION] = getUniform("sdrSaturation"); + m_uniformLocations[SHADER_SDR_BRIGHTNESS] = getUniform("sdrBrightnessMultiplier"); + m_uniformLocations[SHADER_CONVERT_MATRIX] = getUniform("convertMatrix"); + m_uniformLocations[SHADER_LUT_3D] = getUniform("iccLut3D"); + m_uniformLocations[SHADER_LUT_SIZE] = getUniform("iccLutSize"); + // + m_uniformLocations[SHADER_TEX] = getUniform("tex"); + m_uniformLocations[SHADER_BLURRED_BG] = getUniform("blurredBG"); + m_uniformLocations[SHADER_UV_SIZE] = getUniform("uvSize"); + m_uniformLocations[SHADER_UV_OFFSET] = getUniform("uvOffset"); + m_uniformLocations[SHADER_ALPHA] = getUniform("alpha"); + m_uniformLocations[SHADER_POS_ATTRIB] = getAttrib("pos"); + m_uniformLocations[SHADER_TEX_ATTRIB] = getAttrib("texcoord"); + m_uniformLocations[SHADER_MATTE_TEX_ATTRIB] = getAttrib("texcoordMatte"); + m_uniformLocations[SHADER_DISCARD_OPAQUE] = getUniform("discardOpaque"); + m_uniformLocations[SHADER_DISCARD_ALPHA] = getUniform("discardAlpha"); + m_uniformLocations[SHADER_DISCARD_ALPHA_VALUE] = getUniform("discardAlphaValue"); + /* set in createVao + m_uniformLocations[SHADER_SHADER_VAO] + m_uniformLocations[SHADER_SHADER_VBO_POS] + m_uniformLocations[SHADER_SHADER_VBO_UV] + */ + m_uniformLocations[SHADER_TOP_LEFT] = getUniform("topLeft"); + m_uniformLocations[SHADER_BOTTOM_RIGHT] = getUniform("bottomRight"); + + // compat for screenshaders + auto fullSize = getUniform("fullSize"); + if (fullSize == -1) + fullSize = getUniform("screen_size"); + if (fullSize == -1) + fullSize = getUniform("screenSize"); + m_uniformLocations[SHADER_FULL_SIZE] = fullSize; + + m_uniformLocations[SHADER_FULL_SIZE_UNTRANSFORMED] = getUniform("fullSizeUntransformed"); + m_uniformLocations[SHADER_RADIUS] = getUniform("radius"); + m_uniformLocations[SHADER_RADIUS_OUTER] = getUniform("radiusOuter"); + m_uniformLocations[SHADER_ROUNDING_POWER] = getUniform("roundingPower"); + m_uniformLocations[SHADER_THICK] = getUniform("thick"); + m_uniformLocations[SHADER_HALFPIXEL] = getUniform("halfpixel"); + m_uniformLocations[SHADER_RANGE] = getUniform("range"); + m_uniformLocations[SHADER_SHADOW_POWER] = getUniform("shadowPower"); + m_uniformLocations[SHADER_USE_ALPHA_MATTE] = getUniform("useAlphaMatte"); + m_uniformLocations[SHADER_APPLY_TINT] = getUniform("applyTint"); + m_uniformLocations[SHADER_TINT] = getUniform("tint"); + m_uniformLocations[SHADER_GRADIENT] = getUniform("gradient"); + m_uniformLocations[SHADER_GRADIENT_LENGTH] = getUniform("gradientLength"); + m_uniformLocations[SHADER_GRADIENT2] = getUniform("gradient2"); + m_uniformLocations[SHADER_GRADIENT2_LENGTH] = getUniform("gradient2Length"); + m_uniformLocations[SHADER_ANGLE] = getUniform("angle"); + m_uniformLocations[SHADER_ANGLE2] = getUniform("angle2"); + m_uniformLocations[SHADER_GRADIENT_LERP] = getUniform("gradientLerp"); + m_uniformLocations[SHADER_TIME] = getUniform("time"); + m_uniformLocations[SHADER_DISTORT] = getUniform("distort"); + m_uniformLocations[SHADER_WL_OUTPUT] = getUniform("wl_output"); + m_uniformLocations[SHADER_CONTRAST] = getUniform("contrast"); + m_uniformLocations[SHADER_PASSES] = getUniform("passes"); + m_uniformLocations[SHADER_VIBRANCY] = getUniform("vibrancy"); + m_uniformLocations[SHADER_VIBRANCY_DARKNESS] = getUniform("vibrancy_darkness"); + m_uniformLocations[SHADER_BRIGHTNESS] = getUniform("brightness"); + m_uniformLocations[SHADER_NOISE] = getUniform("noise"); + m_uniformLocations[SHADER_POINTER] = getUniform("pointer_position"); + m_uniformLocations[SHADER_POINTER_SHAPE] = getUniform("pointer_shape"); + m_uniformLocations[SHADER_POINTER_SWITCH_TIME] = getUniform("pointer_switch_time"); + m_uniformLocations[SHADER_POINTER_SHAPE_PREVIOUS] = getUniform("pointer_shape_previous"); + m_uniformLocations[SHADER_POINTER_PRESSED_POSITIONS] = getUniform("pointer_pressed_positions"); + m_uniformLocations[SHADER_POINTER_HIDDEN] = getUniform("pointer_hidden"); + m_uniformLocations[SHADER_POINTER_KILLING] = getUniform("pointer_killing"); + m_uniformLocations[SHADER_POINTER_PRESSED_TIMES] = getUniform("pointer_pressed_times"); + m_uniformLocations[SHADER_POINTER_PRESSED_KILLED] = getUniform("pointer_pressed_killed"); + m_uniformLocations[SHADER_POINTER_PRESSED_TOUCHED] = getUniform("pointer_pressed_touched"); + m_uniformLocations[SHADER_POINTER_INACTIVE_TIMEOUT] = getUniform("pointer_inactive_timeout"); + m_uniformLocations[SHADER_POINTER_LAST_ACTIVE] = getUniform("pointer_last_active"); + m_uniformLocations[SHADER_POINTER_SIZE] = getUniform("pointer_size"); +} + +void CShader::createVao() { + GLuint shaderVao = 0, shaderVbo = 0; glGenVertexArrays(1, &shaderVao); glBindVertexArray(shaderVao); - if (uniformLocations[SHADER_POS_ATTRIB] != -1) { + if (m_uniformLocations[SHADER_POS_ATTRIB] != -1) { glGenBuffers(1, &shaderVbo); glBindBuffer(GL_ARRAY_BUFFER, shaderVbo); - glBufferData(GL_ARRAY_BUFFER, sizeof(fullVerts), fullVerts, GL_STATIC_DRAW); - glEnableVertexAttribArray(uniformLocations[SHADER_POS_ATTRIB]); - glVertexAttribPointer(uniformLocations[SHADER_POS_ATTRIB], 2, GL_FLOAT, GL_FALSE, 0, nullptr); + glBufferData(GL_ARRAY_BUFFER, sizeof(fullVerts), fullVerts.data(), GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(m_uniformLocations[SHADER_POS_ATTRIB]); + glVertexAttribPointer(m_uniformLocations[SHADER_POS_ATTRIB], 2, GL_FLOAT, GL_FALSE, sizeof(SVertex), (void*)offsetof(SVertex, x)); } // UV VBO (dynamic, may be updated per frame) - if (uniformLocations[SHADER_TEX_ATTRIB] != -1) { - glGenBuffers(1, &shaderVboUv); - glBindBuffer(GL_ARRAY_BUFFER, shaderVboUv); - glBufferData(GL_ARRAY_BUFFER, sizeof(fullVerts), fullVerts, GL_DYNAMIC_DRAW); // Initial dummy UVs - glEnableVertexAttribArray(uniformLocations[SHADER_TEX_ATTRIB]); - glVertexAttribPointer(uniformLocations[SHADER_TEX_ATTRIB], 2, GL_FLOAT, GL_FALSE, 0, nullptr); + if (m_uniformLocations[SHADER_TEX_ATTRIB] != -1 && shaderVbo != 0) { + glBindBuffer(GL_ARRAY_BUFFER, shaderVbo); + glEnableVertexAttribArray(m_uniformLocations[SHADER_TEX_ATTRIB]); + glVertexAttribPointer(m_uniformLocations[SHADER_TEX_ATTRIB], 2, GL_FLOAT, GL_FALSE, sizeof(SVertex), (void*)offsetof(SVertex, u)); } glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); - uniformLocations[SHADER_SHADER_VAO] = shaderVao; - uniformLocations[SHADER_SHADER_VBO_POS] = shaderVbo; - uniformLocations[SHADER_SHADER_VBO_UV] = shaderVboUv; + m_uniformLocations[SHADER_SHADER_VAO] = shaderVao; + m_uniformLocations[SHADER_SHADER_VBO] = shaderVbo; - RASSERT(uniformLocations[SHADER_SHADER_VAO] >= 0, "SHADER_SHADER_VAO could not be created"); - RASSERT(uniformLocations[SHADER_SHADER_VBO_POS] >= 0, "SHADER_SHADER_VBO_POS could not be created"); - RASSERT(uniformLocations[SHADER_SHADER_VBO_UV] >= 0, "SHADER_SHADER_VBO_UV could not be created"); + RASSERT(m_uniformLocations[SHADER_SHADER_VAO] >= 0, "SHADER_SHADER_VAO could not be created"); + RASSERT(m_uniformLocations[SHADER_SHADER_VBO] >= 0, "SHADER_SHADER_VBO_POS could not be created"); } -void SShader::setUniformInt(eShaderUniform location, GLint v0) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformInt(eShaderUniform location, GLint v0) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -67,11 +252,12 @@ void SShader::setUniformInt(eShaderUniform location, GLint v0) { return; cached = v0; - glUniform1i(uniformLocations[location], v0); + + GLCALL(glUniform1i(m_uniformLocations[location], v0)); } -void SShader::setUniformFloat(eShaderUniform location, GLfloat v0) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformFloat(eShaderUniform location, GLfloat v0) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -83,11 +269,11 @@ void SShader::setUniformFloat(eShaderUniform location, GLfloat v0) { } cached = v0; - glUniform1f(uniformLocations[location], v0); + GLCALL(glUniform1f(m_uniformLocations[location], v0)); } -void SShader::setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -99,11 +285,11 @@ void SShader::setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1) } cached = std::array{v0, v1}; - glUniform2f(uniformLocations[location], v0, v1); + GLCALL(glUniform2f(m_uniformLocations[location], v0, v1)); } -void SShader::setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -115,11 +301,11 @@ void SShader::setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, } cached = std::array{v0, v1, v2}; - glUniform3f(uniformLocations[location], v0, v1, v2); + GLCALL(glUniform3f(m_uniformLocations[location], v0, v1, v2)); } -void SShader::setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -131,11 +317,11 @@ void SShader::setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, } cached = std::array{v0, v1, v2, v3}; - glUniform4f(uniformLocations[location], v0, v1, v2, v3); + GLCALL(glUniform4f(m_uniformLocations[location], v0, v1, v2, v3)); } -void SShader::setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -147,11 +333,11 @@ void SShader::setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLbool } cached = SUniformMatrix3Data{.count = count, .transpose = transpose, .value = value}; - glUniformMatrix3fv(uniformLocations[location], count, transpose, value.data()); + GLCALL(glUniformMatrix3fv(m_uniformLocations[location], count, transpose, value.data())); } -void SShader::setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -163,11 +349,11 @@ void SShader::setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLbo } cached = SUniformMatrix4Data{.count = count, .transpose = transpose, .value = value}; - glUniformMatrix4x2fv(uniformLocations[location], count, transpose, value.data()); + GLCALL(glUniformMatrix4x2fv(m_uniformLocations[location], count, transpose, value.data())); } -void SShader::setUniformfv(eShaderUniform location, GLsizei count, const std::vector& value, GLsizei vec_size) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformfv(eShaderUniform location, GLsizei count, const std::vector& value, GLsizei vec_size) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -180,36 +366,35 @@ void SShader::setUniformfv(eShaderUniform location, GLsizei count, const std::ve cached = SUniformVData{.count = count, .value = value}; switch (vec_size) { - case 1: glUniform1fv(uniformLocations[location], count, value.data()); break; - case 2: glUniform2fv(uniformLocations[location], count, value.data()); break; - case 4: glUniform4fv(uniformLocations[location], count, value.data()); break; + case 1: GLCALL(glUniform1fv(m_uniformLocations[location], count, value.data())); break; + case 2: GLCALL(glUniform2fv(m_uniformLocations[location], count, value.data())); break; + case 4: GLCALL(glUniform4fv(m_uniformLocations[location], count, value.data())); break; default: UNREACHABLE(); } } -void SShader::setUniform1fv(eShaderUniform location, GLsizei count, const std::vector& value) { +void CShader::setUniform1fv(eShaderUniform location, GLsizei count, const std::vector& value) { setUniformfv(location, count, value, 1); } -void SShader::setUniform2fv(eShaderUniform location, GLsizei count, const std::vector& value) { +void CShader::setUniform2fv(eShaderUniform location, GLsizei count, const std::vector& value) { setUniformfv(location, count, value, 2); } -void SShader::setUniform4fv(eShaderUniform location, GLsizei count, const std::vector& value) { +void CShader::setUniform4fv(eShaderUniform location, GLsizei count, const std::vector& value) { setUniformfv(location, count, value, 4); } -void SShader::destroy() { +void CShader::destroy() { uniformStatus.fill(std::monostate()); - if (program == 0) + if (m_program == 0) return; - GLuint shaderVao, shaderVbo, shaderVboUv; + GLuint shaderVao, shaderVbo; - shaderVao = uniformLocations[SHADER_SHADER_VAO] == -1 ? 0 : uniformLocations[SHADER_SHADER_VAO]; - shaderVbo = uniformLocations[SHADER_SHADER_VBO_POS] == -1 ? 0 : uniformLocations[SHADER_SHADER_VBO_POS]; - shaderVboUv = uniformLocations[SHADER_SHADER_VBO_UV] == -1 ? 0 : uniformLocations[SHADER_SHADER_VBO_UV]; + shaderVao = m_uniformLocations[SHADER_SHADER_VAO] == -1 ? 0 : m_uniformLocations[SHADER_SHADER_VAO]; + shaderVbo = m_uniformLocations[SHADER_SHADER_VBO] == -1 ? 0 : m_uniformLocations[SHADER_SHADER_VBO]; if (shaderVao) glDeleteVertexArrays(1, &shaderVao); @@ -217,9 +402,22 @@ void SShader::destroy() { if (shaderVbo) glDeleteBuffers(1, &shaderVbo); - if (shaderVboUv) - glDeleteBuffers(1, &shaderVboUv); - - glDeleteProgram(program); - program = 0; + glDeleteProgram(m_program); + m_program = 0; +} + +GLint CShader::getUniformLocation(eShaderUniform location) const { + return m_uniformLocations[location]; +} + +GLuint CShader::program() const { + return m_program; +} + +int CShader::getInitialTime() const { + return m_initialTime; +} + +void CShader::setInitialTime(int time) { + m_initialTime = time; } diff --git a/src/render/Shader.hpp b/src/render/Shader.hpp index 4f545642..9b097c44 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,8 +30,7 @@ enum eShaderUniform : uint8_t { SHADER_DISCARD_ALPHA, SHADER_DISCARD_ALPHA_VALUE, SHADER_SHADER_VAO, - SHADER_SHADER_VBO_POS, - SHADER_SHADER_VBO_UV, + SHADER_SHADER_VBO, SHADER_TOP_LEFT, SHADER_BOTTOM_RIGHT, SHADER_FULL_SIZE, @@ -75,19 +74,41 @@ enum eShaderUniform : uint8_t { SHADER_POINTER_INACTIVE_TIMEOUT, SHADER_POINTER_LAST_ACTIVE, SHADER_POINTER_SIZE, + SHADER_LUT_3D, + SHADER_LUT_SIZE, + SHADER_BLURRED_BG, + SHADER_UV_SIZE, + SHADER_UV_OFFSET, SHADER_LAST, }; -struct SShader { - SShader(); - ~SShader(); +class CShader { + public: + CShader(); + ~CShader(); - GLuint program = 0; + bool createProgram(const std::string& vert, const std::string& frag, bool dynamic = false, bool silent = false); + void setUniformInt(eShaderUniform location, GLint v0); + void setUniformFloat(eShaderUniform location, GLfloat v0); + void setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1); + void setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2); + void setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); + void setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value); + void setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value); + void setUniform1fv(eShaderUniform location, GLsizei count, const std::vector& value); + void setUniform2fv(eShaderUniform location, GLsizei count, const std::vector& value); + void setUniform4fv(eShaderUniform location, GLsizei count, const std::vector& value); + void destroy(); + GLuint program() const; + GLint getUniformLocation(eShaderUniform location) const; + int getInitialTime() const; + void setInitialTime(int time); - std::array uniformLocations; - - float initialTime = 0; + private: + GLuint m_program = 0; + float m_initialTime = 0; + std::array m_uniformLocations; struct SUniformMatrix3Data { GLsizei count = 0; @@ -113,19 +134,9 @@ struct SShader { uniformStatus; // - void createVao(); - void setUniformInt(eShaderUniform location, GLint v0); - void setUniformFloat(eShaderUniform location, GLfloat v0); - void setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1); - void setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2); - void setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); - void setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value); - void setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value); - void setUniform1fv(eShaderUniform location, GLsizei count, const std::vector& value); - void setUniform2fv(eShaderUniform location, GLsizei count, const std::vector& value); - void setUniform4fv(eShaderUniform location, GLsizei count, const std::vector& value); - void destroy(); - - private: - void setUniformfv(eShaderUniform location, GLsizei count, const std::vector& value, GLsizei vec_size); + void logShaderError(const GLuint&, bool program = false, bool silent = false); + GLuint compileShader(const GLuint&, std::string, bool dynamic = false, bool silent = false); + void getUniformLocations(); + void createVao(); + void setUniformfv(eShaderUniform location, GLsizei count, const std::vector& value, GLsizei vec_size); }; diff --git a/src/render/ShaderLoader.cpp b/src/render/ShaderLoader.cpp new file mode 100644 index 00000000..0d2d0ee4 --- /dev/null +++ b/src/render/ShaderLoader.cpp @@ -0,0 +1,176 @@ +#include "ShaderLoader.hpp" +#include +#include +#include +#include +#include +#include "../debug/log/Logger.hpp" +#include "shaders/Shaders.hpp" +#include "../helpers/fs/FsUtils.hpp" +#include "Renderer.hpp" +#include +#include +#include + +using namespace Render; + +CShaderLoader::CShaderLoader(const std::vector includes, const std::array& frags, const std::string shaderPath) : m_shaderPath(shaderPath) { + m_callbacks = glsl_include_callbacks_t{ + .include_local = + [](void* ctx, const char* header_name, const char* includer_name, size_t include_depth) { + auto shaderLoader = sc(ctx); + auto res = new glsl_include_result_t; + if (shaderLoader->m_overrideDefines.length() && std::string{header_name} == "defines.h") { + res->header_name = header_name; + res->header_data = shaderLoader->m_overrideDefines.c_str(); + res->header_length = shaderLoader->m_overrideDefines.length(); + } else if (shaderLoader->includes().contains(header_name)) { + res->header_name = header_name; + res->header_data = shaderLoader->includes().at(header_name).c_str(); + res->header_length = shaderLoader->includes().at(header_name).length(); + } else { + res->header_name = nullptr; + res->header_data = nullptr; + res->header_length = 0; + } + + shaderLoader->m_includeResults.push_back(res); + return res; + }, + .free_include_result = + [](void* ctx, glsl_include_result_t* result) { + auto shaderLoader = sc(ctx); + std::erase(shaderLoader->m_includeResults, result); + delete result; + return 0; + }, + }; + + for (const auto& inc : includes) { + include(inc); + } + + std::ranges::transform(frags, m_fragFiles.begin(), [&](const auto& filename) { return loadShader(filename); }); +} + +CShaderLoader::~CShaderLoader() { + // glslFreeIncludeResult should leave it empty by this point + for (const auto& res : m_includeResults) { + delete res; + } +} + +void CShaderLoader::include(const std::string& filename) { + m_includes.insert({filename, loadShader(filename)}); +} + +std::string CShaderLoader::getDefines(ShaderFeatureFlags features) { + std::string res = ""; + std::map defines = { + {"USE_RGBA", features & SH_FEAT_RGBA ? "1" : "0"}, {"USE_DISCARD", features & SH_FEAT_DISCARD ? "1" : "0"}, {"USE_TINT", features & SH_FEAT_TINT ? "1" : "0"}, + {"USE_ROUNDING", features & SH_FEAT_ROUNDING ? "1" : "0"}, {"USE_CM", features & SH_FEAT_CM ? "1" : "0"}, {"USE_TONEMAP", features & SH_FEAT_TONEMAP ? "1" : "0"}, + {"USE_SDR_MOD", features & SH_FEAT_SDR_MOD ? "1" : "0"}, {"USE_BLUR", features & SH_FEAT_BLUR ? "1" : "0"}, {"USE_ICC", features & SH_FEAT_ICC ? "1" : "0"}, + }; + for (const auto& [name, value] : defines) { + res += std::format("#define {} {}\n", name, value); + } + return res; +} + +std::string CShaderLoader::processSource(const std::string& source, glslang_stage_t stage) { + const glslang_input_t input = { + .language = GLSLANG_SOURCE_GLSL, + .stage = stage, + .client = GLSLANG_CLIENT_NONE, + .target_language = GLSLANG_TARGET_NONE, + .code = source.c_str(), + .default_version = 100, + .default_profile = GLSLANG_NO_PROFILE, + .force_default_version_and_profile = false, + .forward_compatible = false, + .messages = GLSLANG_MSG_DEFAULT_BIT, + .resource = glslang_default_resource(), + .callbacks = m_callbacks, + .callbacks_ctx = this, + }; + + glslang_shader_t* shader = glslang_shader_create(&input); + + if (!glslang_shader_preprocess(shader, &input)) { + Log::logger->log(Log::ERR, "GLSL preprocessing failed"); + Log::logger->log(Log::ERR, "{}", glslang_shader_get_info_log(shader)); + Log::logger->log(Log::ERR, "{}", glslang_shader_get_info_debug_log(shader)); + Log::logger->log(Log::ERR, "{}", input.code); + glslang_shader_delete(shader); + return source; + } + + std::stringstream stream(glslang_shader_get_preprocessed_code(shader)); + std::string code = ""; + std::string line; + + while (std::getline(stream, line)) { + if (!line.starts_with("#line ")) + code += line + "\n"; + } + + return code; +} + +std::string CShaderLoader::process(const std::string& filename) { + auto source = loadShader(filename); + return processSource(source, filename.ends_with(".vert") ? GLSLANG_STAGE_VERTEX : GLSLANG_STAGE_FRAGMENT); +} + +std::string CShaderLoader::process(const std::string& filename, const std::map& defines) { + m_overrideDefines = ""; + for (const auto& [name, value] : defines) { + m_overrideDefines += std::format("#define {} {}\n", name, value); + } + const auto& res = process(filename); + m_overrideDefines = ""; + return res; +} + +std::string CShaderLoader::getVariantSource(ePreparedFragmentShader frag, ShaderFeatureFlags features) { + static const auto PCM = CConfigValue("render:cm_enabled"); + if (!*PCM) + features &= ~(SH_FEAT_CM | SH_FEAT_TONEMAP | SH_FEAT_SDR_MOD); + + if (!m_fragVariants[frag].contains(features)) { + ASSERT(m_fragFiles[frag].length()); + m_overrideDefines = getDefines(features); + m_fragVariants[frag][features] = processSource(m_fragFiles[frag]); + m_overrideDefines = ""; + } + + return m_fragVariants[frag][features]; +} + +const std::map& CShaderLoader::includes() { + return m_includes; +} + +// TODO notify user if bundled shader is newer than ~/.config override +std::string CShaderLoader::loadShader(const std::string& filename) { + if (m_shaderPath.length()) { + std::filesystem::path path = m_shaderPath; + const auto src = NFsUtils::readFileAsString(path / filename); + if (src.has_value()) + return src.value(); + } + const auto home = Hyprutils::Path::getHome(); + if (home.has_value()) { + const auto src = NFsUtils::readFileAsString(home.value() + "/hypr/shaders/" + filename); + if (src.has_value()) + return src.value(); + } + for (auto& e : ASSET_PATHS) { + const auto src = NFsUtils::readFileAsString(std::string{e} + "/hypr/shaders/" + filename); + if (src.has_value()) + return src.value(); + } + if (SHADERS.contains(filename)) + return SHADERS.at(filename); + throw std::runtime_error(std::format("Couldn't load shader {}", filename)); +} diff --git a/src/render/ShaderLoader.hpp b/src/render/ShaderLoader.hpp new file mode 100644 index 00000000..e522e9fa --- /dev/null +++ b/src/render/ShaderLoader.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "../helpers/memory/Memory.hpp" + +namespace Render { + enum ePreparedFragmentShaderFeature : uint16_t { + SH_FEAT_UNKNOWN = 0, // all features just in case + + SH_FEAT_RGBA = (1 << 0), // RGBA/RGBX texture sampling + SH_FEAT_DISCARD = (1 << 1), // RGBA/RGBX texture sampling + SH_FEAT_TINT = (1 << 2), // uniforms: tint; condition: applyTint + SH_FEAT_ROUNDING = (1 << 3), // uniforms: radius, roundingPower, topLeft, fullSize; condition: radius > 0 + SH_FEAT_CM = (1 << 4), // uniforms: srcTFRange, dstTFRange, srcRefLuminance, convertMatrix; condition: !skipCM + SH_FEAT_TONEMAP = (1 << 5), // uniforms: maxLuminance, dstMaxLuminance, dstRefLuminance; condition: maxLuminance < dstMaxLuminance * 1.01 + SH_FEAT_SDR_MOD = (1 << 6), // uniforms: sdrSaturation, sdrBrightnessMultiplier; condition: SDR <-> HDR && (sdrSaturation != 1 || sdrBrightnessMultiplier != 1) + SH_FEAT_BLUR = (1 << 7), // condition: render:use_shader_blur_blend + SH_FEAT_ICC = (1 << 8), // + + // uniforms: targetPrimariesXYZ; condition: SH_FEAT_TONEMAP || SH_FEAT_SDR_MOD + }; + + using ShaderFeatureFlags = uint16_t; + + enum ePreparedFragmentShader : uint8_t { + SH_FRAG_QUAD = 0, + SH_FRAG_PASSTHRURGBA, + SH_FRAG_MATTE, + SH_FRAG_EXT, + SH_FRAG_BLUR1, + SH_FRAG_BLUR2, + SH_FRAG_BLURPREPARE, + SH_FRAG_BLURFINISH, + SH_FRAG_SHADOW, + SH_FRAG_SURFACE, + SH_FRAG_BORDER1, + SH_FRAG_GLITCH, + + SH_FRAG_LAST, + }; + + class CShaderLoader { + public: + CShaderLoader(const std::vector includes, const std::array& frags, const std::string shaderPath = ""); + ~CShaderLoader(); + + void include(const std::string& filename); + std::string process(const std::string& filename); + std::string process(const std::string& filename, const std::map& defines); + + std::string getVariantSource(ePreparedFragmentShader frag, ShaderFeatureFlags features); + + const std::map& includes(); + + std::vector m_includeResults; + + private: + std::string loadShader(const std::string& filename); + std::string getDefines(ShaderFeatureFlags features); + std::string processSource(const std::string& source, glslang_stage_t stage = GLSLANG_STAGE_FRAGMENT); + + // + std::string m_shaderPath; + std::array m_fragFiles; + std::array, SH_FRAG_LAST> m_fragVariants; + std::map m_includes; + + std::string m_overrideDefines; + glsl_include_callbacks_t m_callbacks; + }; + + inline UP g_pShaderLoader; +} diff --git a/src/render/Texture.cpp b/src/render/Texture.cpp index 17c416c7..28ae4b41 100644 --- a/src/render/Texture.cpp +++ b/src/render/Texture.cpp @@ -1,207 +1,24 @@ #include "Texture.hpp" -#include "Renderer.hpp" -#include "../Compositor.hpp" -#include "../protocols/types/Buffer.hpp" -#include "../helpers/Format.hpp" #include -CTexture::CTexture() = default; - -CTexture::~CTexture() { - if (!g_pCompositor || g_pCompositor->m_isShuttingDown || !g_pHyprRenderer) - return; - - g_pHyprRenderer->makeEGLCurrent(); - destroyTexture(); -} - -CTexture::CTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size_, bool keepDataCopy) : m_drmFormat(drmFormat), m_keepDataCopy(keepDataCopy) { - createFromShm(drmFormat, pixels, stride, size_); -} - -CTexture::CTexture(const Aquamarine::SDMABUFAttrs& attrs, void* image) { - createFromDma(attrs, image); -} - -CTexture::CTexture(const SP buffer, bool keepDataCopy) : m_keepDataCopy(keepDataCopy) { - if (!buffer) - return; - - m_opaque = buffer->opaque; - - auto attrs = buffer->dmabuf(); - - if (!attrs.success) { - // attempt shm - auto shm = buffer->shm(); - - if (!shm.success) { - 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 b9811230..38c3ff01 100644 --- a/src/render/Texture.hpp +++ b/src/render/Texture.hpp @@ -3,6 +3,7 @@ #include "../defines.hpp" #include #include +#include class IHLBuffer; HYPRUTILS_FORWARD(Math, CRegion); @@ -11,59 +12,47 @@ enum eTextureType : int8_t { TEXTURE_INVALID = -1, // Invalid TEXTURE_RGBA = 0, // 4 channels TEXTURE_RGBX, // discard A + TEXTURE_3D_LUT, // 3D LUT TEXTURE_EXTERNAL, // EGLImage }; -class CTexture { +class ITexture { public: - CTexture(); + ITexture(ITexture&) = delete; + ITexture(ITexture&&) = delete; + ITexture(const ITexture&&) = delete; + ITexture(const ITexture&) = delete; - CTexture(CTexture&) = delete; - CTexture(CTexture&&) = delete; - CTexture(const CTexture&&) = delete; - CTexture(const CTexture&) = delete; + virtual ~ITexture() = default; - CTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy = false); + virtual void setTexParameter(GLenum pname, GLint param) = 0; + virtual void allocate(const Vector2D& size, uint32_t drmFormat = 0) = 0; + virtual void update(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const CRegion& damage) = 0; + virtual void bind() {}; + virtual void unbind() {}; + virtual bool ok(); + virtual bool isDMA(); - CTexture(const SP buffer, bool keepDataCopy = false); - // this ctor takes ownership of the eglImage. - CTexture(const Aquamarine::SDMABUFAttrs&, void* image); - ~CTexture(); - - void destroyTexture(); - void allocate(); - void update(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const CRegion& damage); const std::vector& dataCopy(); - void bind(); - void unbind(); - void setTexParameter(GLenum pname, GLint param); - eTextureType m_type = TEXTURE_RGBA; - GLenum m_target = GL_TEXTURE_2D; - GLuint m_texID = 0; - Vector2D m_size = {}; - void* m_eglImage = nullptr; - eTransform m_transform = HYPRUTILS_TRANSFORM_NORMAL; - bool m_opaque = false; + eTextureType m_type = TEXTURE_RGBA; + Vector2D m_size = {}; + eTransform m_transform = HYPRUTILS_TRANSFORM_NORMAL; + bool m_opaque = false; + uint32_t m_drmFormat = 0; // for shm bool m_isSynchronous = false; - private: - enum eTextureParam : uint8_t { - TEXTURE_PAR_WRAP_S = 0, - TEXTURE_PAR_WRAP_T, - TEXTURE_PAR_MAG_FILTER, - TEXTURE_PAR_MIN_FILTER, - TEXTURE_PAR_SWIZZLE_R, - TEXTURE_PAR_SWIZZLE_B, - TEXTURE_PAR_LAST, - }; + // TODO move to GLTexture + GLuint m_texID = 0; + GLenum magFilter = GL_LINEAR; // useNearestNeighbor overwrites these + GLenum minFilter = GL_LINEAR; - void createFromShm(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size); - void createFromDma(const Aquamarine::SDMABUFAttrs&, void* image); - inline constexpr std::optional getCacheStateIndex(GLenum pname); + protected: + ITexture() = default; + ITexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy = false, bool opaque = false); + ITexture(std::span lut3D, size_t N); - bool m_keepDataCopy = false; - std::vector m_dataCopy; - std::array, TEXTURE_PAR_LAST> m_cachedStates; + bool m_keepDataCopy = false; + std::vector m_dataCopy; }; diff --git a/src/render/Transformer.hpp b/src/render/Transformer.hpp index 048b1898..8f401859 100644 --- a/src/render/Transformer.hpp +++ b/src/render/Transformer.hpp @@ -14,7 +14,7 @@ class IWindowTransformer { // called by Hyprland. For more data about what is being rendered, inspect render data. // returns the out fb. - virtual CFramebuffer* transform(CFramebuffer* in) = 0; + virtual IFramebuffer* transform(IFramebuffer* in) = 0; // called by Hyprland before a window main pass is started. virtual void preWindowRender(CSurfacePassElement::SRenderData* pRenderData); diff --git a/src/render/decorations/CHyprBorderDecoration.cpp b/src/render/decorations/CHyprBorderDecoration.cpp index 75298ff7..3e4f04a9 100644 --- a/src/render/decorations/CHyprBorderDecoration.cpp +++ b/src/render/decorations/CHyprBorderDecoration.cpp @@ -4,7 +4,6 @@ #include "../../managers/eventLoop/EventLoopManager.hpp" #include "../pass/BorderPassElement.hpp" #include "../Renderer.hpp" -#include "../../managers/HookSystemManager.hpp" CHyprBorderDecoration::CHyprBorderDecoration(PHLWINDOW pWindow) : IHyprWindowDecoration(pWindow), m_window(pWindow) { ; @@ -115,26 +114,15 @@ void CHyprBorderDecoration::updateWindow(PHLWINDOW) { } void CHyprBorderDecoration::damageEntire() { - if (!validMapped(m_window)) + if (!validMapped(m_window) || m_window->m_fullscreenState.internal == FSMODE_FULLSCREEN) return; - auto surfaceBox = m_window->getWindowMainSurfaceBox(); - const auto ROUNDING = m_window->rounding(); - const auto ROUNDINGSIZE = ROUNDING - M_SQRT1_2 * ROUNDING + 2; - const auto BORDERSIZE = m_window->getRealBorderSize() + 1; + const auto GLOBAL_BOX = assignedBoxGlobal(); + const auto ROUNDING = m_window->rounding(); + const auto BORDERSIZE = m_window->getRealBorderSize() + 1; - const auto PWINDOWWORKSPACE = m_window->m_workspace; - if (PWINDOWWORKSPACE && PWINDOWWORKSPACE->m_renderOffset->isBeingAnimated() && !m_window->m_pinned) - surfaceBox.translate(PWINDOWWORKSPACE->m_renderOffset->value()); - surfaceBox.translate(m_window->m_floatingOffset); - - CBox surfaceBoxExpandedBorder = surfaceBox; - surfaceBoxExpandedBorder.expand(BORDERSIZE); - CBox surfaceBoxShrunkRounding = surfaceBox; - surfaceBoxShrunkRounding.expand(-ROUNDINGSIZE); - - CRegion borderRegion(surfaceBoxExpandedBorder); - borderRegion.subtract(surfaceBoxShrunkRounding); + CRegion borderRegion(GLOBAL_BOX); + borderRegion.subtract(GLOBAL_BOX.copy().expand(-(BORDERSIZE + ROUNDING))); for (auto const& m : g_pCompositor->m_monitors) { if (!g_pHyprRenderer->shouldRenderWindow(m_window.lock(), m)) { @@ -161,6 +149,5 @@ std::string CHyprBorderDecoration::getDisplayName() { } bool CHyprBorderDecoration::doesntWantBorders() { - return m_window->m_windowData.noBorder.valueOrDefault() || m_window->m_X11DoesntWantBorders || m_window->getRealBorderSize() == 0 || - !m_window->m_windowData.decorate.valueOrDefault(); + return m_window->m_X11DoesntWantBorders || m_window->getRealBorderSize() == 0 || !m_window->m_ruleApplicator->decorate().valueOrDefault(); } diff --git a/src/render/decorations/CHyprDropShadowDecoration.cpp b/src/render/decorations/CHyprDropShadowDecoration.cpp index bcc4c84e..5e1b6e8a 100644 --- a/src/render/decorations/CHyprDropShadowDecoration.cpp +++ b/src/render/decorations/CHyprDropShadowDecoration.cpp @@ -104,10 +104,10 @@ void CHyprDropShadowDecoration::render(PHLMONITOR pMonitor, float const& a) { if (PWINDOW->m_realShadowColor->value() == CHyprColor(0, 0, 0, 0)) return; // don't draw invisible shadows - if (!PWINDOW->m_windowData.decorate.valueOrDefault()) + if (!PWINDOW->m_ruleApplicator->decorate().valueOrDefault()) return; - if (PWINDOW->m_windowData.noShadow.valueOrDefault()) + if (PWINDOW->m_ruleApplicator->noShadow().valueOrDefault()) return; static auto PSHADOWS = CConfigValue("decoration:shadow:enabled"); @@ -155,9 +155,9 @@ void CHyprDropShadowDecoration::render(PHLMONITOR pMonitor, float const& a) { g_pHyprOpenGL->m_renderData.currentWindow = m_window; // we'll take the liberty of using this as it should not be used rn - CFramebuffer& alphaFB = g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorFB; - CFramebuffer& alphaSwapFB = g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorSwapFB; - auto* LASTFB = g_pHyprOpenGL->m_renderData.currentFB; + auto alphaFB = g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorFB; + auto alphaSwapFB = g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorSwapFB; + auto LASTFB = g_pHyprOpenGL->m_renderData.currentFB; fullBox.scale(pMonitor->m_scale).round(); @@ -188,7 +188,7 @@ void CHyprDropShadowDecoration::render(PHLMONITOR pMonitor, float const& a) { g_pHyprOpenGL->m_renderData.damage.subtract(windowBox.copy().expand(-ROUNDING * pMonitor->m_scale)).intersect(saveDamage); g_pHyprOpenGL->m_renderData.renderModif.applyToRegion(g_pHyprOpenGL->m_renderData.damage); - alphaFB.bind(); + alphaFB->bind(); // build the matte // 10-bit formats have dogshit alpha channels, so we have to use the matte to its fullest. @@ -202,7 +202,7 @@ void CHyprDropShadowDecoration::render(PHLMONITOR pMonitor, float const& a) { g_pHyprOpenGL->renderRect(windowBox, CHyprColor(0, 0, 0, 1.0), {.round = (ROUNDING + 1 /* This fixes small pixel gaps. */) * pMonitor->m_scale, .roundingPower = ROUNDINGPOWER}); - alphaSwapFB.bind(); + alphaSwapFB->bind(); // alpha swap just has the shadow color. It will be the "texture" to render. g_pHyprOpenGL->renderRect(fullBox, PWINDOW->m_realShadowColor->value().stripA(), {.round = 0}); @@ -213,7 +213,7 @@ void CHyprDropShadowDecoration::render(PHLMONITOR pMonitor, float const& a) { g_pHyprOpenGL->pushMonitorTransformEnabled(true); g_pHyprOpenGL->setRenderModifEnabled(false); - g_pHyprOpenGL->renderTextureMatte(alphaSwapFB.getTexture(), monbox, alphaFB); + g_pHyprOpenGL->renderTextureMatte(alphaSwapFB->getTexture(), monbox, alphaFB); g_pHyprOpenGL->setRenderModifEnabled(true); g_pHyprOpenGL->popMonitorTransformEnabled(); diff --git a/src/render/decorations/CHyprGroupBarDecoration.cpp b/src/render/decorations/CHyprGroupBarDecoration.cpp index 49fcd347..6ce69261 100644 --- a/src/render/decorations/CHyprGroupBarDecoration.cpp +++ b/src/render/decorations/CHyprGroupBarDecoration.cpp @@ -1,19 +1,22 @@ #include "CHyprGroupBarDecoration.hpp" #include "../../Compositor.hpp" #include "../../config/ConfigValue.hpp" -#include "managers/LayoutManager.hpp" +#include "../../desktop/state/FocusState.hpp" +#include "../../desktop/view/Group.hpp" #include #include #include "../pass/TexPassElement.hpp" #include "../pass/RectPassElement.hpp" #include "../Renderer.hpp" #include "../../managers/input/InputManager.hpp" +#include "../../layout/LayoutManager.hpp" +#include "../../layout/supplementary/DragController.hpp" // shared things to conserve VRAM -static SP m_tGradientActive = makeShared(); -static SP m_tGradientInactive = makeShared(); -static SP m_tGradientLockedActive = makeShared(); -static SP m_tGradientLockedInactive = makeShared(); +static SP m_tGradientActive; +static SP m_tGradientInactive; +static SP m_tGradientLockedActive; +static SP m_tGradientLockedInactive; constexpr int BAR_TEXT_PAD = 2; @@ -21,7 +24,16 @@ CHyprGroupBarDecoration::CHyprGroupBarDecoration(PHLWINDOW pWindow) : IHyprWindo static auto PGRADIENTS = CConfigValue("group:groupbar:enabled"); static auto PENABLED = CConfigValue("group:groupbar:gradients"); - if (m_tGradientActive->m_texID == 0 && *PENABLED && *PGRADIENTS) + if (!m_tGradientActive) + m_tGradientActive = g_pHyprRenderer->createTexture(); + if (!m_tGradientInactive) + m_tGradientInactive = g_pHyprRenderer->createTexture(); + if (!m_tGradientLockedActive) + m_tGradientLockedActive = g_pHyprRenderer->createTexture(); + if (!m_tGradientLockedInactive) + m_tGradientLockedInactive = g_pHyprRenderer->createTexture(); + + if (!m_tGradientActive->ok() && *PENABLED && *PGRADIENTS) refreshGroupBarGradients(); } @@ -64,19 +76,14 @@ eDecorationType CHyprGroupBarDecoration::getDecorationType() { // void CHyprGroupBarDecoration::updateWindow(PHLWINDOW pWindow) { - if (m_window->m_groupData.pNextWindow.expired()) { + if (!m_window->m_group) { m_window->removeWindowDeco(this); return; } m_dwGroupMembers.clear(); - PHLWINDOW head = pWindow->getGroupHead(); - m_dwGroupMembers.emplace_back(head); - - PHLWINDOW curr = head->m_groupData.pNextWindow.lock(); - while (curr != head) { - m_dwGroupMembers.emplace_back(curr); - curr = curr->m_groupData.pNextWindow.lock(); + for (const auto& w : m_window->m_group->windows()) { + m_dwGroupMembers.emplace_back(w); } damageEntire(); @@ -126,6 +133,8 @@ 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()); @@ -144,6 +153,8 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { float xoff = 0; float yoff = 0; + bool blur = *PBLUR != 0; + for (int i = 0; i < barsToDraw; ++i) { const auto WINDOWINDEX = *PSTACKED ? m_dwGroupMembers.size() - i - 1 : i; @@ -153,41 +164,34 @@ 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; - CHyprColor color = m_dwGroupMembers[WINDOWINDEX].lock() == g_pCompositor->m_lastWindow.lock() ? PCOLACTIVE->m_colors[0] : PCOLINACTIVE->m_colors[0]; + CHyprColor color = m_dwGroupMembers[WINDOWINDEX].lock() == Desktop::focusState()->window() ? PCOLACTIVE->m_colors[0] : PCOLINACTIVE->m_colors[0]; color.a *= a; if (!rect.empty()) { CRectPassElement::SRectData rectdata; rectdata.color = color; + rectdata.blur = blur; rectdata.box = rect; if (*PROUNDING) { + rectdata.round = *PROUNDING; rectdata.roundingPower = *PROUNDINGPOWER; if (*PROUNDONLYEDGES) { - static constexpr double PADDING = 20; - - if (i == 0 && barsToDraw == 1) - rectdata.round = *PROUNDING; - else if (i == 0) { - double first = rect.w - (*PROUNDING * 2); + rectdata.round = 0; + const double offset = *PROUNDING * 2; + if (i == 0) { rectdata.round = *PROUNDING; - rectdata.clipBox = CBox{rect.pos() - Vector2D{PADDING, 0.F}, Vector2D{first + PADDING, rect.h}}; - g_pHyprRenderer->m_renderPass.add(makeUnique(rectdata)); - rectdata.round = 0; - rectdata.clipBox = CBox{rect.pos() + Vector2D{first, 0.F}, Vector2D{rect.w - first + PADDING, rect.h}}; + rectdata.clipBox = rect; + rectdata.box = CBox{rect.pos(), Vector2D{rect.w + offset, rect.h}}; } else if (i == barsToDraw - 1) { - double first = *PROUNDING * 2; - rectdata.round = 0; - rectdata.clipBox = CBox{rect.pos() - Vector2D{PADDING, 0.F}, Vector2D{first + PADDING, rect.h}}; - g_pHyprRenderer->m_renderPass.add(makeUnique(rectdata)); rectdata.round = *PROUNDING; - rectdata.clipBox = CBox{rect.pos() + Vector2D{first, 0.F}, Vector2D{rect.w - first + PADDING, rect.h}}; + rectdata.clipBox = rect; + rectdata.box = CBox{rect.pos() - Vector2D{offset, 0.F}, Vector2D{rect.w + offset, rect.h}}; } - } else - rectdata.round = *PROUNDING; + } } g_pHyprRenderer->m_renderPass.add(makeUnique(rectdata)); } @@ -199,36 +203,30 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { if (!rect.empty()) { if (*PGRADIENTS) { - const auto GRADIENTTEX = (m_dwGroupMembers[WINDOWINDEX] == g_pCompositor->m_lastWindow ? (GROUPLOCKED ? m_tGradientLockedActive : m_tGradientActive) : - (GROUPLOCKED ? m_tGradientLockedInactive : m_tGradientInactive)); - if (GRADIENTTEX->m_texID) { + const auto GRADIENTTEX = (m_dwGroupMembers[WINDOWINDEX] == Desktop::focusState()->window() ? (GROUPLOCKED ? m_tGradientLockedActive : m_tGradientActive) : + (GROUPLOCKED ? m_tGradientLockedInactive : m_tGradientInactive)); + if (GRADIENTTEX->ok()) { CTexPassElement::SRenderData data; - data.tex = GRADIENTTEX; - data.box = rect; + data.tex = GRADIENTTEX; + data.blur = blur; + data.box = rect; + data.a = a; if (*PGRADIENTROUNDING) { + data.round = *PGRADIENTROUNDING; data.roundingPower = *PGRADIENTROUNDINGPOWER; if (*PGRADIENTROUNDINGONLYEDGES) { - static constexpr double PADDING = 20; - - if (i == 0 && barsToDraw == 1) - data.round = *PGRADIENTROUNDING; - else if (i == 0) { - double first = rect.w - (*PGRADIENTROUNDING * 2); + data.round = 0; + const double offset = *PGRADIENTROUNDING * 2; + if (i == 0) { data.round = *PGRADIENTROUNDING; - data.clipBox = CBox{rect.pos() - Vector2D{PADDING, 0.F}, Vector2D{first + PADDING, rect.h}}; - g_pHyprRenderer->m_renderPass.add(makeUnique(data)); - data.round = 0; - data.clipBox = CBox{rect.pos() + Vector2D{first, 0.F}, Vector2D{rect.w - first + PADDING, rect.h}}; + data.clipBox = rect; + data.box = CBox{rect.pos(), Vector2D{rect.w + offset, rect.h}}; } else if (i == barsToDraw - 1) { - double first = *PGRADIENTROUNDING * 2; - data.round = 0; - data.clipBox = CBox{rect.pos() - Vector2D{PADDING, 0.F}, Vector2D{first + PADDING, rect.h}}; - g_pHyprRenderer->m_renderPass.add(makeUnique(data)); data.round = *PGRADIENTROUNDING; - data.clipBox = CBox{rect.pos() + Vector2D{first, 0.F}, Vector2D{rect.w - first + PADDING, rect.h}}; + data.clipBox = rect; + data.box = CBox{rect.pos() - Vector2D{offset, 0.F}, Vector2D{rect.w + offset, rect.h}}; } - } else - data.round = *PGRADIENTROUNDING; + } } g_pHyprRenderer->m_renderPass.add(makeUnique(data)); } @@ -238,14 +236,15 @@ 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; - if (m_dwGroupMembers[WINDOWINDEX] == g_pCompositor->m_lastWindow) + SP titleTex; + if (m_dwGroupMembers[WINDOWINDEX] == Desktop::focusState()->window()) titleTex = GROUPLOCKED ? pTitleTex->m_texLockedActive : pTitleTex->m_texActive; else titleTex = GROUPLOCKED ? pTitleTex->m_texLockedInactive : pTitleTex->m_texInactive; @@ -253,7 +252,7 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { rect.y += std::ceil(((rect.height - titleTex->m_size.y) / 2.0) - (*PTEXTOFFSET * pMonitor->m_scale)); rect.height = titleTex->m_size.y; rect.width = titleTex->m_size.x; - rect.x += std::round(((m_barWidth * pMonitor->m_scale) / 2.0) - (titleTex->m_size.x / 2.0)); + rect.x += std::round((((m_barWidth + *PTEXTPADDING) * pMonitor->m_scale) / 2.0) - ((titleTex->m_size.x + *PTEXTPADDING) / 2.0)); rect.round(); CTexPassElement::SRenderData data; @@ -317,12 +316,12 @@ CTitleTex::CTitleTex(PHLWINDOW pWindow, const Vector2D& bufferSize, const float #undef RENDER_TEXT } -static void renderGradientTo(SP tex, CGradientValueData* grad) { +static void renderGradientTo(SP tex, CGradientValueData* grad) { - if (!g_pCompositor->m_lastMonitor) + if (!Desktop::focusState()->monitor()) return; - const Vector2D& bufferSize = g_pCompositor->m_lastMonitor->m_pixelSize; + const Vector2D& bufferSize = Desktop::focusState()->monitor()->m_pixelSize; const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, bufferSize.x, bufferSize.y); const auto CAIRO = cairo_create(CAIROSURFACE); @@ -349,15 +348,7 @@ static void renderGradientTo(SP tex, CGradientValueData* grad) { cairo_surface_flush(CAIROSURFACE); // copy the data to an OpenGL texture we have - const auto DATA = cairo_image_surface_get_data(CAIROSURFACE); - tex->allocate(); - tex->bind(); - tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); - tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); - tex->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); - tex->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bufferSize.x, bufferSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA); + tex = g_pHyprRenderer->createTexture(CAIROSURFACE); // delete cairo cairo_destroy(CAIRO); @@ -377,13 +368,11 @@ void refreshGroupBarGradients() { auto* const GROUPCOLACTIVELOCKED = sc((PGROUPCOLACTIVELOCKED.ptr())->getData()); auto* const GROUPCOLINACTIVELOCKED = sc((PGROUPCOLINACTIVELOCKED.ptr())->getData()); - g_pHyprRenderer->makeEGLCurrent(); - - if (m_tGradientActive->m_texID != 0) { - m_tGradientActive->destroyTexture(); - m_tGradientInactive->destroyTexture(); - m_tGradientLockedActive->destroyTexture(); - m_tGradientLockedInactive->destroyTexture(); + if (m_tGradientActive && m_tGradientActive->ok()) { + m_tGradientActive.reset(); + m_tGradientInactive.reset(); + m_tGradientLockedActive.reset(); + m_tGradientLockedInactive.reset(); } if (!*PENABLED || !*PGRADIENTS) @@ -399,7 +388,7 @@ bool CHyprGroupBarDecoration::onBeginWindowDragOnDeco(const Vector2D& pos) { static auto PSTACKED = CConfigValue("group:groupbar:stacked"); static auto POUTERGAP = CConfigValue("group:groupbar:gaps_out"); static auto PINNERGAP = CConfigValue("group:groupbar:gaps_in"); - if (m_window.lock() == m_window->m_groupData.pNextWindow.lock()) + if (m_window->m_group->size() == 1) return false; const float BARRELATIVEX = pos.x - assignedBoxGlobal().x; @@ -412,95 +401,33 @@ bool CHyprGroupBarDecoration::onBeginWindowDragOnDeco(const Vector2D& pos) { if (*PSTACKED && (BARRELATIVEY - (m_barHeight + *POUTERGAP) * WINDOWINDEX < *POUTERGAP)) return false; - PHLWINDOW pWindow = m_window->getGroupWindowByIndex(WINDOWINDEX); + PHLWINDOW pWindow = m_window->m_group->fromIndex(WINDOWINDEX); - // hack - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow); - if (!pWindow->m_isFloating) { - const bool GROUPSLOCKEDPREV = g_pKeybindManager->m_groupsLocked; - g_pKeybindManager->m_groupsLocked = true; - g_pLayoutManager->getCurrentLayout()->onWindowCreated(pWindow); - g_pKeybindManager->m_groupsLocked = GROUPSLOCKEDPREV; - } + const auto& GROUP = m_window->m_group; - g_pInputManager->m_currentlyDraggedWindow = pWindow; + // remove the window from the group + GROUP->remove(pWindow); + + // start a move drag on it + g_layoutManager->dragController()->dragBegin(pWindow->layoutTarget(), MBIND_MOVE); if (!g_pCompositor->isWindowActive(pWindow)) - g_pCompositor->focusWindow(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)); @@ -527,7 +454,7 @@ bool CHyprGroupBarDecoration::onMouseButtonOnDeco(const Vector2D& pos, const IPo if (e.state == WL_POINTER_BUTTON_STATE_PRESSED) pressedCursorPos = pos; else if (e.state == WL_POINTER_BUTTON_STATE_RELEASED && pressedCursorPos == pos) - g_pXWaylandManager->sendCloseWindow(m_window->getGroupWindowByIndex(WINDOWINDEX)); + g_pXWaylandManager->sendCloseWindow(m_window->m_group->fromIndex(WINDOWINDEX)); return true; } @@ -540,17 +467,17 @@ bool CHyprGroupBarDecoration::onMouseButtonOnDeco(const Vector2D& pos, const IPo const auto STACKPAD = *PSTACKED && (BARRELATIVEY - (m_barHeight + *POUTERGAP) * WINDOWINDEX < *POUTERGAP); if (TABPAD || STACKPAD) { if (!g_pCompositor->isWindowActive(m_window.lock())) - g_pCompositor->focusWindow(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) - g_pCompositor->focusWindow(pWindow); + Desktop::focusState()->rawWindowFocus(pWindow, Desktop::FOCUS_REASON_CLICK); if (pWindow->m_isFloating) g_pCompositor->changeWindowZOrder(pWindow, true); @@ -561,13 +488,13 @@ bool CHyprGroupBarDecoration::onMouseButtonOnDeco(const Vector2D& pos, const IPo bool CHyprGroupBarDecoration::onScrollOnDeco(const Vector2D& pos, const IPointer::SAxisEvent e) { static auto PGROUPBARSCROLLING = CConfigValue("group:groupbar:scrolling"); - if (!*PGROUPBARSCROLLING || m_window->m_groupData.pNextWindow.expired()) + if (!*PGROUPBARSCROLLING || !m_window->m_group) return false; if (e.delta > 0) - m_window->setGroupCurrent(m_window->m_groupData.pNextWindow.lock()); + m_window->m_group->moveCurrent(true); else - m_window->setGroupCurrent(m_window->getGroupPrevious()); + m_window->m_group->moveCurrent(false); return true; } @@ -608,5 +535,5 @@ CBox CHyprGroupBarDecoration::assignedBoxGlobal() { bool CHyprGroupBarDecoration::visible() { static auto PENABLED = CConfigValue("group:groupbar:enabled"); - return *PENABLED && m_window->m_windowData.decorate.valueOrDefault(); + return *PENABLED && m_window->m_ruleApplicator->decorate().valueOrDefault(); } diff --git a/src/render/decorations/CHyprGroupBarDecoration.hpp b/src/render/decorations/CHyprGroupBarDecoration.hpp index 3e5d3c2d..5c3f4ae5 100644 --- a/src/render/decorations/CHyprGroupBarDecoration.hpp +++ b/src/render/decorations/CHyprGroupBarDecoration.hpp @@ -12,10 +12,10 @@ class CTitleTex { CTitleTex(PHLWINDOW pWindow, const Vector2D& bufferSize, const float monitorScale); ~CTitleTex() = default; - SP m_texActive; - SP m_texInactive; - SP m_texLockedActive; - SP m_texLockedInactive; + SP m_texActive; + SP m_texInactive; + SP m_texLockedActive; + SP m_texLockedInactive; std::string m_content; PHLWINDOWREF m_windowOwner; diff --git a/src/render/decorations/DecorationPositioner.cpp b/src/render/decorations/DecorationPositioner.cpp index 1082812f..470b5bb7 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,7 +50,7 @@ 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 {}; } @@ -214,29 +207,23 @@ void CDecorationPositioner::onWindowUpdate(PHLWINDOW pWindow) { continue; } - auto desiredSize = 0; - if (LEFT) - desiredSize = wd->positioningInfo.desiredExtents.topLeft.x; - else if (RIGHT) - desiredSize = wd->positioningInfo.desiredExtents.bottomRight.x; - else if (TOP) - desiredSize = wd->positioningInfo.desiredExtents.topLeft.y; - else - desiredSize = wd->positioningInfo.desiredExtents.bottomRight.y; + const auto desiredExtents = wd->positioningInfo.desiredExtents; const auto EDGEPOINT = getEdgeDefinedPoint(wd->positioningInfo.edges, pWindow); Vector2D pos, size; if (EDGESNO == 4) { - pos = wb.pos() - EDGEPOINT - Vector2D{stickyOffsetXL + desiredSize, stickyOffsetYT + desiredSize}; - size = wb.size() + Vector2D{stickyOffsetXL + stickyOffsetXR + desiredSize * 2, stickyOffsetYB + stickyOffsetYT + desiredSize * 2}; + stickyOffsetXL += desiredExtents.topLeft.x; + stickyOffsetXR += desiredExtents.bottomRight.x; + stickyOffsetYT += desiredExtents.topLeft.y; + stickyOffsetYB += desiredExtents.bottomRight.y; - stickyOffsetXL += desiredSize; - stickyOffsetXR += desiredSize; - stickyOffsetYT += desiredSize; - stickyOffsetYB += desiredSize; + pos = wb.pos() - EDGEPOINT - Vector2D{stickyOffsetXL, stickyOffsetYT}; + size = wb.size() + Vector2D{stickyOffsetXL + stickyOffsetXR, stickyOffsetYB + stickyOffsetYT}; } else if (LEFT) { + const auto desiredSize = desiredExtents.topLeft.x; + pos = wb.pos() - EDGEPOINT - Vector2D{stickyOffsetXL, -stickyOffsetYT}; pos.x -= desiredSize; size = {sc(desiredSize), wb.size().y + stickyOffsetYB + stickyOffsetYT}; @@ -244,12 +231,16 @@ void CDecorationPositioner::onWindowUpdate(PHLWINDOW pWindow) { if (SOLID) stickyOffsetXL += desiredSize; } else if (RIGHT) { + const auto desiredSize = desiredExtents.bottomRight.x; + pos = wb.pos() + Vector2D{wb.size().x, 0.0} - EDGEPOINT + Vector2D{stickyOffsetXR, -stickyOffsetYT}; size = {sc(desiredSize), wb.size().y + stickyOffsetYB + stickyOffsetYT}; if (SOLID) stickyOffsetXR += desiredSize; } else if (TOP) { + const auto desiredSize = desiredExtents.topLeft.y; + pos = wb.pos() - EDGEPOINT - Vector2D{stickyOffsetXL, stickyOffsetYT}; pos.y -= desiredSize; size = {wb.size().x + stickyOffsetXL + stickyOffsetXR, sc(desiredSize)}; @@ -257,6 +248,8 @@ void CDecorationPositioner::onWindowUpdate(PHLWINDOW pWindow) { if (SOLID) stickyOffsetYT += desiredSize; } else { + const auto desiredSize = desiredExtents.bottomRight.y; + pos = wb.pos() + Vector2D{0.0, wb.size().y} - EDGEPOINT - Vector2D{stickyOffsetXL, stickyOffsetYB}; size = {wb.size().x + stickyOffsetXL + stickyOffsetXR, sc(desiredSize)}; @@ -278,7 +271,7 @@ void CDecorationPositioner::onWindowUpdate(PHLWINDOW pWindow) { if (WINDOWDATA->extents != SBoxExtents{{stickyOffsetXL + reservedXL, stickyOffsetYT + reservedYT}, {stickyOffsetXR + reservedXR, stickyOffsetYB + reservedYB}}) { WINDOWDATA->extents = {{stickyOffsetXL + reservedXL, stickyOffsetYT + reservedYT}, {stickyOffsetXR + reservedXR, stickyOffsetYB + reservedYB}}; - g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow); + pWindow->layoutTarget()->recalc(); } } diff --git a/src/render/decorations/DecorationPositioner.hpp b/src/render/decorations/DecorationPositioner.hpp index 8048c7ad..8fbb44c7 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 { diff --git a/src/render/decorations/IHyprWindowDecoration.cpp b/src/render/decorations/IHyprWindowDecoration.cpp index 0dd11867..26782098 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 26bfcb45..9916d392 100644 --- a/src/render/decorations/IHyprWindowDecoration.hpp +++ b/src/render/decorations/IHyprWindowDecoration.hpp @@ -26,7 +26,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/GLFramebuffer.cpp b/src/render/gl/GLFramebuffer.cpp new file mode 100644 index 00000000..d821f766 --- /dev/null +++ b/src/render/gl/GLFramebuffer.cpp @@ -0,0 +1,170 @@ +#include "GLFramebuffer.hpp" +#include "../OpenGL.hpp" +#include "../Renderer.hpp" +#include "macros.hpp" +#include "render/Framebuffer.hpp" + +CGLFramebuffer::CGLFramebuffer() : IFramebuffer() {} +CGLFramebuffer::CGLFramebuffer(const std::string& name) : IFramebuffer(name) {} + +bool CGLFramebuffer::internalAlloc(int w, int h, uint32_t drmFormat) { + g_pHyprRenderer->makeEGLCurrent(); + + bool firstAlloc = false; + + if (!m_tex) { + m_tex = g_pHyprRenderer->createTexture(); + m_tex->allocate({w, h}); + m_tex->bind(); + m_tex->setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + m_tex->setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + m_tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); + m_tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); + firstAlloc = true; + } + + if (!m_fbAllocated) { + glGenFramebuffers(1, &m_fb); + m_fbAllocated = true; + firstAlloc = true; + } + + if (firstAlloc) { + const auto format = NFormatUtils::getPixelFormatFromDRM(drmFormat); + m_tex->bind(); + glTexImage2D(GL_TEXTURE_2D, 0, format->glInternalFormat ? format->glInternalFormat : format->glFormat, w, h, 0, format->glFormat, format->glType, nullptr); + glBindFramebuffer(GL_FRAMEBUFFER, m_fb); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_tex->m_texID, 0); + + if (m_stencilTex && m_stencilTex->ok()) { + m_stencilTex->bind(); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, w, h, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_stencilTex->m_texID, 0); + + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + } + + auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + RASSERT((status == GL_FRAMEBUFFER_COMPLETE), "Framebuffer incomplete, couldn't create! (FB status: {}, GL Error: 0x{:x})", status, sc(glGetError())); + + if (m_stencilTex && m_stencilTex->ok()) + m_stencilTex->unbind(); + + Log::logger->log(Log::DEBUG, "Framebuffer created, status {}", status); + } + + glBindTexture(GL_TEXTURE_2D, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + return true; +} + +void CGLFramebuffer::addStencil(SP tex) { + if (m_stencilTex == tex) + return; + + RASSERT(!m_fbAllocated, "Should add stencil tex prior to FB allocation") + m_stencilTex = tex; +} + +void CGLFramebuffer::bind() { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fb); + + if (g_pHyprOpenGL) + g_pHyprOpenGL->setViewport(0, 0, g_pHyprOpenGL->m_renderData.pMonitor->m_pixelSize.x, g_pHyprOpenGL->m_renderData.pMonitor->m_pixelSize.y); + else + glViewport(0, 0, m_size.x, m_size.y); +} + +void CGLFramebuffer::unbind() { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); +} + +void CGLFramebuffer::release() { + if (m_fbAllocated) { + glBindFramebuffer(GL_FRAMEBUFFER, m_fb); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + glDeleteFramebuffers(1, &m_fb); + m_fbAllocated = false; + m_fb = 0; + } + + if (m_tex) + m_tex.reset(); + + m_size = Vector2D(); +} + +bool CGLFramebuffer::readPixels(CHLBufferReference buffer, uint32_t offsetX, uint32_t offsetY, uint32_t width, uint32_t height) { + auto shm = buffer->shm(); + auto [pixelData, fmt, bufLen] = buffer->beginDataPtr(0); // no need for end, cuz it's shm + + const auto PFORMAT = NFormatUtils::getPixelFormatFromDRM(shm.format); + if (!PFORMAT) { + LOGM(Log::ERR, "Can't copy: failed to find a pixel format"); + return false; + } + + g_pHyprRenderer->makeEGLCurrent(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, getFBID()); + bind(); + + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + uint32_t packStride = NFormatUtils::minStride(PFORMAT, m_size.x); + int glFormat = PFORMAT->glFormat; + + if (glFormat == GL_RGBA) + glFormat = GL_BGRA_EXT; + + if (glFormat != GL_BGRA_EXT && glFormat != GL_RGB) { + if (PFORMAT->swizzle.has_value()) { + std::array RGBA = SWIZZLE_RGBA; + std::array BGRA = SWIZZLE_BGRA; + if (PFORMAT->swizzle == RGBA) + glFormat = GL_RGBA; + else if (PFORMAT->swizzle == BGRA) + glFormat = GL_BGRA_EXT; + else { + LOGM(Log::ERR, "Copied frame via shm might be broken or color flipped"); + glFormat = GL_RGBA; + } + } + } + + // This could be optimized by using a pixel buffer object to make this async, + // but really clients should just use a dma buffer anyways. + if (packStride == sc(shm.stride)) { + glReadPixels(offsetX, offsetY, width > 0 ? width : m_size.x, height > 0 ? height : m_size.y, glFormat, PFORMAT->glType, pixelData); + } else { + const auto h = height > 0 ? height : m_size.y; + for (size_t i = 0; i < h; ++i) { + uint32_t y = i; + glReadPixels(offsetX, offsetY + y, width > 0 ? width : m_size.x, 1, glFormat, PFORMAT->glType, pixelData + i * shm.stride); + } + } + + unbind(); + glPixelStorei(GL_PACK_ALIGNMENT, 4); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + return true; +} + +CGLFramebuffer::~CGLFramebuffer() { + release(); +} + +GLuint CGLFramebuffer::getFBID() { + return m_fbAllocated ? m_fb : 0; +} + +void CGLFramebuffer::invalidate(const std::vector& attachments) { + if (!isAllocated()) + return; + + glInvalidateFramebuffer(GL_FRAMEBUFFER, attachments.size(), attachments.data()); +} diff --git a/src/render/gl/GLFramebuffer.hpp b/src/render/gl/GLFramebuffer.hpp new file mode 100644 index 00000000..c171444e --- /dev/null +++ b/src/render/gl/GLFramebuffer.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "../../defines.hpp" +#include "../Texture.hpp" +#include "../Framebuffer.hpp" +#include + +class CGLFramebuffer : public IFramebuffer { + public: + CGLFramebuffer(); + CGLFramebuffer(const std::string& name); + ~CGLFramebuffer(); + + void addStencil(SP tex) override; + void release() override; + bool readPixels(CHLBufferReference buffer, uint32_t offsetX = 0, uint32_t offsetY = 0, uint32_t width = 0, uint32_t height = 0) override; + + void bind() override; + void unbind(); + GLuint getFBID(); + void invalidate(const std::vector& attachments); + + protected: + bool internalAlloc(int w, int h, uint32_t format = DRM_FORMAT_ARGB8888) override; + + private: + GLuint m_fb = -1; + + friend class CGLRenderbuffer; +}; diff --git a/src/render/gl/GLRenderbuffer.cpp b/src/render/gl/GLRenderbuffer.cpp new file mode 100644 index 00000000..8299d0e4 --- /dev/null +++ b/src/render/gl/GLRenderbuffer.cpp @@ -0,0 +1,71 @@ +#include "GLRenderbuffer.hpp" +#include "../Renderer.hpp" +#include "../OpenGL.hpp" +#include "../../Compositor.hpp" +#include "../Framebuffer.hpp" +#include "GLFramebuffer.hpp" +#include "render/Renderbuffer.hpp" +#include +#include +#include + +#include + +CGLRenderbuffer::~CGLRenderbuffer() { + if (!g_pCompositor || g_pCompositor->m_isShuttingDown || !g_pHyprRenderer) + return; + + g_pHyprRenderer->makeEGLCurrent(); + + unbind(); + m_framebuffer->release(); + + if (m_rbo) + glDeleteRenderbuffers(1, &m_rbo); + + if (m_image != EGL_NO_IMAGE_KHR) + g_pHyprOpenGL->m_proc.eglDestroyImageKHR(g_pHyprOpenGL->m_eglDisplay, m_image); +} + +CGLRenderbuffer::CGLRenderbuffer(SP buffer, uint32_t format) : IRenderbuffer(buffer, format) { + auto dma = buffer->dmabuf(); + + m_image = g_pHyprOpenGL->createEGLImage(dma); + if (m_image == EGL_NO_IMAGE_KHR) { + Log::logger->log(Log::ERR, "rb: createEGLImage failed"); + return; + } + + glGenRenderbuffers(1, &m_rbo); + glBindRenderbuffer(GL_RENDERBUFFER, m_rbo); + g_pHyprOpenGL->m_proc.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, m_image); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + m_framebuffer = makeShared(); + glGenFramebuffers(1, &GLFB(m_framebuffer)->m_fb); + GLFB(m_framebuffer)->m_fbAllocated = true; + m_framebuffer->m_size = buffer->size; + m_framebuffer->m_drmFormat = dma.format; + m_framebuffer->bind(); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_rbo); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + Log::logger->log(Log::ERR, "rbo: glCheckFramebufferStatus failed"); + return; + } + + GLFB(m_framebuffer)->unbind(); + + m_listeners.destroyBuffer = buffer->events.destroy.listen([this] { g_pHyprRenderer->onRenderbufferDestroy(this); }); + + m_good = true; +} + +void CGLRenderbuffer::bind() { + g_pHyprRenderer->makeEGLCurrent(); + m_framebuffer->bind(); +} + +void CGLRenderbuffer::unbind() { + GLFB(m_framebuffer)->unbind(); +} diff --git a/src/render/gl/GLRenderbuffer.hpp b/src/render/gl/GLRenderbuffer.hpp new file mode 100644 index 00000000..8367f702 --- /dev/null +++ b/src/render/gl/GLRenderbuffer.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "../../helpers/memory/Memory.hpp" +#include "../Renderbuffer.hpp" +#include + +class CMonitor; + +class CGLRenderbuffer : public IRenderbuffer { + public: + CGLRenderbuffer(SP buffer, uint32_t format); + ~CGLRenderbuffer(); + + void bind() override; + void unbind() override; + + private: + void* m_image = nullptr; + GLuint m_rbo = 0; +}; diff --git a/src/render/gl/GLTexture.cpp b/src/render/gl/GLTexture.cpp new file mode 100644 index 00000000..6a1fb172 --- /dev/null +++ b/src/render/gl/GLTexture.cpp @@ -0,0 +1,223 @@ +#include "GLTexture.hpp" +#include "../Renderer.hpp" +#include "../../Compositor.hpp" +#include "../../helpers/Format.hpp" +#include "render/Texture.hpp" +#include + +CGLTexture::CGLTexture(bool opaque) { + m_opaque = opaque; +} + +CGLTexture::~CGLTexture() { + if (!g_pCompositor || g_pCompositor->m_isShuttingDown || !g_pHyprRenderer) + return; + + g_pHyprRenderer->makeEGLCurrent(); + if (m_texID) { + GLCALL(glDeleteTextures(1, &m_texID)); + m_texID = 0; + } + + if (m_eglImage) + g_pHyprOpenGL->m_proc.eglDestroyImageKHR(g_pHyprOpenGL->m_eglDisplay, m_eglImage); + m_eglImage = nullptr; + m_cachedStates.fill(std::nullopt); +} + +CGLTexture::CGLTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size_, bool keepDataCopy, bool opaque) : + ITexture(drmFormat, pixels, stride, size_, keepDataCopy, opaque) { + + g_pHyprRenderer->makeEGLCurrent(); + + const auto format = NFormatUtils::getPixelFormatFromDRM(drmFormat); + ASSERT(format); + + m_type = format->withAlpha ? TEXTURE_RGBA : TEXTURE_RGBX; + m_size = size_; + m_isSynchronous = true; + m_target = GL_TEXTURE_2D; + allocate(size_); + bind(); + setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + if (format->swizzle.has_value()) + swizzle(format->swizzle.value()); + + bool alignmentChanged = false; + if (format->bytesPerBlock != 4) { + const GLint alignment = (stride % 4 == 0) ? 4 : 1; + GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, alignment)); + alignmentChanged = true; + } + + GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / format->bytesPerBlock)); + GLCALL(glTexImage2D(GL_TEXTURE_2D, 0, format->glInternalFormat ? format->glInternalFormat : format->glFormat, size_.x, size_.y, 0, format->glFormat, format->glType, pixels)); + GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0)); + if (alignmentChanged) + GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 4)); + + unbind(); +} + +CGLTexture::CGLTexture(const Aquamarine::SDMABUFAttrs& attrs, void* image, bool opaque) { + m_opaque = opaque; + if (!g_pHyprOpenGL->m_proc.glEGLImageTargetTexture2DOES) { + Log::logger->log(Log::ERR, "Cannot create a dmabuf texture: no glEGLImageTargetTexture2DOES"); + return; + } + + m_opaque = NFormatUtils::isFormatOpaque(attrs.format); + + // #TODO external only formats should be external aswell. + // also needs a seperate color shader. + /*if (NFormatUtils::isFormatYUV(attrs.format)) { + m_target = GL_TEXTURE_EXTERNAL_OES; + m_type = TEXTURE_EXTERNAL; + } else {*/ + m_target = GL_TEXTURE_2D; + m_type = NFormatUtils::isFormatOpaque(attrs.format) ? TEXTURE_RGBX : TEXTURE_RGBA; + //} + + allocate(attrs.size); + m_eglImage = image; + + bind(); + setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + GLCALL(g_pHyprOpenGL->m_proc.glEGLImageTargetTexture2DOES(m_target, image)); + unbind(); +} + +CGLTexture::CGLTexture(std::span lut3D, size_t N) : ITexture(lut3D, N), m_target(GL_TEXTURE_3D) { + allocate({}); + bind(); + + GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); + setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); + setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + setTexParameter(GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + + // Expand RGB->RGBA on upload (alpha=1) + std::vector rgba; + rgba.resize(N * N * N * 4); + for (size_t i = 0, j = 0; i < N * N * N; ++i, j += 3) { + rgba[i * 4 + 0] = lut3D[j + 0]; + rgba[i * 4 + 1] = lut3D[j + 1]; + rgba[i * 4 + 2] = lut3D[j + 2]; + rgba[i * 4 + 3] = 1.F; + } + + GLCALL(glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA16F, N, N, N, 0, GL_RGBA, GL_FLOAT, rgba.data())); + + unbind(); +} + +void CGLTexture::update(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const CRegion& damage) { + if (damage.empty()) + return; + + g_pHyprRenderer->makeEGLCurrent(); + + const auto format = NFormatUtils::getPixelFormatFromDRM(drmFormat); + ASSERT(format); + + bind(); + + if (format->swizzle.has_value()) + swizzle(format->swizzle.value()); + + bool alignmentChanged = false; + if (format->bytesPerBlock != 4) { + const GLint alignment = (stride % 4 == 0) ? 4 : 1; + GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, alignment)); + alignmentChanged = true; + } + + GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / format->bytesPerBlock)); + + damage.copy().intersect(CBox{{}, m_size}).forEachRect([&format, &pixels](const auto& rect) { + GLCALL(glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, rect.x1)); + GLCALL(glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, rect.y1)); + + int width = rect.x2 - rect.x1; + int height = rect.y2 - rect.y1; + GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x1, rect.y1, width, height, format->glFormat, format->glType, pixels)); + }); + + if (alignmentChanged) + GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 4)); + + GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0)); + GLCALL(glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0)); + GLCALL(glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0)); + + unbind(); + + if (m_keepDataCopy) { + m_dataCopy.resize(stride * m_size.y); + memcpy(m_dataCopy.data(), pixels, stride * m_size.y); + } +} + +void CGLTexture::allocate(const Vector2D& size, uint32_t drmFormat) { + if (!m_texID) + GLCALL(glGenTextures(1, &m_texID)); + m_size = size; + m_drmFormat = drmFormat; +} + +void CGLTexture::bind() { + GLCALL(glBindTexture(m_target, m_texID)); +} + +void CGLTexture::unbind() { + GLCALL(glBindTexture(m_target, 0)); +} + +bool CGLTexture::ok() { + return m_texID > 0; +} + +bool CGLTexture::isDMA() { + return m_eglImage; +} + +constexpr std::optional CGLTexture::getCacheStateIndex(GLenum pname) { + switch (pname) { + case GL_TEXTURE_WRAP_S: return TEXTURE_PAR_WRAP_S; + case GL_TEXTURE_WRAP_T: return TEXTURE_PAR_WRAP_T; + case GL_TEXTURE_MAG_FILTER: return TEXTURE_PAR_MAG_FILTER; + case GL_TEXTURE_MIN_FILTER: return TEXTURE_PAR_MIN_FILTER; + case GL_TEXTURE_SWIZZLE_R: return TEXTURE_PAR_SWIZZLE_R; + case GL_TEXTURE_SWIZZLE_B: return TEXTURE_PAR_SWIZZLE_B; + default: return std::nullopt; + } +} + +void CGLTexture::setTexParameter(GLenum pname, GLint param) { + const auto cacheIndex = getCacheStateIndex(pname); + + if (!cacheIndex) { + GLCALL(glTexParameteri(m_target, pname, param)); + return; + } + + const auto idx = cacheIndex.value(); + + if (m_cachedStates[idx] == param) + return; + + m_cachedStates[idx] = param; + GLCALL(glTexParameteri(m_target, pname, param)); +} + +void CGLTexture::swizzle(const std::array& colors) { + setTexParameter(GL_TEXTURE_SWIZZLE_R, colors.at(0)); + setTexParameter(GL_TEXTURE_SWIZZLE_G, colors.at(1)); + setTexParameter(GL_TEXTURE_SWIZZLE_B, colors.at(2)); + setTexParameter(GL_TEXTURE_SWIZZLE_A, colors.at(3)); +} diff --git a/src/render/gl/GLTexture.hpp b/src/render/gl/GLTexture.hpp new file mode 100644 index 00000000..34510e90 --- /dev/null +++ b/src/render/gl/GLTexture.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include "../Texture.hpp" +#include +#include + +class CGLTexture : public ITexture { + public: + using ITexture::ITexture; + + CGLTexture(CGLTexture&) = delete; + CGLTexture(CGLTexture&&) = delete; + CGLTexture(const CGLTexture&&) = delete; + CGLTexture(const CGLTexture&) = delete; + + CGLTexture(bool opaque = false); + CGLTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy = false, bool opaque = false); + CGLTexture(const Aquamarine::SDMABUFAttrs&, void* image, bool opaque = false); + CGLTexture(std::span lut3D, size_t N); + ~CGLTexture(); + + void allocate(const Vector2D& size, uint32_t drmFormat = 0) override; + void update(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const CRegion& damage) override; + void bind() override; + void unbind() override; + void setTexParameter(GLenum pname, GLint param) override; + bool ok() override; + bool isDMA() override; + + private: + void* m_eglImage = nullptr; + + enum eTextureParam : uint8_t { + TEXTURE_PAR_WRAP_S = 0, + TEXTURE_PAR_WRAP_T, + TEXTURE_PAR_MAG_FILTER, + TEXTURE_PAR_MIN_FILTER, + TEXTURE_PAR_SWIZZLE_R, + TEXTURE_PAR_SWIZZLE_B, + TEXTURE_PAR_LAST, + }; + + GLenum m_target = GL_TEXTURE_2D; + + void swizzle(const std::array& colors); + constexpr std::optional getCacheStateIndex(GLenum pname); + + std::array, TEXTURE_PAR_LAST> m_cachedStates; +}; diff --git a/src/render/pass/FramebufferElement.cpp b/src/render/pass/FramebufferElement.cpp index 7cfa8b4b..bc7c686a 100644 --- a/src/render/pass/FramebufferElement.cpp +++ b/src/render/pass/FramebufferElement.cpp @@ -6,7 +6,7 @@ CFramebufferElement::CFramebufferElement(const CFramebufferElement::SFramebuffer } void CFramebufferElement::draw(const CRegion& damage) { - CFramebuffer* fb = nullptr; + SP fb = nullptr; if (m_data.main) { switch (m_data.framebufferID) { @@ -16,22 +16,22 @@ void CFramebufferElement::draw(const CRegion& damage) { } if (!fb) { - Debug::log(ERR, "BUG THIS: CFramebufferElement::draw: main but null"); + 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_pHyprOpenGL->m_renderData.pCurrentMonData->offloadFB; break; - case FB_MONITOR_RENDER_EXTRA_MIRROR: fb = &g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorFB; break; - case FB_MONITOR_RENDER_EXTRA_MIRROR_SWAP: fb = &g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorSwapFB; break; - case FB_MONITOR_RENDER_EXTRA_OFF_MAIN: fb = &g_pHyprOpenGL->m_renderData.pCurrentMonData->offMainFB; break; - case FB_MONITOR_RENDER_EXTRA_MONITOR_MIRROR: fb = &g_pHyprOpenGL->m_renderData.pCurrentMonData->monitorMirrorFB; break; - case FB_MONITOR_RENDER_EXTRA_BLUR: fb = &g_pHyprOpenGL->m_renderData.pCurrentMonData->blurFB; break; + case FB_MONITOR_RENDER_EXTRA_OFFLOAD: fb = g_pHyprOpenGL->m_renderData.pCurrentMonData->offloadFB; break; + case FB_MONITOR_RENDER_EXTRA_MIRROR: fb = g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorFB; break; + case FB_MONITOR_RENDER_EXTRA_MIRROR_SWAP: fb = g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorSwapFB; break; + case FB_MONITOR_RENDER_EXTRA_OFF_MAIN: fb = g_pHyprOpenGL->m_renderData.pCurrentMonData->offMainFB; break; + case FB_MONITOR_RENDER_EXTRA_MONITOR_MIRROR: fb = g_pHyprOpenGL->m_renderData.pCurrentMonData->monitorMirrorFB; break; + case FB_MONITOR_RENDER_EXTRA_BLUR: fb = g_pHyprOpenGL->m_renderData.pCurrentMonData->blurFB; break; } if (!fb) { - Debug::log(ERR, "BUG THIS: CFramebufferElement::draw: not main but null"); + Log::logger->log(Log::ERR, "BUG THIS: CFramebufferElement::draw: not main but null"); return; } } diff --git a/src/render/pass/Pass.cpp b/src/render/pass/Pass.cpp index 3e14d787..a4436516 100644 --- a/src/render/pass/Pass.cpp +++ b/src/render/pass/Pass.cpp @@ -2,12 +2,13 @@ #include "../OpenGL.hpp" #include #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 "../../Compositor.hpp" +#include "../../desktop/state/FocusState.hpp" #include "../../protocols/core/Compositor.hpp" bool CRenderPass::empty() const { @@ -54,7 +55,16 @@ void CRenderPass::simplify() { auto opaque = el->element->opaqueRegion(); if (!opaque.empty()) { - opaque.scale(g_pHyprOpenGL->m_renderData.pMonitor->m_scale); + // scale and rounding is very particular so we have to use CBoxes scale and round functions + if (opaque.getRects().size() == 1) + opaque = opaque.getExtents().scale(g_pHyprOpenGL->m_renderData.pMonitor->m_scale).round(); + else { + CRegion scaledRegion; + opaque.forEachRect([&scaledRegion](const auto& RECT) { + scaledRegion.add(CBox(RECT.x1, RECT.y1, RECT.x2 - RECT.x1, RECT.y2 - RECT.y1).scale(g_pHyprOpenGL->m_renderData.pMonitor->m_scale).round()); + }); + opaque = scaledRegion; + } // if this intersects the liveBlur region, allow live blur to operate correctly. // do not occlude a border near it. @@ -161,7 +171,7 @@ CRegion CRenderPass::render(const CRegion& damage_) { } else g_pHyprOpenGL->m_renderData.finalDamage = m_damage; - if (std::ranges::any_of(m_passElements, [](const auto& el) { return el->element->disableSimplification(); })) { + if (g_pHyprOpenGL->m_renderData.noSimplify || std::ranges::any_of(m_passElements, [](const auto& el) { return el->element->disableSimplification(); })) { for (auto& el : m_passElements) { el->elementDamage = m_damage; } @@ -206,11 +216,11 @@ void CRenderPass::renderDebugData() { std::unordered_map offsets; // render focus stuff - auto renderHLSurface = [&offsets](SP texture, SP surface, const CHyprColor& color) { + auto renderHLSurface = [&offsets](SP texture, SP surface, const CHyprColor& color) { if (!surface || !texture) return; - auto hlSurface = CWLSurface::fromResource(surface); + auto hlSurface = Desktop::View::CWLSurface::fromResource(surface); if (!hlSurface) return; @@ -242,13 +252,13 @@ 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 (g_pCompositor->m_lastWindow) - renderHLSurface(m_debugData.lastWindowText, g_pCompositor->m_lastWindow->m_wlSurface->resource(), Colors::LIGHT_BLUE.modifyA(0.1F)); + if (Desktop::focusState()->window()) + 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) { diff --git a/src/render/pass/Pass.hpp b/src/render/pass/Pass.hpp index 435b5301..b45af88b 100644 --- a/src/render/pass/Pass.hpp +++ b/src/render/pass/Pass.hpp @@ -4,7 +4,7 @@ #include "PassElement.hpp" class CGradientValueData; -class CTexture; +class ITexture; class CRenderPass { public: @@ -36,7 +36,7 @@ class CRenderPass { struct { bool present = false; - SP keyboardFocusText, pointerFocusText, lastWindowText; + SP keyboardFocusText, pointerFocusText, lastWindowText; } m_debugData; friend class CHyprOpenGLImpl; diff --git a/src/render/pass/SurfacePassElement.cpp b/src/render/pass/SurfacePassElement.cpp index 36b9e5f9..c5feb8f7 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 @@ -51,10 +52,10 @@ void CSurfacePassElement::draw(const CRegion& damage) { if (!TEXTURE->m_texID) return; - const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_pInputManager->m_currentlyDraggedWindow && g_pInputManager->m_dragMode == MBIND_RESIZE; + const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_layoutManager->dragController()->target() && g_layoutManager->dragController()->mode() == MBIND_RESIZE; TRACY_GPU_ZONE("RenderSurface"); - auto PSURFACE = 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 : 1.F); const float OVERALL_ALPHA = PSURFACE ? PSURFACE->m_overallOpacity : 1.F; @@ -102,8 +103,8 @@ void CSurfacePassElement::draw(const CRegion& damage) { 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; + 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_pHyprOpenGL->blend(false); @@ -163,15 +164,15 @@ void CSurfacePassElement::draw(const CRegion& damage) { CBox CSurfacePassElement::getTexBox() { const double outputX = -m_data.pMonitor->m_position.x, outputY = -m_data.pMonitor->m_position.y; - const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_pInputManager->m_currentlyDraggedWindow && g_pInputManager->m_dragMode == MBIND_RESIZE; - 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 */) { @@ -216,7 +217,7 @@ CBox CSurfacePassElement::getTexBox() { } 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); @@ -233,7 +234,7 @@ bool CSurfacePassElement::needsLiveBlur() { } 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); @@ -254,7 +255,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 +273,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 {}; @@ -315,7 +316,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 f4dbb45a..058744de 100644 --- a/src/render/pass/SurfacePassElement.hpp +++ b/src/render/pass/SurfacePassElement.hpp @@ -4,7 +4,7 @@ #include "../../helpers/time/Time.hpp" class CWLSurfaceResource; -class CTexture; +class ITexture; class CSyncTimeline; class CSurfacePassElement : public IPassElement { @@ -16,7 +16,7 @@ class CSurfacePassElement : public IPassElement { void* data = nullptr; SP surface = nullptr; - SP texture = nullptr; + SP texture = nullptr; bool mainSurface = true; double w = 0, h = 0; int rounding = 0; diff --git a/src/render/pass/TexPassElement.hpp b/src/render/pass/TexPassElement.hpp index a922843d..770e8b05 100644 --- a/src/render/pass/TexPassElement.hpp +++ b/src/render/pass/TexPassElement.hpp @@ -3,13 +3,13 @@ #include class CWLSurfaceResource; -class CTexture; +class ITexture; class CSyncTimeline; class CTexPassElement : public IPassElement { public: struct SRenderData { - SP tex; + SP tex; CBox box; float a = 1.F; float blurA = 1.F; diff --git a/src/render/pass/TextureMatteElement.cpp b/src/render/pass/TextureMatteElement.cpp index aeeeabc6..8023df8b 100644 --- a/src/render/pass/TextureMatteElement.cpp +++ b/src/render/pass/TextureMatteElement.cpp @@ -9,11 +9,11 @@ void CTextureMatteElement::draw(const CRegion& damage) { if (m_data.disableTransformAndModify) { g_pHyprOpenGL->pushMonitorTransformEnabled(true); g_pHyprOpenGL->setRenderModifEnabled(false); - g_pHyprOpenGL->renderTextureMatte(m_data.tex, m_data.box, *m_data.fb); + g_pHyprOpenGL->renderTextureMatte(m_data.tex, m_data.box, m_data.fb); g_pHyprOpenGL->setRenderModifEnabled(true); g_pHyprOpenGL->popMonitorTransformEnabled(); } else - g_pHyprOpenGL->renderTextureMatte(m_data.tex, m_data.box, *m_data.fb); + g_pHyprOpenGL->renderTextureMatte(m_data.tex, m_data.box, m_data.fb); } bool CTextureMatteElement::needsLiveBlur() { diff --git a/src/render/pass/TextureMatteElement.hpp b/src/render/pass/TextureMatteElement.hpp index 57d0e1e3..273c6474 100644 --- a/src/render/pass/TextureMatteElement.hpp +++ b/src/render/pass/TextureMatteElement.hpp @@ -2,14 +2,14 @@ #include "PassElement.hpp" #include "../Framebuffer.hpp" -class CTexture; +class ITexture; class CTextureMatteElement : public IPassElement { public: struct STextureMatteData { CBox box; - SP tex; - SP fb; + SP tex; + SP fb; bool disableTransformAndModify = false; }; diff --git a/src/render/shaders/glsl/CM.frag b/src/render/shaders/glsl/CM.frag deleted file mode 100644 index 031fe7f3..00000000 --- a/src/render/shaders/glsl/CM.frag +++ /dev/null @@ -1,54 +0,0 @@ -#version 300 es -//#extension GL_OES_EGL_image_external : require -#extension GL_ARB_shading_language_include : enable - -precision highp float; -in vec2 v_texcoord; -uniform sampler2D tex; -//uniform samplerExternalOES texture0; - -uniform int texType; // eTextureType: 0 - rgba, 1 - rgbx, 2 - ext -// uniform int skipCM; -uniform int sourceTF; // eTransferFunction -uniform int targetTF; // eTransferFunction -uniform mat4x2 targetPrimaries; - -uniform float alpha; - -uniform int discardOpaque; -uniform int discardAlpha; -uniform float discardAlphaValue; - -uniform int applyTint; -uniform vec3 tint; - -#include "rounding.glsl" -#include "CM.glsl" - -layout(location = 0) out vec4 fragColor; -void main() { - vec4 pixColor; - if (texType == 1) - pixColor = vec4(texture(tex, v_texcoord).rgb, 1.0); -// else if (texType == 2) -// pixColor = texture(texture0, v_texcoord); - else // assume rgba - pixColor = texture(tex, v_texcoord); - - if (discardOpaque == 1 && pixColor[3] * alpha == 1.0) - discard; - - if (discardAlpha == 1 && pixColor[3] <= discardAlphaValue) - discard; - - // this shader shouldn't be used when skipCM == 1 - pixColor = doColorManagement(pixColor, sourceTF, targetTF, targetPrimaries); - - if (applyTint == 1) - pixColor = vec4(pixColor.rgb * tint.rgb, pixColor[3]); - - if (radius > 0.0) - pixColor = rounding(pixColor); - - fragColor = pixColor * alpha; -} diff --git a/src/render/shaders/glsl/CM.glsl b/src/render/shaders/glsl/CM.glsl index 0e79aab0..323a3008 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 796fb42d..044df3cc 100644 --- a/src/render/shaders/glsl/blur1.frag +++ b/src/render/shaders/glsl/blur1.frag @@ -1,143 +1,21 @@ #version 300 es -precision highp float; -uniform sampler2D tex; +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable -uniform float radius; -uniform vec2 halfpixel; -uniform int passes; -uniform float vibrancy; -uniform float vibrancy_darkness; +precision highp float; +uniform sampler2D tex; -in vec2 v_texcoord; - -// see http://alienryderflex.com/hsp.html -const float Pr = 0.299; -const float Pg = 0.587; -const float Pb = 0.114; - -// Y is "v" ( brightness ). X is "s" ( saturation ) -// see https://www.desmos.com/3d/a88652b9a4 -// Determines if high brightness or high saturation is more important -const float a = 0.93; -const float b = 0.11; -const float c = 0.66; // Determines the smoothness of the transition of unboosted to boosted colors -// - -// http://www.flong.com/archive/texts/code/shapers_circ/ -float doubleCircleSigmoid(float x, float a) { - a = clamp(a, 0.0, 1.0); - - float y = .0; - if (x <= a) { - y = a - sqrt(a * a - x * x); - } else { - y = a + sqrt(pow(1. - a, 2.) - pow(x - 1., 2.)); - } - return y; -} - -vec3 rgb2hsl(vec3 col) { - float red = col.r; - float green = col.g; - float blue = col.b; - - float minc = min(col.r, min(col.g, col.b)); - float maxc = max(col.r, max(col.g, col.b)); - float delta = maxc - minc; - - float lum = (minc + maxc) * 0.5; - float sat = 0.0; - float hue = 0.0; - - if (lum > 0.0 && lum < 1.0) { - float mul = (lum < 0.5) ? (lum) : (1.0 - lum); - sat = delta / (mul * 2.0); - } - - if (delta > 0.0) { - vec3 maxcVec = vec3(maxc); - vec3 masks = vec3(equal(maxcVec, col)) * vec3(notEqual(maxcVec, vec3(green, blue, red))); - vec3 adds = vec3(0.0, 2.0, 4.0) + vec3(green - blue, blue - red, red - green) / delta; - - hue += dot(adds, masks); - hue /= 6.0; - - if (hue < 0.0) - hue += 1.0; - } - - return vec3(hue, sat, lum); -} - -vec3 hsl2rgb(vec3 col) { - const float onethird = 1.0 / 3.0; - const float twothird = 2.0 / 3.0; - const float rcpsixth = 6.0; - - float hue = col.x; - float sat = col.y; - float lum = col.z; - - vec3 xt = vec3(0.0); - - if (hue < onethird) { - xt.r = rcpsixth * (onethird - hue); - xt.g = rcpsixth * hue; - xt.b = 0.0; - } else if (hue < twothird) { - xt.r = 0.0; - xt.g = rcpsixth * (twothird - hue); - xt.b = rcpsixth * (hue - onethird); - } else - xt = vec3(rcpsixth * (hue - twothird), 0.0, rcpsixth * (1.0 - hue)); - - xt = min(xt, 1.0); - - float sat2 = 2.0 * sat; - float satinv = 1.0 - sat; - float luminv = 1.0 - lum; - float lum2m1 = (2.0 * lum) - 1.0; - vec3 ct = (sat2 * xt) + satinv; - - vec3 rgb; - if (lum >= 0.5) - rgb = (luminv * ct) + lum2m1; - else - rgb = lum * ct; - - return rgb; -} +uniform float radius; +uniform vec2 halfpixel; +uniform int passes; +uniform float vibrancy; +uniform float vibrancy_darkness; +in vec2 v_texcoord; layout(location = 0) out vec4 fragColor; + +#include "blur1.glsl" + void main() { - vec2 uv = v_texcoord * 2.0; - - vec4 sum = texture(tex, uv) * 4.0; - sum += texture(tex, uv - halfpixel.xy * radius); - sum += texture(tex, uv + halfpixel.xy * radius); - sum += texture(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius); - sum += texture(tex, uv - vec2(halfpixel.x, -halfpixel.y) * radius); - - vec4 color = sum / 8.0; - - if (vibrancy == 0.0) { - fragColor = color; - } else { - // Invert it so that it correctly maps to the config setting - float vibrancy_darkness1 = 1.0 - vibrancy_darkness; - - // Decrease the RGB components based on their perceived brightness, to prevent visually dark colors from overblowing the rest. - vec3 hsl = rgb2hsl(color.rgb); - // Calculate perceived brightness, as not boost visually dark colors like deep blue as much as equally saturated yellow - float perceivedBrightness = doubleCircleSigmoid(sqrt(color.r * color.r * Pr + color.g * color.g * Pg + color.b * color.b * Pb), 0.8 * vibrancy_darkness1); - - float b1 = b * vibrancy_darkness1; - float boostBase = hsl[1] > 0.0 ? smoothstep(b1 - c * 0.5, b1 + c * 0.5, 1.0 - (pow(1.0 - hsl[1] * cos(a), 2.0) + pow(1.0 - perceivedBrightness * sin(a), 2.0))) : 0.0; - - float saturation = clamp(hsl[1] + (boostBase * vibrancy) / float(passes), 0.0, 1.0); - - vec3 newColor = hsl2rgb(vec3(hsl[0], saturation, hsl[2])); - - fragColor = vec4(newColor, color[3]); - } + fragColor = blur1(v_texcoord, tex, radius, halfpixel, passes, vibrancy, vibrancy_darkness); } diff --git a/src/render/shaders/glsl/blur1.glsl b/src/render/shaders/glsl/blur1.glsl new file mode 100644 index 00000000..36e7d660 --- /dev/null +++ b/src/render/shaders/glsl/blur1.glsl @@ -0,0 +1,130 @@ +// see http://alienryderflex.com/hsp.html +const float Pr = 0.299; +const float Pg = 0.587; +const float Pb = 0.114; + +// Y is "v" ( brightness ). X is "s" ( saturation ) +// see https://www.desmos.com/3d/a88652b9a4 +// Determines if high brightness or high saturation is more important +const float a = 0.93; +const float b = 0.11; +const float c = 0.66; // Determines the smoothness of the transition of unboosted to boosted colors +// + +// http://www.flong.com/archive/texts/code/shapers_circ/ +float doubleCircleSigmoid(float x, float a) { + a = clamp(a, 0.0, 1.0); + + float y = .0; + if (x <= a) { + y = a - sqrt(a * a - x * x); + } else { + y = a + sqrt(pow(1. - a, 2.) - pow(x - 1., 2.)); + } + return y; +} + +vec3 rgb2hsl(vec3 col) { + float red = col.r; + float green = col.g; + float blue = col.b; + + float minc = min(col.r, min(col.g, col.b)); + float maxc = max(col.r, max(col.g, col.b)); + float delta = maxc - minc; + + float lum = (minc + maxc) * 0.5; + float sat = 0.0; + float hue = 0.0; + + if (lum > 0.0 && lum < 1.0) { + float mul = (lum < 0.5) ? (lum) : (1.0 - lum); + sat = delta / (mul * 2.0); + } + + if (delta > 0.0) { + vec3 maxcVec = vec3(maxc); + vec3 masks = vec3(equal(maxcVec, col)) * vec3(notEqual(maxcVec, vec3(green, blue, red))); + vec3 adds = vec3(0.0, 2.0, 4.0) + vec3(green - blue, blue - red, red - green) / delta; + + hue += dot(adds, masks); + hue /= 6.0; + + if (hue < 0.0) + hue += 1.0; + } + + return vec3(hue, sat, lum); +} + +vec3 hsl2rgb(vec3 col) { + const float onethird = 1.0 / 3.0; + const float twothird = 2.0 / 3.0; + const float rcpsixth = 6.0; + + float hue = col.x; + float sat = col.y; + float lum = col.z; + + vec3 xt = vec3(0.0); + + if (hue < onethird) { + xt.r = rcpsixth * (onethird - hue); + xt.g = rcpsixth * hue; + xt.b = 0.0; + } else if (hue < twothird) { + xt.r = 0.0; + xt.g = rcpsixth * (twothird - hue); + xt.b = rcpsixth * (hue - onethird); + } else + xt = vec3(rcpsixth * (hue - twothird), 0.0, rcpsixth * (1.0 - hue)); + + xt = min(xt, 1.0); + + float sat2 = 2.0 * sat; + float satinv = 1.0 - sat; + float luminv = 1.0 - lum; + float lum2m1 = (2.0 * lum) - 1.0; + vec3 ct = (sat2 * xt) + satinv; + + vec3 rgb; + if (lum >= 0.5) + rgb = (luminv * ct) + lum2m1; + else + rgb = lum * ct; + + return rgb; +} + +vec4 blur1(vec2 v_texcoord, sampler2D tex, float radius, vec2 halfpixel, int passes, float vibrancy, float vibrancy_darkness) { + vec2 uv = v_texcoord * 2.0; + + vec4 sum = texture(tex, uv) * 4.0; + sum += texture(tex, uv - halfpixel.xy * radius); + sum += texture(tex, uv + halfpixel.xy * radius); + sum += texture(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius); + sum += texture(tex, uv - vec2(halfpixel.x, -halfpixel.y) * radius); + + vec4 color = sum / 8.0; + + if (vibrancy == 0.0) { + return color; + } else { + // Invert it so that it correctly maps to the config setting + float vibrancy_darkness1 = 1.0 - vibrancy_darkness; + + // Decrease the RGB components based on their perceived brightness, to prevent visually dark colors from overblowing the rest. + vec3 hsl = rgb2hsl(color.rgb); + // Calculate perceived brightness, as not boost visually dark colors like deep blue as much as equally saturated yellow + float perceivedBrightness = doubleCircleSigmoid(sqrt(color.r * color.r * Pr + color.g * color.g * Pg + color.b * color.b * Pb), 0.8 * vibrancy_darkness1); + + float b1 = b * vibrancy_darkness1; + float boostBase = hsl[1] > 0.0 ? smoothstep(b1 - c * 0.5, b1 + c * 0.5, 1.0 - (pow(1.0 - hsl[1] * cos(a), 2.0) + pow(1.0 - perceivedBrightness * sin(a), 2.0))) : 0.0; + + float saturation = clamp(hsl[1] + (boostBase * vibrancy) / float(passes), 0.0, 1.0); + + vec3 newColor = hsl2rgb(vec3(hsl[0], saturation, hsl[2])); + + return vec4(newColor, color[3]); + } +} diff --git a/src/render/shaders/glsl/blur2.frag b/src/render/shaders/glsl/blur2.frag index bfe448d5..62caae56 100644 --- a/src/render/shaders/glsl/blur2.frag +++ b/src/render/shaders/glsl/blur2.frag @@ -1,25 +1,18 @@ #version 300 es -precision highp float; +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable + +precision highp float; uniform sampler2D tex; -uniform float radius; -uniform vec2 halfpixel; +uniform float radius; +uniform vec2 halfpixel; -in vec2 v_texcoord; +in vec2 v_texcoord; layout(location = 0) out vec4 fragColor; +#include "blur2.glsl" + void main() { - vec2 uv = v_texcoord / 2.0; - - vec4 sum = texture(tex, uv + vec2(-halfpixel.x * 2.0, 0.0) * radius); - - sum += texture(tex, uv + vec2(-halfpixel.x, halfpixel.y) * radius) * 2.0; - sum += texture(tex, uv + vec2(0.0, halfpixel.y * 2.0) * radius); - sum += texture(tex, uv + vec2(halfpixel.x, halfpixel.y) * radius) * 2.0; - sum += texture(tex, uv + vec2(halfpixel.x * 2.0, 0.0) * radius); - sum += texture(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius) * 2.0; - sum += texture(tex, uv + vec2(0.0, -halfpixel.y * 2.0) * radius); - sum += texture(tex, uv + vec2(-halfpixel.x, -halfpixel.y) * radius) * 2.0; - - fragColor = sum / 12.0; + fragColor = blur2(v_texcoord, tex, radius, halfpixel); } diff --git a/src/render/shaders/glsl/blur2.glsl b/src/render/shaders/glsl/blur2.glsl new file mode 100644 index 00000000..e73e90e3 --- /dev/null +++ b/src/render/shaders/glsl/blur2.glsl @@ -0,0 +1,15 @@ +vec4 blur2(vec2 v_texcoord, sampler2D tex, float radius, vec2 halfpixel) { + vec2 uv = v_texcoord / 2.0; + + vec4 sum = texture(tex, uv + vec2(-halfpixel.x * 2.0, 0.0) * radius); + + sum += texture(tex, uv + vec2(-halfpixel.x, halfpixel.y) * radius) * 2.0; + sum += texture(tex, uv + vec2(0.0, halfpixel.y * 2.0) * radius); + sum += texture(tex, uv + vec2(halfpixel.x, halfpixel.y) * radius) * 2.0; + sum += texture(tex, uv + vec2(halfpixel.x * 2.0, 0.0) * radius); + sum += texture(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius) * 2.0; + sum += texture(tex, uv + vec2(0.0, -halfpixel.y * 2.0) * radius); + sum += texture(tex, uv + vec2(-halfpixel.x, -halfpixel.y) * radius) * 2.0; + + return sum / 12.0; +} diff --git a/src/render/shaders/glsl/blurFinish.glsl b/src/render/shaders/glsl/blurFinish.glsl new file mode 100644 index 00000000..f3d225c3 --- /dev/null +++ b/src/render/shaders/glsl/blurFinish.glsl @@ -0,0 +1,17 @@ +float hash(vec2 p) { + vec3 p3 = fract(vec3(p.xyx) * 1689.1984); + p3 += dot(p3, p3.yzx + 33.33); + return fract((p3.x + p3.y) * p3.z); +} + +vec4 blurFinish(vec4 pixColor, vec2 v_texcoord, float noise, float brightness) { + // noise + float noiseHash = hash(v_texcoord); + float noiseAmount = noiseHash - 0.5; + pixColor.rgb += noiseAmount * noise; + + // brightness + pixColor.rgb *= min(1.0, brightness); + + return pixColor; +} diff --git a/src/render/shaders/glsl/blurfinish.frag b/src/render/shaders/glsl/blurfinish.frag index 6ab48337..0342646b 100644 --- a/src/render/shaders/glsl/blurfinish.frag +++ b/src/render/shaders/glsl/blurfinish.frag @@ -1,32 +1,19 @@ #version 300 es +#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable -precision highp float; -in vec2 v_texcoord; // is in 0-1 +precision highp float; +in vec2 v_texcoord; // is in 0-1 uniform sampler2D tex; -uniform float noise; -uniform float brightness; +uniform float noise; +uniform float brightness; -float hash(vec2 p) { - vec3 p3 = fract(vec3(p.xyx) * 1689.1984); - p3 += dot(p3, p3.yzx + 33.33); - return fract((p3.x + p3.y) * p3.z); -} +#include "blurFinish.glsl" layout(location = 0) out vec4 fragColor; void main() { vec4 pixColor = texture(tex, v_texcoord); - // noise - float noiseHash = hash(v_texcoord); - float noiseAmount = (mod(noiseHash, 1.0) - 0.5); - pixColor.rgb += noiseAmount * noise; - - // brightness - if (brightness < 1.0) { - pixColor.rgb *= brightness; - } - - fragColor = pixColor; + fragColor = blurFinish(pixColor, v_texcoord, noise, brightness); } diff --git a/src/render/shaders/glsl/blurprepare.frag b/src/render/shaders/glsl/blurprepare.frag index 548289c3..e96c54bb 100644 --- a/src/render/shaders/glsl/blurprepare.frag +++ b/src/render/shaders/glsl/blurprepare.frag @@ -1,48 +1,38 @@ #version 300 es +#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable -precision highp float; -in vec2 v_texcoord; // is in 0-1 +#include "defines.h" + +precision highp float; +in vec2 v_texcoord; // is in 0-1 uniform sampler2D tex; -uniform float contrast; -uniform float brightness; +uniform float contrast; +uniform float brightness; -uniform int skipCM; -uniform int sourceTF; // eTransferFunction -uniform int targetTF; // eTransferFunction +uniform int sourceTF; // eTransferFunction +uniform int targetTF; // eTransferFunction -#include "CM.glsl" +#if USE_CM +uniform vec2 srcTFRange; +uniform vec2 dstTFRange; -float gain(float x, float k) { - float a = 0.5 * pow(2.0 * ((x < 0.5) ? x : 1.0 - x), k); - return (x < 0.5) ? a : 1.0 - a; -} +uniform float srcRefLuminance; +uniform mat3 convertMatrix; + +uniform float sdrBrightnessMultiplier; +#include "cm_helpers.glsl" +#endif + +#include "blurprepare.glsl" layout(location = 0) out vec4 fragColor; void main() { - vec4 pixColor = texture(tex, v_texcoord); - - if (skipCM == 0) { - if (sourceTF == CM_TRANSFER_FUNCTION_ST2084_PQ) { - pixColor.rgb /= sdrBrightnessMultiplier; - } - pixColor.rgb = convertMatrix * toLinearRGB(pixColor.rgb, sourceTF); - pixColor = toNit(pixColor, 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 00000000..e4a0daad --- /dev/null +++ b/src/render/shaders/glsl/blurprepare.glsl @@ -0,0 +1,37 @@ +#ifndef ALLOW_INCLUDES +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable +#endif + +#include "defines.h" + +#if USE_CM +#include "cm_helpers.glsl" +#endif + +#include "gain.glsl" + +vec4 blurPrepare(vec4 pixColor, float contrast, float brightness +#if USE_CM + , + int sourceTF, int targetTF, mat3 convertMatrix, vec2 srcTFRange, vec2 dstTFRange, float srcRefLuminance, float sdrBrightnessMultiplier +#endif +) { +#if USE_CM + if (sourceTF == CM_TRANSFER_FUNCTION_ST2084_PQ) { + pixColor.rgb /= sdrBrightnessMultiplier; + } + pixColor.rgb = convertMatrix * toLinearRGB(pixColor.rgb, sourceTF); + pixColor = toNit(pixColor, vec2(srcTFRange[0], srcRefLuminance)); + pixColor = fromLinearNit(pixColor, targetTF, dstTFRange); +#endif + + // contrast + if (contrast != 1.0) + pixColor.rgb = gain(pixColor.rgb, contrast); + + // brightness + pixColor.rgb *= max(1.0, brightness); + + return pixColor; +} diff --git a/src/render/shaders/glsl/border.frag b/src/render/shaders/glsl/border.frag index d93773fd..151593c1 100644 --- a/src/render/shaders/glsl/border.frag +++ b/src/render/shaders/glsl/border.frag @@ -1,182 +1,60 @@ #version 300 es +#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable -precision highp float; -in vec2 v_texcoord; +precision highp float; +in vec2 v_texcoord; -uniform int skipCM; -uniform int sourceTF; // eTransferFunction -uniform int targetTF; // eTransferFunction -uniform mat4x2 targetPrimaries; +uniform int sourceTF; // eTransferFunction +uniform int targetTF; // eTransferFunction +uniform mat3 targetPrimariesXYZ; -uniform vec2 fullSizeUntransformed; +uniform vec2 fullSizeUntransformed; uniform float radiusOuter; uniform float thick; // Gradients are in OkLabA!!!! {l, a, b, alpha} -uniform vec4 gradient[10]; -uniform vec4 gradient2[10]; -uniform int gradientLength; -uniform int gradient2Length; +uniform vec4 gradient[10]; +uniform vec4 gradient2[10]; +uniform int gradientLength; +uniform int gradient2Length; uniform float angle; uniform float angle2; uniform float gradientLerp; uniform float alpha; +uniform float radius; +uniform float roundingPower; +uniform vec2 topLeft; +uniform vec2 fullSize; #include "rounding.glsl" #include "CM.glsl" - -vec4 okLabAToSrgb(vec4 lab) { - float l = pow(lab[0] + lab[1] * 0.3963377774 + lab[2] * 0.2158037573, 3.0); - float m = pow(lab[0] + lab[1] * (-0.1055613458) + lab[2] * (-0.0638541728), 3.0); - float s = pow(lab[0] + lab[1] * (-0.0894841775) + lab[2] * (-1.2914855480), 3.0); - - return vec4(fromLinearRGB( - vec3( - l * 4.0767416621 + m * -3.3077115913 + s * 0.2309699292, - l * (-1.2684380046) + m * 2.6097574011 + s * (-0.3413193965), - l * (-0.0041960863) + m * (-0.7034186147) + s * 1.7076147010 - ), CM_TRANSFER_FUNCTION_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; void main() { - highp vec2 pixCoord = vec2(gl_FragCoord); - highp vec2 pixCoordOuter = pixCoord; - highp vec2 originalPixCoord = v_texcoord; - originalPixCoord *= fullSizeUntransformed; - float additionalAlpha = 1.0; - - vec4 pixColor = vec4(1.0, 1.0, 1.0, 1.0); - - bool done = false; - - pixCoord -= topLeft + fullSize * 0.5; - pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0; - pixCoordOuter = pixCoord; - pixCoord -= fullSize * 0.5 - radius; - pixCoordOuter -= fullSize * 0.5 - radiusOuter; - - // center the pixes don't make it top-left - pixCoord += vec2(1.0, 1.0) / fullSize; - pixCoordOuter += vec2(1.0, 1.0) / fullSize; - - if (min(pixCoord.x, pixCoord.y) > 0.0 && radius > 0.0) { - float dist = pow(pow(pixCoord.x,roundingPower)+pow(pixCoord.y,roundingPower),1.0/roundingPower); - float distOuter = pow(pow(pixCoordOuter.x,roundingPower)+pow(pixCoordOuter.y,roundingPower),1.0/roundingPower); - float h = (thick / 2.0); - - if (dist < radius - h) { - // lower - float normalized = smoothstep(0.0, 1.0, (dist - radius + thick + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); - additionalAlpha *= normalized; - done = true; - } else if (min(pixCoordOuter.x, pixCoordOuter.y) > 0.0) { - // higher - float normalized = 1.0 - smoothstep(0.0, 1.0, (distOuter - radiusOuter + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); - additionalAlpha *= normalized; - done = true; - } else if (distOuter < radiusOuter - h) { - additionalAlpha = 1.0; - done = true; - } - } - - // now check for other shit - if (!done) { - // distance to all straight bb borders - float distanceT = originalPixCoord[1]; - float distanceB = fullSizeUntransformed[1] - originalPixCoord[1]; - float distanceL = originalPixCoord[0]; - float distanceR = fullSizeUntransformed[0] - originalPixCoord[0]; - - // get the smallest - float smallest = min(min(distanceT, distanceB), min(distanceL, distanceR)); - - if (smallest > thick) - discard; - } - - if (additionalAlpha == 0.0) - discard; - - pixColor = getColorForCoord(v_texcoord); - pixColor.rgb *= pixColor[3]; - - if (skipCM == 0) - pixColor = doColorManagement(pixColor, sourceTF, targetTF, targetPrimaries); - - pixColor *= alpha * additionalAlpha; - - fragColor = pixColor; + fragColor = getBorder(v_texcoord, alpha, fullSizeUntransformed, radiusOuter, thick, radius, roundingPower, topLeft, fullSize, gradientLength, gradient, angle, gradient2Length, + gradient2, angle2, gradientLerp +#if USE_CM + , + sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange +#if USE_ICC + , + iccLut3D, iccLutSize +#else +#if USE_TONEMAP || USE_SDR_MOD + , + targetPrimariesXYZ +#endif +#if USE_TONEMAP + , + maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance +#endif +#if USE_SDR_MOD + , + sdrSaturation, sdrBrightnessMultiplier +#endif +#endif +#endif + ); } diff --git a/src/render/shaders/glsl/border.glsl b/src/render/shaders/glsl/border.glsl new file mode 100644 index 00000000..fa2a6980 --- /dev/null +++ b/src/render/shaders/glsl/border.glsl @@ -0,0 +1,203 @@ +#ifndef ALLOW_INCLUDES +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable +#endif +#include "cm_helpers.glsl" +#if USE_ROUNDING +#include "rounding.glsl" +#endif + +vec4 okLabAToSrgb(vec4 lab) { + float l = pow(lab[0] + lab[1] * 0.3963377774 + lab[2] * 0.2158037573, 3.0); + float m = pow(lab[0] + lab[1] * (-0.1055613458) + lab[2] * (-0.0638541728), 3.0); + float s = pow(lab[0] + lab[1] * (-0.0894841775) + lab[2] * (-1.2914855480), 3.0); + + return vec4(fromLinearRGB(vec3(l * 4.0767416621 + m * -3.3077115913 + s * 0.2309699292, l * (-1.2684380046) + m * 2.6097574011 + s * (-0.3413193965), + l * (-0.0041960863) + m * (-0.7034186147) + s * 1.7076147010), + CM_TRANSFER_FUNCTION_GAMMA22), + lab[3]); +} + +vec4 getOkColorForCoordArray1(vec2 normalizedCoord, int gradientLength, vec4 gradient[10], float angle) { + if (gradientLength < 2) + return gradient[0]; + + float finalAng = 0.0; + + if (angle > 4.71 /* 270 deg */) { + normalizedCoord[1] = 1.0 - normalizedCoord[1]; + finalAng = 6.28 - angle; + } else if (angle > 3.14 /* 180 deg */) { + normalizedCoord[0] = 1.0 - normalizedCoord[0]; + normalizedCoord[1] = 1.0 - normalizedCoord[1]; + finalAng = angle - 3.14; + } else if (angle > 1.57 /* 90 deg */) { + normalizedCoord[0] = 1.0 - normalizedCoord[0]; + finalAng = 3.14 - angle; + } else { + finalAng = angle; + } + + float sine = sin(finalAng); + + float progress = (normalizedCoord[1] * sine + normalizedCoord[0] * (1.0 - sine)) * float(gradientLength - 1); + int bottom = int(floor(progress)); + int top = bottom + 1; + + return gradient[top] * (progress - float(bottom)) + gradient[bottom] * (float(top) - progress); +} + +vec4 getOkColorForCoordArray2(vec2 normalizedCoord, float angle, int gradient2Length, vec4 gradient2[10], float angle2) { + if (gradient2Length < 2) + return gradient2[0]; + + float finalAng = 0.0; + + if (angle2 > 4.71 /* 270 deg */) { + normalizedCoord[1] = 1.0 - normalizedCoord[1]; + finalAng = 6.28 - angle; + } else if (angle2 > 3.14 /* 180 deg */) { + normalizedCoord[0] = 1.0 - normalizedCoord[0]; + normalizedCoord[1] = 1.0 - normalizedCoord[1]; + finalAng = angle - 3.14; + } else if (angle2 > 1.57 /* 90 deg */) { + normalizedCoord[0] = 1.0 - normalizedCoord[0]; + finalAng = 3.14 - angle2; + } else { + finalAng = angle2; + } + + float sine = sin(finalAng); + + float progress = (normalizedCoord[1] * sine + normalizedCoord[0] * (1.0 - sine)) * float(gradient2Length - 1); + int bottom = int(floor(progress)); + int top = bottom + 1; + + return gradient2[top] * (progress - float(bottom)) + gradient2[bottom] * (float(top) - progress); +} + +vec4 getColorForCoord(vec2 normalizedCoord, int gradientLength, vec4 gradient[10], float angle, int gradient2Length, vec4 gradient2[10], float angle2, float gradientLerp) { + vec4 result1 = getOkColorForCoordArray1(normalizedCoord, gradientLength, gradient, angle); + + if (gradient2Length <= 0) + return okLabAToSrgb(result1); + + vec4 result2 = getOkColorForCoordArray2(normalizedCoord, angle, gradient2Length, gradient2, angle2); + + return okLabAToSrgb(mix(result1, result2, gradientLerp)); +} + +vec4 getBorder(vec2 v_texcoord, float alpha, vec2 fullSizeUntransformed, float radiusOuter, float thick, float radius, float roundingPower, vec2 topLeft, vec2 fullSize, + int gradientLength, vec4 gradient[10], float angle, int gradient2Length, vec4 gradient2[10], float angle2, float gradientLerp +#if USE_CM + , + int sourceTF, int targetTF, mat3 convertMatrix, vec2 srcTFRange, vec2 dstTFRange +#if USE_ICC + , + highp sampler3D iccLut3D, float iccLutSize +#else +#if USE_TONEMAP || USE_SDR_MOD + , + mat3 targetPrimariesXYZ +#endif +#if USE_TONEMAP + , + float maxLuminance, float dstMaxLuminance, float dstRefLuminance, float srcRefLuminance +#endif +#if USE_SDR_MOD + , + float sdrSaturation, float sdrBrightnessMultiplier +#endif +#endif +#endif +) { + vec2 pixCoord = vec2(gl_FragCoord); + vec2 pixCoordOuter = pixCoord; + vec2 originalPixCoord = v_texcoord; + originalPixCoord *= fullSizeUntransformed; + float additionalAlpha = 1.0; + + vec4 pixColor = vec4(1.0, 1.0, 1.0, 1.0); + + bool done = false; + + pixCoord -= topLeft + fullSize * 0.5; + pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0; + pixCoordOuter = pixCoord; + pixCoord -= fullSize * 0.5 - radius; + pixCoordOuter -= fullSize * 0.5 - radiusOuter; + + // center the pixes don't make it top-left + pixCoord += vec2(1.0, 1.0) / fullSize; + pixCoordOuter += vec2(1.0, 1.0) / fullSize; + +#if USE_ROUNDING + if (min(pixCoord.x, pixCoord.y) > 0.0 && radius > 0.0) { + float dist = pow(pow(pixCoord.x, roundingPower) + pow(pixCoord.y, roundingPower), 1.0 / roundingPower); + float distOuter = pow(pow(pixCoordOuter.x, roundingPower) + pow(pixCoordOuter.y, roundingPower), 1.0 / roundingPower); + float h = (thick / 2.0); + + if (dist < radius - h) { + // lower + float normalized = smoothstep(0.0, 1.0, (dist - radius + thick + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); + additionalAlpha *= normalized; + done = true; + } else if (min(pixCoordOuter.x, pixCoordOuter.y) > 0.0) { + // higher + float normalized = 1.0 - smoothstep(0.0, 1.0, (distOuter - radiusOuter + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); + additionalAlpha *= normalized; + done = true; + } else if (distOuter < radiusOuter - h) { + additionalAlpha = 1.0; + done = true; + } + } +#endif + + // now check for other shit + if (!done) { + // distance to all straight bb borders + float distanceT = originalPixCoord[1]; + float distanceB = fullSizeUntransformed[1] - originalPixCoord[1]; + float distanceL = originalPixCoord[0]; + float distanceR = fullSizeUntransformed[0] - originalPixCoord[0]; + + // get the smallest + float smallest = min(min(distanceT, distanceB), min(distanceL, distanceR)); + + if (smallest > thick) + discard; + } + + if (additionalAlpha == 0.0) + discard; + + pixColor = getColorForCoord(v_texcoord, gradientLength, gradient, angle, gradient2Length, gradient2, angle2, gradientLerp); + pixColor.rgb *= pixColor[3]; + +#if USE_CM + pixColor = doColorManagement(pixColor, sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange +#if USE_ICC + , + iccLut3D, iccLutSize +#else +#if USE_TONEMAP || USE_SDR_MOD + , + targetPrimariesXYZ +#endif +#if USE_TONEMAP + , + maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance +#endif +#if USE_SDR_MOD + , + sdrSaturation, sdrBrightnessMultiplier +#endif +#endif + ); +#endif + + pixColor *= alpha * additionalAlpha; + + return pixColor; +} diff --git a/src/render/shaders/glsl/cm_helpers.glsl b/src/render/shaders/glsl/cm_helpers.glsl new file mode 100644 index 00000000..5e0d14f6 --- /dev/null +++ b/src/render/shaders/glsl/cm_helpers.glsl @@ -0,0 +1,248 @@ +#ifndef ALLOW_INCLUDES +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable +#endif +#ifndef CM_HELPERS_GLSL +#define CM_HELPERS_GLSL + +#include "defines.h" +#include "constants.h" + +#if USE_SDR_MOD +vec4 saturate(vec4 color, mat3 primaries, float saturation) { + if (saturation == 1.0) + return color; + vec3 brightness = vec3(primaries[1][0], primaries[1][1], primaries[1][2]); + float Y = dot(color.rgb, brightness); + return vec4(mix(vec3(Y), color.rgb, saturation), color[3]); +} +#endif + +vec3 applyIcc3DLut(vec3 linearRgb01, highp sampler3D iccLut3D, float iccLutSize) { + vec3 x = clamp(linearRgb01, 0.0, 1.0); + + // Map [0..1] to texel centers to avoid edge issues + float N = iccLutSize; + vec3 coord = (x * (N - 1.0) + 0.5) / N; + + return texture(iccLut3D, coord).rgb; +} + +vec3 xy2xyz(vec2 xy) { + if (xy.y == 0.0) + return vec3(0.0, 0.0, 0.0); + + return vec3(xy.x / xy.y, 1.0, (1.0 - xy.x - xy.y) / xy.y); +} + +// The primary source for these transfer functions is https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.1361-0-199802-W!!PDF-E.pdf +vec3 tfInvPQ(vec3 color) { + vec3 E = pow(clamp(color.rgb, vec3(0.0), vec3(1.0)), vec3(PQ_INV_M2)); + return pow((max(E - PQ_C1, vec3(0.0))) / (PQ_C2 - PQ_C3 * E), vec3(PQ_INV_M1)); +} + +vec3 tfInvHLG(vec3 color) { + bvec3 isLow = lessThanEqual(color.rgb, vec3(HLG_E_CUT)); + vec3 lo = color.rgb * color.rgb / 3.0; + vec3 hi = (exp((color.rgb - HLG_C) / HLG_A) + HLG_B) / 12.0; + return mix(hi, lo, isLow); +} + +// Many transfer functions (including sRGB) follow the same pattern: a linear +// segment for small values and a power function for larger values. The +// following function implements this pattern from which sRGB, BT.1886, and +// others can be derived by plugging in the right constants. +vec3 tfInvLinPow(vec3 color, float gamma, float thres, float scale, float alpha) { + bvec3 isLow = lessThanEqual(color.rgb, vec3(thres * scale)); + vec3 lo = color.rgb / scale; + vec3 hi = pow((color.rgb + alpha - 1.0) / alpha, vec3(gamma)); + return mix(hi, lo, isLow); +} + +vec3 tfInvSRGB(vec3 color) { + return tfInvLinPow(color, SRGB_POW, SRGB_CUT, SRGB_SCALE, SRGB_ALPHA); +} + +vec3 tfInvExtSRGB(vec3 color) { + // EXT sRGB is the sRGB transfer function mirrored around 0. + return sign(color) * tfInvSRGB(abs(color)); +} + +vec3 tfInvBT1886(vec3 color) { + return tfInvLinPow(color, BT1886_POW, BT1886_CUT, BT1886_SCALE, BT1886_ALPHA); +} + +vec3 tfInvXVYCC(vec3 color) { + // The inverse transfer function for XVYCC is the BT1886 transfer function mirrored around 0, + // same as what EXT sRGB is to sRGB. + return sign(color) * tfInvBT1886(abs(color)); +} + +vec3 tfInvST240(vec3 color) { + return tfInvLinPow(color, ST240_POW, ST240_CUT, ST240_SCALE, ST240_ALPHA); +} + +// Forward transfer functions corresponding to the inverse functions above. +vec3 tfPQ(vec3 color) { + vec3 E = pow(clamp(color.rgb, vec3(0.0), vec3(1.0)), vec3(PQ_M1)); + return pow((vec3(PQ_C1) + PQ_C2 * E) / (vec3(1.0) + PQ_C3 * E), vec3(PQ_M2)); +} + +vec3 tfHLG(vec3 color) { + bvec3 isLow = lessThanEqual(color.rgb, vec3(HLG_D_CUT)); + vec3 lo = sqrt(max(color.rgb, vec3(0.0)) * 3.0); + vec3 hi = HLG_A * log(max(12.0 * color.rgb - HLG_B, vec3(0.0001))) + HLG_C; + return mix(hi, lo, isLow); +} + +vec3 tfLinPow(vec3 color, float gamma, float thres, float scale, float alpha) { + bvec3 isLow = lessThanEqual(color.rgb, vec3(thres)); + vec3 lo = color.rgb * scale; + vec3 hi = pow(color.rgb, vec3(1.0 / gamma)) * alpha - (alpha - 1.0); + return mix(hi, lo, isLow); +} + +vec3 tfSRGB(vec3 color) { + return tfLinPow(color, SRGB_POW, SRGB_CUT, SRGB_SCALE, SRGB_ALPHA); +} + +vec3 tfExtSRGB(vec3 color) { + // EXT sRGB is the sRGB transfer function mirrored around 0. + return sign(color) * tfSRGB(abs(color)); +} + +vec3 tfBT1886(vec3 color) { + return tfLinPow(color, BT1886_POW, BT1886_CUT, BT1886_SCALE, BT1886_ALPHA); +} + +vec3 tfXVYCC(vec3 color) { + // The transfer function for XVYCC is the BT1886 transfer function mirrored around 0, + // same as what EXT sRGB is to sRGB. + return sign(color) * tfBT1886(abs(color)); +} + +vec3 tfST240(vec3 color) { + return tfLinPow(color, ST240_POW, ST240_CUT, ST240_SCALE, ST240_ALPHA); +} + +vec3 toLinearRGB(vec3 color, int tf) { + switch (tf) { + case CM_TRANSFER_FUNCTION_EXT_LINEAR: return color; + case CM_TRANSFER_FUNCTION_ST2084_PQ: return tfInvPQ(color); + case CM_TRANSFER_FUNCTION_GAMMA22: return pow(max(color, vec3(0.0)), vec3(2.2)); + case CM_TRANSFER_FUNCTION_GAMMA28: return pow(max(color, vec3(0.0)), vec3(2.8)); + case CM_TRANSFER_FUNCTION_HLG: return tfInvHLG(color); + case CM_TRANSFER_FUNCTION_EXT_SRGB: return tfInvExtSRGB(color); + case CM_TRANSFER_FUNCTION_BT1886: return tfInvBT1886(color); + case CM_TRANSFER_FUNCTION_ST240: return tfInvST240(color); + case CM_TRANSFER_FUNCTION_LOG_100: return mix(exp((color - 1.0) * 2.0 * log(10.0)), vec3(0.0), lessThanEqual(color, vec3(0.0))); + case CM_TRANSFER_FUNCTION_LOG_316: return mix(exp((color - 1.0) * 2.5 * log(10.0)), vec3(0.0), lessThanEqual(color, vec3(0.0))); + case CM_TRANSFER_FUNCTION_XVYCC: return tfInvXVYCC(color); + case CM_TRANSFER_FUNCTION_ST428: return pow(max(color, vec3(0.0)), vec3(ST428_POW)) * ST428_SCALE; + case CM_TRANSFER_FUNCTION_SRGB: + default: return tfInvSRGB(color); + } +} + +vec4 toLinear(vec4 color, int tf) { + if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) + return color; + + color.rgb /= max(color.a, 0.001); + color.rgb = toLinearRGB(color.rgb, tf); + color.rgb *= color.a; + return color; +} + +vec4 toNit(vec4 color, vec2 range) { + color.rgb = color.rgb * (range[1] - range[0]) + range[0]; + return color; +} + +vec3 fromLinearRGB(vec3 color, int tf) { + switch (tf) { + case CM_TRANSFER_FUNCTION_EXT_LINEAR: return color; + case CM_TRANSFER_FUNCTION_ST2084_PQ: return tfPQ(color); + case CM_TRANSFER_FUNCTION_GAMMA22: return pow(max(color, vec3(0.0)), vec3(1.0 / 2.2)); + case CM_TRANSFER_FUNCTION_GAMMA28: return pow(max(color, vec3(0.0)), vec3(1.0 / 2.8)); + case CM_TRANSFER_FUNCTION_HLG: return tfHLG(color); + case CM_TRANSFER_FUNCTION_EXT_SRGB: return tfExtSRGB(color); + case CM_TRANSFER_FUNCTION_BT1886: return tfBT1886(color); + case CM_TRANSFER_FUNCTION_ST240: return tfST240(color); + case CM_TRANSFER_FUNCTION_LOG_100: return mix(1.0 + log(color) / log(10.0) / 2.0, vec3(0.0), lessThanEqual(color, vec3(0.01))); + case CM_TRANSFER_FUNCTION_LOG_316: return mix(1.0 + log(color) / log(10.0) / 2.5, vec3(0.0), lessThanEqual(color, vec3(sqrt(10.0) / 1000.0))); + case CM_TRANSFER_FUNCTION_XVYCC: return tfXVYCC(color); + case CM_TRANSFER_FUNCTION_ST428: return pow(max(color, vec3(0.0)) / ST428_SCALE, vec3(1.0 / ST428_POW)); + case CM_TRANSFER_FUNCTION_SRGB: + default: return tfSRGB(color); + } +} + +vec4 fromLinear(vec4 color, int tf) { + if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) + return color; + + color.rgb /= max(color.a, 0.001); + color.rgb = fromLinearRGB(color.rgb, tf); + color.rgb *= color.a; + return color; +} + +vec4 fromLinearNit(vec4 color, int tf, vec2 range) { + if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) + color.rgb = color.rgb / SDR_MAX_LUMINANCE; + else { + color.rgb /= max(color.a, 0.001); + color.rgb = (color.rgb - range[0]) / (range[1] - range[0]); + color.rgb = fromLinearRGB(color.rgb, tf); + color.rgb *= color.a; + } + return color; +} + +#if USE_TONEMAP +#include "tonemap.glsl" +#endif + +vec4 doColorManagement(vec4 pixColor, int srcTF, int dstTF, mat3 convertMatrix, vec2 srcTFRange, vec2 dstTFRange +#if USE_ICC + , + highp sampler3D iccLut3D, float iccLutSize +#else +#if USE_TONEMAP || USE_SDR_MOD + , + mat3 dstxyz +#endif +#if USE_TONEMAP + , + float maxLuminance, float dstMaxLuminance, float dstRefLuminance, float srcRefLuminance +#endif +#if USE_SDR_MOD + , + float sdrSaturation, float sdrBrightnessMultiplier +#endif +#endif +) { + pixColor.rgb /= max(pixColor.a, 0.001); + pixColor.rgb = toLinearRGB(pixColor.rgb, srcTF); +#if USE_ICC + pixColor.rgb = applyIcc3DLut(pixColor.rgb, iccLut3D, iccLutSize); + pixColor.rgb *= pixColor.a; +#else + pixColor.rgb = convertMatrix * pixColor.rgb; + pixColor = toNit(pixColor, srcTFRange); + pixColor.rgb *= pixColor.a; +#if USE_TONEMAP + pixColor = tonemap(pixColor, dstxyz, maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance); +#endif + pixColor = fromLinearNit(pixColor, dstTF, dstTFRange); +#if USE_SDR_MOD + pixColor = saturate(pixColor, dstxyz, sdrSaturation); + pixColor.rgb *= sdrBrightnessMultiplier; +#endif +#endif + + return pixColor; +} + +#endif \ No newline at end of file diff --git a/src/render/shaders/glsl/constants.h b/src/render/shaders/glsl/constants.h new file mode 100644 index 00000000..bbab5284 --- /dev/null +++ b/src/render/shaders/glsl/constants.h @@ -0,0 +1,62 @@ +#ifndef CONSTANTS_H +#define CONSTANTS_H +//enum eTransferFunction +#define CM_TRANSFER_FUNCTION_BT1886 1 +#define CM_TRANSFER_FUNCTION_GAMMA22 2 +#define CM_TRANSFER_FUNCTION_GAMMA28 3 +#define CM_TRANSFER_FUNCTION_ST240 4 +#define CM_TRANSFER_FUNCTION_EXT_LINEAR 5 +#define CM_TRANSFER_FUNCTION_LOG_100 6 +#define CM_TRANSFER_FUNCTION_LOG_316 7 +#define CM_TRANSFER_FUNCTION_XVYCC 8 +#define CM_TRANSFER_FUNCTION_SRGB 9 +#define CM_TRANSFER_FUNCTION_EXT_SRGB 10 +#define CM_TRANSFER_FUNCTION_ST2084_PQ 11 +#define CM_TRANSFER_FUNCTION_ST428 12 +#define CM_TRANSFER_FUNCTION_HLG 13 + +// sRGB constants +#define SRGB_POW 2.4 +#define SRGB_CUT 0.0031308 +#define SRGB_SCALE 12.92 +#define SRGB_ALPHA 1.055 + +#define BT1886_POW (1.0 / 0.45) +#define BT1886_CUT 0.018053968510807 +#define BT1886_SCALE 4.5 +#define BT1886_ALPHA (1.0 + 5.5 * BT1886_CUT) + +// See http://car.france3.mars.free.fr/HD/INA-%2026%20jan%2006/SMPTE%20normes%20et%20confs/s240m.pdf +#define ST240_POW (1.0 / 0.45) +#define ST240_CUT 0.0228 +#define ST240_SCALE 4.0 +#define ST240_ALPHA 1.1115 + +#define ST428_POW 2.6 +#define ST428_SCALE (52.37 / 48.0) + +// PQ constants +#define PQ_M1 0.1593017578125 +#define PQ_M2 78.84375 +#define PQ_INV_M1 (1.0 / PQ_M1) +#define PQ_INV_M2 (1.0 / PQ_M2) +#define PQ_C1 0.8359375 +#define PQ_C2 18.8515625 +#define PQ_C3 18.6875 + +// HLG constants +#define HLG_D_CUT (1.0 / 12.0) +#define HLG_E_CUT 0.5 +#define HLG_A 0.17883277 +#define HLG_B 0.28466892 +#define HLG_C 0.55991073 + +#define SDR_MIN_LUMINANCE 0.2 +#define SDR_MAX_LUMINANCE 80.0 +#define HDR_MIN_LUMINANCE 0.005 +#define HDR_MAX_LUMINANCE 10000.0 +#define HLG_MAX_LUMINANCE 1000.0 + +#define M_E 2.718281828459045 + +#endif diff --git a/src/render/shaders/glsl/defines.h b/src/render/shaders/glsl/defines.h new file mode 100644 index 00000000..31b120a4 --- /dev/null +++ b/src/render/shaders/glsl/defines.h @@ -0,0 +1,10 @@ +// DO NOT EDIT. Will be overwritten in runtime +#define USE_RGBA 1 +#define USE_DISCARD 1 +#define USE_TINT 1 +#define USE_ROUNDING 1 +#define USE_CM 1 +#define USE_TONEMAP 1 +#define USE_SDR_MOD 1 +#define USE_BLUR 1 +#define USE_ICC 1 diff --git a/src/render/shaders/glsl/ext.frag b/src/render/shaders/glsl/ext.frag index f540a9f9..1c614bd3 100644 --- a/src/render/shaders/glsl/ext.frag +++ b/src/render/shaders/glsl/ext.frag @@ -1,38 +1,43 @@ #version 300 es +#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable #extension GL_OES_EGL_image_external_essl3 : require -precision highp float; -in vec2 v_texcoord; -uniform samplerExternalOES texture0; -uniform float alpha; +precision highp float; +in vec2 v_texcoord; +uniform samplerExternalOES tex; +uniform float alpha; +uniform float radius; +uniform float roundingPower; +uniform vec2 topLeft; +uniform vec2 fullSize; #include "rounding.glsl" -uniform int discardOpaque; -uniform int discardAlpha; -uniform int discardAlphaValue; +uniform int discardOpaque; +uniform int discardAlpha; +uniform int discardAlphaValue; -uniform int applyTint; +uniform int applyTint; uniform vec3 tint; layout(location = 0) out vec4 fragColor; void main() { - vec4 pixColor = texture(texture0, v_texcoord); + vec4 pixColor = texture(tex, v_texcoord); if (discardOpaque == 1 && pixColor[3] * alpha == 1.0) - discard; + discard; if (applyTint == 1) { - pixColor[0] = pixColor[0] * tint[0]; - pixColor[1] = pixColor[1] * tint[1]; - pixColor[2] = pixColor[2] * tint[2]; + pixColor[0] = pixColor[0] * tint[0]; + pixColor[1] = pixColor[1] * tint[1]; + pixColor[2] = pixColor[2] * tint[2]; } if (radius > 0.0) - pixColor = rounding(pixColor); + pixColor = rounding(pixColor, radius, roundingPower, topLeft, fullSize); fragColor = pixColor * alpha; } diff --git a/src/render/shaders/glsl/gain.glsl b/src/render/shaders/glsl/gain.glsl new file mode 100644 index 00000000..2bdc0002 --- /dev/null +++ b/src/render/shaders/glsl/gain.glsl @@ -0,0 +1,6 @@ +vec3 gain(vec3 x, float k) { + vec3 t = step(0.5, x); + vec3 y = mix(x, 1.0 - x, t); + vec3 a = 0.5 * pow(2.0 * y, vec3(k)); + return mix(a, 1.0 - a, t); +} diff --git a/src/render/shaders/glsl/glitch.frag b/src/render/shaders/glsl/glitch.frag index e399a8b1..d7259cc4 100644 --- a/src/render/shaders/glsl/glitch.frag +++ b/src/render/shaders/glsl/glitch.frag @@ -5,7 +5,7 @@ in vec2 v_texcoord; uniform sampler2D tex; uniform float time; // quirk: time is set to 0 at the beginning, should be around 10 when crash. uniform float distort; -uniform vec2 screenSize; +uniform vec2 fullSize; float rand(float co) { return fract(sin(dot(vec2(co, co), vec2(12.9898, 78.233))) * 43758.5453); @@ -31,7 +31,7 @@ void main() { float ABERR_OFFSET = 4.0 * (distort / 5.5) * time; float TEAR_AMOUNT = 9000.0 * (1.0 - (distort / 5.5)); float TEAR_BANDS = 108.0 / 2.0 * (distort / 5.5) * 2.0; - float MELT_AMOUNT = (distort * 8.0) / screenSize.y; + float MELT_AMOUNT = (distort * 8.0) / fullSize.y; float NOISE = abs(mod(noise(v_texcoord) * distort * time * 2.771, 1.0)) * time / 10.0; if (time < 2.0) @@ -44,7 +44,7 @@ void main() { if (time < 3.0) blockOffset = vec2(0,0); - float meltSeed = abs(mod(rand(floor(v_texcoord.x * screenSize.x * 17.719)) * 281.882, 1.0)); + float meltSeed = abs(mod(rand(floor(v_texcoord.x * fullSize.x * 17.719)) * 281.882, 1.0)); if (meltSeed < 0.8) { meltSeed = 0.0; } else { @@ -52,11 +52,11 @@ void main() { } float meltAmount = MELT_AMOUNT * meltSeed; - vec2 pixCoord = vec2(v_texcoord.x + offset + NOISE * 3.0 / screenSize.x + blockOffset.x, v_texcoord.y - meltAmount + 0.02 * NOISE / screenSize.x + NOISE * 3.0 / screenSize.y + blockOffset.y); + vec2 pixCoord = vec2(v_texcoord.x + offset + NOISE * 3.0 / fullSize.x + blockOffset.x, v_texcoord.y - meltAmount + 0.02 * NOISE / fullSize.x + NOISE * 3.0 / fullSize.y + blockOffset.y); vec4 pixColor = texture(tex, pixCoord); - vec4 pixColorLeft = texture(tex, pixCoord + vec2(ABERR_OFFSET / screenSize.x, 0)); - vec4 pixColorRight = texture(tex, pixCoord + vec2(-ABERR_OFFSET / screenSize.x, 0)); + vec4 pixColorLeft = texture(tex, pixCoord + vec2(ABERR_OFFSET / fullSize.x, 0)); + vec4 pixColorRight = texture(tex, pixCoord + vec2(-ABERR_OFFSET / fullSize.x, 0)); pixColor[0] = pixColorLeft[0]; pixColor[2] = pixColorRight[2]; diff --git a/src/render/shaders/glsl/quad.frag b/src/render/shaders/glsl/quad.frag index 5dae493e..61895a60 100644 --- a/src/render/shaders/glsl/quad.frag +++ b/src/render/shaders/glsl/quad.frag @@ -1,17 +1,27 @@ #version 300 es +#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable -precision highp float; -in vec4 v_color; +#include "defines.h" +precision highp float; +in vec4 v_color; + +#if USE_ROUNDING +uniform float radius; +uniform float roundingPower; +uniform vec2 topLeft; +uniform vec2 fullSize; #include "rounding.glsl" +#endif layout(location = 0) out vec4 fragColor; void main() { vec4 pixColor = v_color; - if (radius > 0.0) - pixColor = rounding(pixColor); +#if USE_ROUNDING + pixColor = rounding(pixColor, radius, roundingPower, topLeft, fullSize); +#endif fragColor = pixColor; } diff --git a/src/render/shaders/glsl/rgba.frag b/src/render/shaders/glsl/rgba.frag deleted file mode 100644 index e4e04500..00000000 --- a/src/render/shaders/glsl/rgba.frag +++ /dev/null @@ -1,39 +0,0 @@ -#version 300 es - -#extension GL_ARB_shading_language_include : enable -precision highp float; -in vec2 v_texcoord; // is in 0-1 -uniform sampler2D tex; -uniform float alpha; - -#include "rounding.glsl" - -uniform int discardOpaque; -uniform int discardAlpha; -uniform float discardAlphaValue; - -uniform int applyTint; -uniform vec3 tint; - -layout(location = 0) out vec4 fragColor; -void main() { - - vec4 pixColor = texture(tex, v_texcoord); - - if (discardOpaque == 1 && pixColor[3] * alpha == 1.0) - discard; - - if (discardAlpha == 1 && pixColor[3] <= discardAlphaValue) - discard; - - if (applyTint == 1) { - pixColor[0] = pixColor[0] * tint[0]; - pixColor[1] = pixColor[1] * tint[1]; - pixColor[2] = pixColor[2] * tint[2]; - } - - if (radius > 0.0) - pixColor = rounding(pixColor); - - fragColor = pixColor * alpha; -} diff --git a/src/render/shaders/glsl/rgbx.frag b/src/render/shaders/glsl/rgbx.frag deleted file mode 100644 index 84672d76..00000000 --- a/src/render/shaders/glsl/rgbx.frag +++ /dev/null @@ -1,35 +0,0 @@ -#version 300 es -#extension GL_ARB_shading_language_include : enable -precision highp float; -in vec2 v_texcoord; -uniform sampler2D tex; -uniform float alpha; - -#include "rounding.glsl" - -uniform int discardOpaque; -uniform int discardAlpha; -uniform int discardAlphaValue; - -uniform int applyTint; -uniform vec3 tint; - -layout(location = 0) out vec4 fragColor; -void main() { - - if (discardOpaque == 1 && alpha == 1.0) - discard; - - vec4 pixColor = vec4(texture(tex, v_texcoord).rgb, 1.0); - - if (applyTint == 1) { - pixColor[0] = pixColor[0] * tint[0]; - pixColor[1] = pixColor[1] * tint[1]; - pixColor[2] = pixColor[2] * tint[2]; - } - - if (radius > 0.0) - pixColor = rounding(pixColor); - - fragColor = pixColor * alpha; -} diff --git a/src/render/shaders/glsl/rounding.glsl b/src/render/shaders/glsl/rounding.glsl index 472415fd..61a0bb9c 100644 --- a/src/render/shaders/glsl/rounding.glsl +++ b/src/render/shaders/glsl/rounding.glsl @@ -1,13 +1,10 @@ +#ifndef ROUNDING_GLSL +#define ROUNDING_GLSL // smoothing constant for the edge: more = blurrier, but smoother -#define M_PI 3.1415926535897932384626433832795 +#define M_PI 3.1415926535897932384626433832795 #define SMOOTHING_CONSTANT (M_PI / 5.34665792551) -uniform float radius; -uniform float roundingPower; -uniform vec2 topLeft; -uniform vec2 fullSize; - -vec4 rounding(vec4 color) { +vec4 rounding(vec4 color, float radius, float roundingPower, vec2 topLeft, vec2 fullSize) { vec2 pixCoord = vec2(gl_FragCoord); pixCoord -= topLeft + fullSize * 0.5; pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0; @@ -15,7 +12,7 @@ vec4 rounding(vec4 color) { pixCoord += vec2(1.0, 1.0) / fullSize; // center the pix don't make it top-left if (pixCoord.x + pixCoord.y > radius) { - float dist = pow(pow(pixCoord.x, roundingPower) + pow(pixCoord.y, roundingPower), 1.0/roundingPower); + float dist = pow(pow(pixCoord.x, roundingPower) + pow(pixCoord.y, roundingPower), 1.0 / roundingPower); if (dist > radius + SMOOTHING_CONSTANT) discard; @@ -27,3 +24,4 @@ vec4 rounding(vec4 color) { return color; } +#endif \ No newline at end of file diff --git a/src/render/shaders/glsl/shadow.frag b/src/render/shaders/glsl/shadow.frag index b6fdf6ee..c23ebd5d 100644 --- a/src/render/shaders/glsl/shadow.frag +++ b/src/render/shaders/glsl/shadow.frag @@ -1,99 +1,57 @@ #version 300 es +#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable -precision highp float; -in vec4 v_color; -in vec2 v_texcoord; +#include "defines.h" -uniform int skipCM; -uniform int sourceTF; // eTransferFunction -uniform int targetTF; // eTransferFunction -uniform mat4x2 targetPrimaries; +precision highp float; +in vec4 v_color; +in vec2 v_texcoord; -uniform vec2 topLeft; -uniform vec2 bottomRight; -uniform vec2 fullSize; +uniform int sourceTF; // eTransferFunction +uniform int targetTF; // eTransferFunction +uniform mat3 targetPrimariesXYZ; + +uniform vec2 topLeft; +uniform vec2 bottomRight; +uniform vec2 fullSize; uniform float radius; uniform float roundingPower; uniform float range; uniform float shadowPower; +#if USE_CM +#include "cm_helpers.glsl" #include "CM.glsl" +#endif -float pixAlphaRoundedDistance(float distanceToCorner) { - if (distanceToCorner > radius) { - return 0.0; - } - - if (distanceToCorner > radius - range) { - return pow((range - (distanceToCorner - radius + range)) / range, shadowPower); // i think? - } - - return 1.0; -} - -float modifiedLength(vec2 a) { - return pow(pow(abs(a.x),roundingPower)+pow(abs(a.y),roundingPower),1.0/roundingPower); -} +#include "shadow.glsl" layout(location = 0) out vec4 fragColor; void main() { + vec4 pixColor = v_color; - vec4 pixColor = v_color; - float originalAlpha = pixColor[3]; - - bool done = false; - - vec2 pixCoord = fullSize * v_texcoord; - - // ok, now we check the distance to a border. - - if (pixCoord[0] < topLeft[0]) { - if (pixCoord[1] < topLeft[1]) { - // top left - pixColor[3] = pixColor[3] * pixAlphaRoundedDistance(modifiedLength(pixCoord - topLeft)); - done = true; - } else if (pixCoord[1] > bottomRight[1]) { - // bottom left - pixColor[3] = pixColor[3] * pixAlphaRoundedDistance(modifiedLength(pixCoord - vec2(topLeft[0], bottomRight[1]))); - done = true; - } - } else if (pixCoord[0] > bottomRight[0]) { - if (pixCoord[1] < topLeft[1]) { - // top right - pixColor[3] = pixColor[3] * pixAlphaRoundedDistance(modifiedLength(pixCoord - vec2(bottomRight[0], topLeft[1]))); - done = true; - } else if (pixCoord[1] > bottomRight[1]) { - // bottom right - pixColor[3] = pixColor[3] * pixAlphaRoundedDistance(modifiedLength(pixCoord - bottomRight)); - done = true; - } - } - - if (!done) { - // distance to all straight bb borders - float distanceT = pixCoord[1]; - float distanceB = fullSize[1] - pixCoord[1]; - float distanceL = pixCoord[0]; - float distanceR = fullSize[0] - pixCoord[0]; - - // get the smallest - float smallest = min(min(distanceT, distanceB), min(distanceL, distanceR)); - - if (smallest < range) { - pixColor[3] = pixColor[3] * pow((smallest / range), shadowPower); - } - } - - if (pixColor[3] == 0.0) { - discard; return; - } - - // premultiply - pixColor.rgb *= pixColor[3]; - - if (skipCM == 0) - pixColor = doColorManagement(pixColor, sourceTF, targetTF, targetPrimaries); - - fragColor = pixColor; + fragColor = getShadow(pixColor, v_texcoord, radius, roundingPower, topLeft, fullSize, range, shadowPower, bottomRight +#if USE_CM + , + sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange +#if USE_ICC + , + iccLut3D, iccLutSize +#else +#if USE_TONEMAP || USE_SDR_MOD + , + targetPrimariesXYZ +#endif +#if USE_TONEMAP + , + maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance +#endif +#if USE_SDR_MOD + , + sdrSaturation, sdrBrightnessMultiplier +#endif +#endif +#endif + ); } \ No newline at end of file diff --git a/src/render/shaders/glsl/shadow.glsl b/src/render/shaders/glsl/shadow.glsl new file mode 100644 index 00000000..48cde562 --- /dev/null +++ b/src/render/shaders/glsl/shadow.glsl @@ -0,0 +1,126 @@ +#ifndef ALLOW_INCLUDES +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable +#endif +#ifndef SHADOW_GLSL +#define SHADOW_GLSL + +#include "cm_helpers.glsl" + +float pixAlphaRoundedDistance(float distanceToCorner, float radius, float range, float shadowPower) { + if (distanceToCorner > radius) { + return 0.0; + } + + if (distanceToCorner > radius - range) { + return pow((range - (distanceToCorner - radius + range)) / range, shadowPower); // i think? + } + + return 1.0; +} + +float modifiedLength(vec2 a, float roundingPower) { + return pow(pow(abs(a.x), roundingPower) + pow(abs(a.y), roundingPower), 1.0 / roundingPower); +} + +vec4 getShadow(vec4 pixColor, vec2 v_texcoord, float radius, float roundingPower, vec2 topLeft, vec2 fullSize, float range, float shadowPower, vec2 bottomRight +#if USE_CM + , + int sourceTF, int targetTF, mat3 convertMatrix, vec2 srcTFRange, vec2 dstTFRange +#if USE_ICC + , + highp sampler3D iccLut3D, float iccLutSize +#else +#if USE_TONEMAP || USE_SDR_MOD + , + mat3 targetPrimariesXYZ +#endif +#if USE_TONEMAP + , + float maxLuminance, float dstMaxLuminance, float dstRefLuminance, float srcRefLuminance +#endif +#if USE_SDR_MOD + , + float sdrSaturation, float sdrBrightnessMultiplier +#endif +#endif +#endif +) { + float originalAlpha = pixColor[3]; + + bool done = false; + + vec2 pixCoord = fullSize * v_texcoord; + + // ok, now we check the distance to a border. + + if (pixCoord[0] < topLeft[0]) { + if (pixCoord[1] < topLeft[1]) { + // top left + pixColor[3] = pixColor[3] * pixAlphaRoundedDistance(modifiedLength(pixCoord - topLeft, roundingPower), radius, range, shadowPower); + done = true; + } else if (pixCoord[1] > bottomRight[1]) { + // bottom left + pixColor[3] = pixColor[3] * pixAlphaRoundedDistance(modifiedLength(pixCoord - vec2(topLeft[0], bottomRight[1]), roundingPower), radius, range, shadowPower); + done = true; + } + } else if (pixCoord[0] > bottomRight[0]) { + if (pixCoord[1] < topLeft[1]) { + // top right + pixColor[3] = pixColor[3] * pixAlphaRoundedDistance(modifiedLength(pixCoord - vec2(bottomRight[0], topLeft[1]), roundingPower), radius, range, shadowPower); + done = true; + } else if (pixCoord[1] > bottomRight[1]) { + // bottom right + pixColor[3] = pixColor[3] * pixAlphaRoundedDistance(modifiedLength(pixCoord - bottomRight, roundingPower), radius, range, shadowPower); + done = true; + } + } + + if (!done) { + // distance to all straight bb borders + float distanceT = pixCoord[1]; + float distanceB = fullSize[1] - pixCoord[1]; + float distanceL = pixCoord[0]; + float distanceR = fullSize[0] - pixCoord[0]; + + // get the smallest + float smallest = min(min(distanceT, distanceB), min(distanceL, distanceR)); + + if (smallest < range) { + pixColor[3] = pixColor[3] * pow((smallest / range), shadowPower); + } + } + + if (pixColor[3] == 0.0) { + discard; + return pixColor; + } + + // premultiply + pixColor.rgb *= pixColor[3]; + +#if USE_CM + pixColor = doColorManagement(pixColor, sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange +#if USE_ICC + , + iccLut3D, iccLutSize +#else +#if USE_TONEMAP || USE_SDR_MOD + , + targetPrimariesXYZ +#endif +#if USE_TONEMAP + , + maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance +#endif +#if USE_SDR_MOD + , + sdrSaturation, sdrBrightnessMultiplier +#endif +#endif + ); +#endif + + return pixColor; +} +#endif \ No newline at end of file diff --git a/src/render/shaders/glsl/surface.frag b/src/render/shaders/glsl/surface.frag new file mode 100644 index 00000000..30023bc8 --- /dev/null +++ b/src/render/shaders/glsl/surface.frag @@ -0,0 +1,104 @@ +#version 300 es +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable + +#include "defines.h" + +precision highp float; +in vec2 v_texcoord; +uniform sampler2D tex; +#if USE_BLUR +uniform vec2 uvSize; +uniform vec2 uvOffset; +uniform sampler2D blurredBG; +#endif + +uniform float alpha; + +#if USE_DISCARD +uniform bool discardOpaque; +uniform bool discardAlpha; +uniform float discardAlphaValue; +#endif + +#if USE_TINT +uniform vec3 tint; +#endif + +#if USE_ROUNDING +uniform float radius; +uniform float roundingPower; +uniform vec2 topLeft; +uniform vec2 fullSize; +#include "rounding.glsl" +#endif + +#if USE_CM +uniform int sourceTF; // eTransferFunction +uniform int targetTF; // eTransferFunction + +#if USE_TONEMAP || USE_SDR_MOD +uniform mat3 targetPrimariesXYZ; +#else +const mat3 targetPrimariesXYZ = mat3(0.0); +#endif + +#include "CM.glsl" +#endif + +layout(location = 0) out vec4 fragColor; +void main() { +#if USE_RGBA + vec4 pixColor = texture(tex, v_texcoord); +#else + vec4 pixColor = vec4(texture(tex, v_texcoord).rgb, 1.0); +#endif + +#if USE_DISCARD && !USE_BLUR + if (discardOpaque && pixColor.a * alpha == 1.0) + discard; + + if (discardAlpha && pixColor.a <= discardAlphaValue) + discard; +#endif + +#if USE_CM + pixColor = doColorManagement(pixColor, sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange +#if USE_ICC + , + iccLut3D, iccLutSize +#else +#if USE_TONEMAP || USE_SDR_MOD + , + targetPrimariesXYZ +#endif +#if USE_TONEMAP + , + maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance +#endif +#if USE_SDR_MOD + , + sdrSaturation, sdrBrightnessMultiplier +#endif +#endif + ); +#endif + +#if USE_TINT + pixColor.rgb = pixColor.rgb * tint; +#endif + +#if USE_ROUNDING + pixColor = rounding(pixColor, radius, roundingPower, topLeft, fullSize); +#endif +#if USE_BLUR +#if USE_DISCARD + pixColor = mix(pixColor, vec4(mix(texture(blurredBG, v_texcoord * uvSize + uvOffset).rgb, pixColor.rgb, pixColor.a), 1.0), + discardAlpha && (pixColor.a <= discardAlphaValue) ? 0.0 : 1.0); +#else + pixColor = vec4(mix(texture(blurredBG, v_texcoord * uvSize + uvOffset).rgb, pixColor.rgb, pixColor.a), 1.0); +#endif +#endif + + fragColor = pixColor * alpha; +} diff --git a/src/render/shaders/glsl/tonemap.glsl b/src/render/shaders/glsl/tonemap.glsl new file mode 100644 index 00000000..a0ba24ef --- /dev/null +++ b/src/render/shaders/glsl/tonemap.glsl @@ -0,0 +1,64 @@ +#ifndef ALLOW_INCLUDES +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable +#endif +#include "constants.h" + +const mat3 BT2020toLMS = mat3(0.3592, 0.6976, -0.0358, -0.1922, 1.1004, 0.0755, 0.0070, 0.0749, 0.8434); +//const mat3 LMStoBT2020 = inverse(BT2020toLMS); +const mat3 LMStoBT2020 = mat3( // + 2.0701800566956135096, -1.3264568761030210255, 0.20661600684785517081, // + 0.36498825003265747974, 0.68046736285223514102, -0.045421753075853231409, // + -0.049595542238932107896, -0.049421161186757487412, 1.1879959417328034394 // +); + +// const mat3 ICtCpPQ = transpose(mat3( +// 2048.0, 2048.0, 0.0, +// 6610.0, -13613.0, 7003.0, +// 17933.0, -17390.0, -543.0 +// ) / 4096.0); +const mat3 ICtCpPQ = mat3( // + 0.5, 1.61376953125, 4.378173828125, // + 0.5, -3.323486328125, -4.24560546875, // + 0.0, 1.709716796875, -0.132568359375 // +); +//const mat3 ICtCpPQInv = inverse(ICtCpPQ); +const mat3 ICtCpPQInv = mat3( // + 1.0, 1.0, 1.0, // + 0.0086090370379327566, -0.0086090370379327566, 0.560031335710679118, // + 0.11102962500302595656, -0.11102962500302595656, -0.32062717498731885185 // +); + +// unused for now +// const mat3 ICtCpHLG = transpose(mat3( +// 2048.0, 2048.0, 0.0, +// 3625.0, -7465.0, 3840.0, +// 9500.0, -9212.0, -288.0 +// ) / 4096.0); +// const mat3 ICtCpHLGInv = inverse(ICtCpHLG); + +vec4 tonemap(vec4 color, mat3 dstXYZ, float maxLuminance, float dstMaxLuminance, float dstRefLuminance, float srcRefLuminance) { + if (maxLuminance < dstMaxLuminance * 1.01) + return vec4(clamp(color.rgb, vec3(0.0), vec3(dstMaxLuminance)), color[3]); + + mat3 toLMS = BT2020toLMS * dstXYZ; + mat3 fromLMS = inverse(dstXYZ) * LMStoBT2020; + + vec3 lms = fromLinear(vec4((toLMS * color.rgb) / HDR_MAX_LUMINANCE, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb; + vec3 ICtCp = ICtCpPQ * lms; + + float E = pow(clamp(ICtCp[0], 0.0, 1.0), PQ_INV_M2); + float luminance = pow((max(E - PQ_C1, 0.0)) / (PQ_C2 - PQ_C3 * E), PQ_INV_M1) * HDR_MAX_LUMINANCE; + + float linearPart = min(luminance, dstRefLuminance); + float luminanceAboveRef = max(luminance - dstRefLuminance, 0.0); + float maxExcessLuminance = max(maxLuminance - dstRefLuminance, 1.0); + float shoulder = log((luminanceAboveRef / maxExcessLuminance + 1.0) * (M_E - 1.0)); + float mappedHigh = shoulder * (dstMaxLuminance - dstRefLuminance); + float newLum = clamp(linearPart + mappedHigh, 0.0, dstMaxLuminance); + + // scale src to dst reference + float refScale = dstRefLuminance / srcRefLuminance; + + return vec4(fromLMS * toLinear(vec4(ICtCpPQInv * ICtCp, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb * HDR_MAX_LUMINANCE * refScale, color[3]); +} diff --git a/src/signal-safe.hpp b/src/signal-safe.hpp deleted file mode 100644 index a48b51b0..00000000 --- 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 6750db10..1ece7454 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; } } @@ -149,7 +149,7 @@ static bool openSockets(std::array& sockets, int display) { } #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); @@ -173,7 +173,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 +183,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; } @@ -232,11 +232,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 +295,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 +305,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 +317,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 +325,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 +341,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 +357,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 +365,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 +375,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 +392,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 +400,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 8e7b2505..5d34a9d8 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 73c512f2..5c5f3b5c 100644 --- a/src/xwayland/XSurface.cpp +++ b/src/xwayland/XSurface.cpp @@ -7,8 +7,6 @@ #ifndef NO_XWAYLAND -#include - CXWaylandSurface::CXWaylandSurface(uint32_t xID_, CBox geometry_, bool OR) : m_xID(xID_), m_geometry(geometry_), m_overrideRedirect(OR) { xcb_res_query_client_ids_cookie_t client_id_cookie = {0}; if (g_pXWayland->m_wm->m_xres) { @@ -42,9 +40,43 @@ CXWaylandSurface::CXWaylandSurface(uint32_t xID_, CBox geometry_, bool OR) : m_x free(reply); // NOLINT(cppcoreguidelines-no-malloc) } + // FIXME: this is a race, we need to listen to props changed + recheckSupportedProps(); + m_events.resourceChange.listenStatic([this] { ensureListeners(); }); } +void CXWaylandSurface::recheckSupportedProps() { + m_supportedProps.clear(); + + auto listCookie = xcb_list_properties(g_pXWayland->m_wm->getConnection(), m_xID); + auto* listReply = xcb_list_properties_reply(g_pXWayland->m_wm->getConnection(), listCookie, nullptr); + auto getCookie = xcb_get_property(g_pXWayland->m_wm->getConnection(), 0, m_xID, HYPRATOMS["WM_PROTOCOLS"], XCB_ATOM_ATOM, 0, 32); + auto* getReply = xcb_get_property_reply(g_pXWayland->m_wm->getConnection(), getCookie, nullptr); + + if (listReply) { + const auto* atoms = xcb_list_properties_atoms(listReply); + auto len = xcb_list_properties_atoms_length(listReply); + + for (auto i = 0; i < len; ++i) { + m_supportedProps[atoms[i]] = true; + } + + free(listReply); + } + + if (getReply) { + const auto* protocols = sc(xcb_get_property_value(getReply)); + const auto len = xcb_get_property_value_length(getReply) / sizeof(xcb_atom_t); + + for (auto i = 0u; i < len; ++i) { + m_supportedProps[protocols[i]] = true; + } + + free(getReply); + } +} + void CXWaylandSurface::ensureListeners() { bool connected = m_listeners.destroySurface; @@ -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 10eecbaf..a8ccac4d 100644 --- a/src/xwayland/XSurface.hpp +++ b/src/xwayland/XSurface.hpp @@ -11,6 +11,7 @@ class CXWaylandSurfaceResource; #ifdef NO_XWAYLAND using xcb_pixmap_t = uint32_t; using xcb_window_t = uint32_t; +using xcb_atom_t = uint32_t; using xcb_icccm_wm_hints_t = struct { int32_t flags; uint32_t input; @@ -111,6 +112,7 @@ class CXWaylandSurface { void unmap(); void considerMap(); void setWithdrawn(bool withdrawn); + void recheckSupportedProps(); struct { CHyprSignalListener destroyResource; @@ -118,5 +120,7 @@ class CXWaylandSurface { CHyprSignalListener commitSurface; } m_listeners; + std::unordered_map m_supportedProps; + friend class CXWM; }; diff --git a/src/xwayland/XWM.cpp b/src/xwayland/XWM.cpp index 9abd955a..7e20d9b1 100644 --- a/src/xwayland/XWM.cpp +++ b/src/xwayland/XWM.cpp @@ -18,8 +18,10 @@ #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" using Hyprutils::Memory::CUniquePointer; using namespace Hyprutils::OS; @@ -31,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) @@ -55,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) @@ -122,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 @@ -207,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); @@ -273,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 = [&]() { @@ -329,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; } @@ -368,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); } @@ -380,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); } @@ -388,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) @@ -444,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; } @@ -454,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) { @@ -488,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? } @@ -555,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; } @@ -564,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) { @@ -619,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; } @@ -641,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; @@ -671,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; } @@ -688,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; } @@ -708,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 @@ -731,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; } @@ -745,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); @@ -805,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(); @@ -842,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); } } } @@ -863,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; } @@ -873,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; } @@ -892,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; } @@ -916,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; } @@ -930,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; } @@ -946,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; } @@ -956,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; } @@ -1037,7 +1060,7 @@ void CXWM::activateSurface(SP surf, bool activate) { if ((surf == m_focusedSurface && activate) || (surf && surf->m_overrideRedirect)) return; - if (!surf || (!activate && g_pCompositor->m_lastWindow && !g_pCompositor->m_lastWindow->m_isX11)) { + if (!surf || (!activate && Desktop::focusState()->window() && !Desktop::focusState()->window()->m_isX11)) { setActiveWindow(XCB_WINDOW_NONE); focusWindow(nullptr); } else { @@ -1049,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 @@ -1067,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"]); @@ -1082,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(); @@ -1094,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); @@ -1123,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()); @@ -1146,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; } @@ -1168,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() { @@ -1258,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); @@ -1278,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; } @@ -1313,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); @@ -1334,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; } @@ -1373,7 +1399,7 @@ SP CXWM::createX11DataOffer(SP surf, SPlog(Log::ERR, "[xwm] No xwayland surface for destination in createX11DataOffer"); return nullptr; } @@ -1431,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; } @@ -1442,7 +1468,7 @@ 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"); + Log::logger->log(Log::ERR, "[xwm] readDataSource died"); g_pXWayland->m_wm->selectionSendNotify(&transfer->request, false); transfers.erase(it); return 0; @@ -1452,13 +1478,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()); @@ -1467,13 +1493,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); @@ -1490,30 +1516,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 + "', "; @@ -1522,7 +1548,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; @@ -1530,7 +1556,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; } @@ -1540,14 +1566,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; } @@ -1557,7 +1583,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; } @@ -1568,7 +1594,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]}); @@ -1581,7 +1607,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; } @@ -1593,28 +1619,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); @@ -1632,7 +1674,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 b328a2c9..af1fa06a 100644 --- a/src/xwayland/XWM.hpp +++ b/src/xwayland/XWM.hpp @@ -35,9 +35,9 @@ struct SXTransfer { xcb_selection_request_event_t request; - int propertyStart; - xcb_get_property_reply_t* propertyReply; - xcb_window_t incomingWindow; + int propertyStart = 0; + xcb_get_property_reply_t* propertyReply = nullptr; + xcb_window_t incomingWindow = 0; bool getIncomingSelectionProp(bool erase); }; @@ -54,6 +54,7 @@ struct SXSelection { bool sendData(xcb_selection_request_event_t* e, std::string mime); int onRead(int fd, uint32_t mask); int onWrite(); + void removeTransfer(xcb_window_t window); struct { CHyprSignalListener setSelection; @@ -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 f7bdf1e6..a022217a 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 00000000..00b1fded --- /dev/null +++ b/start/CMakeLists.txt @@ -0,0 +1,24 @@ +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) + +file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp") + +add_executable(start-hyprland ${SRCFILES}) + +target_link_libraries(start-hyprland PUBLIC PkgConfig::starthyprland_deps) + +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 00000000..2f5007bd --- /dev/null +++ b/start/src/core/Instance.cpp @@ -0,0 +1,184 @@ +#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; + } + + write(m_wakeupWrite.get(), "vax", 3); + + 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) { + static std::array buf; + read(fd.get(), buf.data(), 1023); +} + +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]; + pipe(pipefds); + + m_fromHlPid = CFileDescriptor{pipefds[0]}; + m_toHlPid = CFileDescriptor{pipefds[1]}; + + pipe(pipefds); + + 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 00000000..2c72dc12 --- /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 00000000..6a44c8d0 --- /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 00000000..ae771203 --- /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 00000000..66ba2c1f --- /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 00000000..da66183e --- /dev/null +++ b/start/src/helpers/Nix.cpp @@ -0,0 +1,125 @@ +#include "Nix.hpp" + +#include "Logger.hpp" +#include "../core/State.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +using namespace Hyprutils::String; +using namespace Hyprutils::OS; + +using namespace Hyprutils::File; + +static std::optional getFromEtcOsRelease(const std::string_view& sv) { + static std::string content = ""; + static bool once = true; + + if (once) { + once = false; + + auto read = readFileAsString("/etc/os-release"); + content = read.value_or(""); + } + + static CVarList2 vars(std::move(content), 0, '\n', true); + + for (const auto& v : vars) { + if (v.starts_with(sv) && v.contains('=')) { + // found + auto value = trim(v.substr(v.find('=') + 1)); + + if (value.back() == value.front() && value.back() == '"') + value = value.substr(1, value.size() - 2); + + return std::string{value}; + } + } + + return std::nullopt; +} + +static bool executableExistsInPath(const std::string& exe) { + const char* PATHENV = std::getenv("PATH"); + if (!PATHENV) + return false; + + CVarList2 paths(PATHENV, 0, ':', true); + std::error_code ec; + + for (const auto& PATH : paths) { + std::filesystem::path candidate = std::filesystem::path(PATH) / exe; + if (!std::filesystem::exists(candidate, ec) || ec) + continue; + if (!std::filesystem::is_regular_file(candidate, ec) || ec) + continue; + auto perms = std::filesystem::status(candidate, ec).permissions(); + if (ec) + continue; + if ((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none) + return true; + } + + return false; +} + +std::expected Nix::nixEnvironmentOk() { + if (!shouldUseNixGL()) + return {}; + + if (!executableExistsInPath("nixGL")) + return std::unexpected( + "Hyprland was installed using Nix, but you're not on NixOS. This requires nixGL to be installed as well.\nYou can install nixGL by running \"nix profile install " + "github:guibou/nixGL --impure\" in your terminal."); + + return {}; +} + +bool Nix::shouldUseNixGL() { + if (g_state->forceNixGl) + return true; + + if (g_state->noNixGl) + return false; + + // check if installed hyprland is nix'd + CProcess proc("Hyprland", {"--version-json"}); + if (!proc.runSync()) { + g_logger->log(Hyprutils::CLI::LOG_ERR, "failed to obtain hyprland version string"); + return false; + } + + auto json = glz::read_json(proc.stdOut()); + if (!json) { + g_logger->log(Hyprutils::CLI::LOG_ERR, "failed to obtain hyprland version string (bad json)"); + return false; + } + + const auto FLAGS = (*json)["flags"].get_array(); + const bool IS_NIX = std::ranges::any_of(FLAGS, [](const auto& e) { return e.get_string() == std::string_view{"nix"}; }); + + if (IS_NIX) { + const auto NAME = getFromEtcOsRelease("NAME"); + + // Hyprland is nix: recommend nixGL iff !NIX && !NIX-OPENGL + + if (*NAME == "NixOS") + return false; + + std::error_code ec; + + if (std::filesystem::exists("/run/opengl-driver", ec) && !ec) // NOLINTNEXTLINE + return false; + + // we are not on nix / no nix opengl driver + return true; + } + + return false; +} diff --git a/start/src/helpers/Nix.hpp b/start/src/helpers/Nix.hpp new file mode 100644 index 00000000..edc01b19 --- /dev/null +++ b/start/src/helpers/Nix.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + +namespace Nix { + std::expected nixEnvironmentOk(); + bool shouldUseNixGL(); +}; \ No newline at end of file diff --git a/start/src/main.cpp b/start/src/main.cpp new file mode 100644 index 00000000..45a78357 --- /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 37aff70d..05cceee0 160000 --- a/subprojects/tracy +++ b/subprojects/tracy @@ -1 +1 @@ -Subproject commit 37aff70dfa50cf6307b3fee6074d627dc2929143 +Subproject commit 05cceee0df3b8d7c6fa87e9638af311dbabc63cb diff --git a/subprojects/tracy.wrap b/subprojects/tracy.wrap deleted file mode 100644 index 11b21787..00000000 --- a/subprojects/tracy.wrap +++ /dev/null @@ -1 +0,0 @@ -[wrap-file] diff --git a/subprojects/udis86.wrap b/subprojects/udis86.wrap deleted file mode 100644 index dfb63984..00000000 --- a/subprojects/udis86.wrap +++ /dev/null @@ -1,5 +0,0 @@ -[wrap-file] -method = cmake - -[provide] -udis86 = libudis86_dep diff --git a/systemd/hyprland-uwsm.desktop b/systemd/hyprland-uwsm.desktop index 3f37532d..2ea70cb6 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/systemd/meson.build b/systemd/meson.build deleted file mode 100644 index bc62e95a..00000000 --- a/systemd/meson.build +++ /dev/null @@ -1,7 +0,0 @@ -if (get_option('uwsm').allowed()) - install_data( - 'hyprland-uwsm.desktop', - install_dir: join_paths(get_option('datadir'), 'wayland-sessions'), - install_tag: 'runtime', - ) -endif diff --git a/tests/desktop/Reserved.cpp b/tests/desktop/Reserved.cpp new file mode 100644 index 00000000..b3942e32 --- /dev/null +++ b/tests/desktop/Reserved.cpp @@ -0,0 +1,52 @@ + +#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); +} \ No newline at end of file