diff --git a/.github/actions/setup_base/action.yml b/.github/actions/setup_base/action.yml index d7b52a791..665d7f07d 100644 --- a/.github/actions/setup_base/action.yml +++ b/.github/actions/setup_base/action.yml @@ -45,6 +45,7 @@ runs: libxkbfile \ lld \ meson \ + muparser \ ninja \ pango \ pixman \ diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d9c0d1a6c..d14ac02e9 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,16 @@ 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: 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$ diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index e935a605b..505829e36 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/nix-test.yml b/.github/workflows/nix-test.yml index 086f0077c..68357093a 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 c83e98801..a3084b270 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 1d514c507..b46b37953 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/translation-ai-check.yml b/.github/workflows/translation-ai-check.yml new file mode 100644 index 000000000..0729e9f61 --- /dev/null +++ b/.github/workflows/translation-ai-check.yml @@ -0,0 +1,154 @@ +name: AI Translation Check + +on: + # pull_request_target: + # types: + # - opened + issue_comment: + types: + - created + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + changes: + name: Check i18n changes + 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 + steps: + - name: Checkout source code + uses: actions/checkout@v5 + + - uses: dorny/paths-filter@v3 + id: changes + with: + filters: | + i18n: + - 'src/i18n/**' + review: + name: Review Translation + needs: changes + if: ${{ needs.changes.outputs.i18n == 'true' }} + 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/CMakeLists.txt b/CMakeLists.txt index 29de4a324..7062e7d98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,7 +108,7 @@ find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION}) 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(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=0.10.2) pkg_check_modules(hyprgraphics_dep REQUIRED IMPORTED_TARGET hyprgraphics>=0.1.6) string(REPLACE "." ";" AQ_VERSION_LIST ${aquamarine_dep_VERSION}) @@ -128,13 +128,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 +183,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 -- @@ -192,7 +220,7 @@ pkg_check_modules( deps REQUIRED IMPORTED_TARGET - xkbcommon + xkbcommon>=1.11.0 uuid wayland-server>=1.22.90 wayland-protocols>=1.45 @@ -205,7 +233,8 @@ pkg_check_modules( libinput>=1.28 gbm gio-2.0 - re2) + re2 + muparser) find_package(hyprwayland-scanner 0.3.10 REQUIRED) @@ -483,7 +512,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) diff --git a/assets/install/meson.build b/assets/install/meson.build deleted file mode 100644 index 450764695..000000000 --- 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 2a28121dd..000000000 --- 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 6ff51d1a5..000000000 --- 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 a1408dc38..07b372c10 100644 --- a/example/hyprland.conf +++ b/example/hyprland.conf @@ -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 { @@ -294,11 +307,25 @@ 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 +} diff --git a/example/meson.build b/example/meson.build deleted file mode 100644 index a338644e0..000000000 --- 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 221c4558d..5df06468d 100644 --- a/flake.lock +++ b/flake.lock @@ -193,11 +193,11 @@ ] }, "locked": { - "lastModified": 1758927902, - "narHash": "sha256-LZgMds7M94+vuMql2bERQ6LiFFdhgsEFezE4Vn+Ys3A=", + "lastModified": 1763254292, + "narHash": "sha256-JNgz3Fz2KMzkT7aR72wsgu/xNeJB//LSmdilh8Z/Zao=", "owner": "hyprwm", "repo": "hyprlang", - "rev": "4dafa28d4f79877d67a7d1a654cddccf8ebf15da", + "rev": "deea98d5b61d066bdc7a68163edd2c4bd28d3a6b", "type": "github" }, "original": { @@ -261,11 +261,11 @@ ] }, "locked": { - "lastModified": 1762387740, - "narHash": "sha256-gQ9zJ+pUI4o+Gh4Z6jhJll7jjCSwi8ZqJIhCE2oqwhQ=", + "lastModified": 1763323331, + "narHash": "sha256-+Z0OfCo1MS8/aIutSAW5aJR9zTae1wz9kcJYMgpwN6M=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "926689ddb9c0a8787e58c02c765a62e32d63d1f7", + "rev": "0c6411851cc779d551edc89b83966696201611aa", "type": "github" }, "original": { @@ -299,11 +299,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1762363567, - "narHash": "sha256-YRqMDEtSMbitIMj+JLpheSz0pwEr0Rmy5mC7myl17xs=", + "lastModified": 1763283776, + "narHash": "sha256-Y7TDFPK4GlqrKrivOcsHG8xSGqQx3A6c+i7novT85Uk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ae814fd3904b621d8ab97418f1d0f2eb0d3716f4", + "rev": "50a96edd8d0db6cc8db57dab6bb6d6ee1f3dc49a", "type": "github" }, "original": { @@ -322,11 +322,11 @@ ] }, "locked": { - "lastModified": 1762441963, - "narHash": "sha256-j+rNQ119ffYUkYt2YYS6rnd6Jh/crMZmbqpkGLXaEt0=", + "lastModified": 1763319842, + "narHash": "sha256-YG19IyrTdnVn0l3DvcUYm85u3PaqBt6tI6VvolcuHnA=", "owner": "cachix", "repo": "git-hooks.nix", - "rev": "8e7576e79b88c16d7ee3bbd112c8d90070832885", + "rev": "7275fa67fbbb75891c16d9dee7d88e58aea2d761", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 5c58e26d0..6799144b4 100644 --- a/flake.nix +++ b/flake.nix @@ -151,7 +151,6 @@ (pkgsFor.${system}) # hyprland-packages hyprland - hyprland-with-hyprtester hyprland-unwrapped # hyprland-extras xdg-desktop-portal-hyprland diff --git a/hyprctl/meson.build b/hyprctl/meson.build deleted file mode 100644 index d6769b84a..000000000 --- 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/hyprpm/src/main.cpp b/hyprpm/src/main.cpp index 47a557e2c..777d1d46e 100644 --- a/hyprpm/src/main.cpp +++ b/hyprpm/src/main.cpp @@ -25,8 +25,8 @@ constexpr std::string_view HELP = R"#(┏ hyprpm, a Hyprland Plugin Manager ┃ ┣ 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. +┣ --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). @@ -47,7 +47,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; std::string customHlUrl; for (int i = 1; i < argc; ++i) { @@ -58,7 +58,9 @@ 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-shallow" || ARGS[i] == "-s") { @@ -149,8 +151,9 @@ 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.")); @@ -194,19 +197,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 fd914f9d2..000000000 --- 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/plugin/src/main.cpp b/hyprtester/plugin/src/main.cpp index 1d0b68dcc..72120eaca 100644 --- a/hyprtester/plugin/src/main.cpp +++ b/hyprtester/plugin/src/main.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #undef private @@ -245,6 +247,30 @@ static SDispatchResult keybind(std::string in) { return {}; } +static Desktop::Rule::CWindowRuleEffectContainer::storageType ruleIDX = 0; + +// +static SDispatchResult addRule(std::string in) { + ruleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule"); + + if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != ruleIDX) + return {.success = false, .error = "re-registering returned a different id?"}; + return {}; +} + +static SDispatchResult checkRule(std::string in) { + if (!g_pCompositor->m_lastWindow) + return {.success = false, .error = "No window"}; + + if (!g_pCompositor->m_lastWindow->m_ruleApplicator->m_otherProps.props.contains(ruleIDX)) + return {.success = false, .error = "No rule"}; + + if (g_pCompositor->m_lastWindow->m_ruleApplicator->m_otherProps.props[ruleIDX]->effect != "effect") + return {.success = false, .error = "Effect isn't \"effect\""}; + + return {}; +} + APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { PHANDLE = handle; @@ -255,6 +281,8 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:gesture", ::simulateGesture); HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:scroll", ::scroll); HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:keybind", ::keybind); + HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_rule", ::addRule); + HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_rule", ::checkRule); // init mouse g_mouse = CTestMouse::create(false); diff --git a/hyprtester/src/tests/clients/pointer-scroll.cpp b/hyprtester/src/tests/clients/pointer-scroll.cpp index e1ba237fa..2ea93a14c 100644 --- a/hyprtester/src/tests/clients/pointer-scroll.cpp +++ b/hyprtester/src/tests/clients/pointer-scroll.cpp @@ -64,7 +64,7 @@ static bool startClient(SClient& client) { // wait for window to appear std::this_thread::sleep_for(std::chrono::milliseconds(5000)); - if (getFromSocket(std::format("/dispatch setprop pid:{} noanim 1", client.proc->pid())) != "ok") { + 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 +130,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 f37b94c35..bb03afd28 100644 --- a/hyprtester/src/tests/clients/pointer-warp.cpp +++ b/hyprtester/src/tests/clients/pointer-warp.cpp @@ -64,7 +64,7 @@ static bool startClient(SClient& client) { // wait for window to appear std::this_thread::sleep_for(std::chrono::milliseconds(5000)); - if (getFromSocket(std::format("/dispatch setprop pid:{} noanim 1", client.proc->pid())) != "ok") { + 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/main/dwindle.cpp b/hyprtester/src/tests/main/dwindle.cpp index a75646c8d..8f17c8152 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"); } diff --git a/hyprtester/src/tests/main/hyprctl.cpp b/hyprtester/src/tests/main/hyprctl.cpp index 95f7caaef..e8759d28c 100644 --- a/hyprtester/src/tests/main/hyprctl.cpp +++ b/hyprtester/src/tests/main/hyprctl.cpp @@ -56,82 +56,82 @@ 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})"); + // 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})"); - // 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_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})"); - // 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_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})"); - // 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_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})"); - // 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_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})"); - // 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_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})"); - // 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"})"); + // 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 +141,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/tags.cpp b/hyprtester/src/tests/main/tags.cpp index 22bedcde3..c345fe718 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 6cfa061c5..871875927 100644 --- a/hyprtester/src/tests/main/window.cpp +++ b/hyprtester/src/tests/main/window.cpp @@ -152,40 +152,22 @@ static bool test() { NLog::log("{}Testing window split ratios", Colors::YELLOW); { - const double INITIAL_RATIO = 1.25; - const int GAPSIN = 5; - const int GAPSOUT = 20; - const int BORDERSIZE = 2; - const int BORDERS = BORDERSIZE * 2; - const int MONITOR_W = 1920; - const int MONITOR_H = 1080; - - const float totalAvailableHeight = MONITOR_H - (GAPSOUT * 2); - const int HEIGHT = std::round(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); - }; - - double geomBoxWidthA_R1 = (availableWidthForSplit * INITIAL_RATIO / 2.0) + GAPSOUT + (GAPSIN / 2.0); - double geomBoxWidthB_R1 = MONITOR_W - geomBoxWidthA_R1; - const int WIDTH1 = calculateFinalWidth(geomBoxWidthB_R1, false); - - const double INVERTED_RATIO = 0.75; - double geomBoxWidthA_R2 = (availableWidthForSplit * INVERTED_RATIO / 2.0) + GAPSOUT + (GAPSIN / 2.0); - double geomBoxWidthB_R2 = MONITOR_W - geomBoxWidthA_R2; - const int WIDTH2 = calculateFinalWidth(geomBoxWidthB_R2, false); - const int WIDTH_A_FINAL = calculateFinalWidth(geomBoxWidthA_R2, true); + const double RATIO = 1.25; + const double PERCENT = RATIO / 2.0 * 100.0; + const int GAPSIN = 5; + const int GAPSOUT = 20; + const int BORDERS = 2 * 2; + const int WTRIM = BORDERS + GAPSIN + GAPSOUT; + const int HEIGHT = 1080 - (BORDERS + (GAPSOUT * 2)); + const int WIDTH1 = std::round(1920.0 / 2.0 * (2 - RATIO)) - WTRIM; + const int WIDTH2 = std::round(1920.0 / 2.0 * RATIO) - WTRIM; OK(getFromSocket("/keyword dwindle:default_split_ratio 1.25")); if (!spawnKitty("kitty_B")) return false; - NLog::log("{}Expecting kitty_B size: {},{}", Colors::YELLOW, WIDTH1, HEIGHT); + NLog::log("{}Expecting kitty_B to take up roughly {}% of screen width", Colors::YELLOW, 100 - PERCENT); EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH1, HEIGHT)); OK(getFromSocket("/dispatch killwindow activewindow")); @@ -197,12 +179,12 @@ static bool test() { if (!spawnKitty("kitty_B")) return false; - NLog::log("{}Expecting kitty_B size: {},{}", Colors::YELLOW, WIDTH2, HEIGHT); + NLog::log("{}Expecting kitty_B to take up roughly {}% of screen width", Colors::YELLOW, PERCENT); EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", 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)); + NLog::log("{}Expecting kitty_A to have the same width as the previous kitty_B", Colors::YELLOW); + EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH1, HEIGHT)); OK(getFromSocket("/keyword dwindle:default_split_ratio 1")); } @@ -246,12 +228,15 @@ static bool test() { testSwapWindow(); + getFromSocket("/dispatch workspace 1"); + 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; @@ -297,29 +282,127 @@ 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(); + + 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(); + + 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, 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"); + } + + OK(getFromSocket("/reload")); + Tests::killAllWindows(); + + OK(getFromSocket("/dispatch plugin:test:add_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_rule")); + + OK(getFromSocket("/reload")); + Tests::killAllWindows(); + + OK(getFromSocket("/dispatch plugin:test:add_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_rule")); + 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 def35d088..622236dcf 100644 --- a/hyprtester/src/tests/main/workspaces.cpp +++ b/hyprtester/src/tests/main/workspaces.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include "../shared.hpp" @@ -15,7 +14,6 @@ static int ret = 0; using namespace Hyprutils::OS; using namespace Hyprutils::Memory; -using namespace Hyprutils::Utils; #define UP CUniquePointer #define SP CSharedPointer @@ -27,7 +25,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")); @@ -361,95 +359,6 @@ static bool test() { 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(); - NLog::log("{}Expecting 0 windows", Colors::YELLOW); EXPECT(Tests::windowCount(), 0); diff --git a/hyprtester/test.conf b/hyprtester/test.conf index 047001c26..ac28bc5a6 100644 --- a/hyprtester/test.conf +++ b/hyprtester/test.conf @@ -318,28 +318,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 @@ -356,7 +398,3 @@ gesture = 5, left, dispatcher, sendshortcut, , i, activewindow gesture = 5, right, dispatcher, sendshortcut, , t, activewindow gesture = 4, right, dispatcher, sendshortcut, , return, activewindow gesture = 4, left, dispatcher, movecursortocorner, 1 - -windowrule = float, pin, class:wr_kitty -windowrule = size 200 200, class:wr_kitty -windowrule = unset pin, class:wr_kitty diff --git a/meson.build b/meson.build deleted file mode 100644 index c7819e80b..000000000 --- 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: 'hyprland/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 e50b4ccee..000000000 --- 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 0fe57191a..45fd273b8 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -26,6 +26,7 @@ libxkbcommon, libuuid, libgbm, + muparser, pango, pciutils, re2, @@ -45,12 +46,12 @@ 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; @@ -70,6 +71,7 @@ in assert assertMsg (!enableNvidiaPatches) "The option `enableNvidiaPatches` has been removed."; assert assertMsg (!hidpiXWayland) "The option `hidpiXWayland` has been removed. Please refer https://wiki.hypr.land/Configuring/XWayland"; assert assertMsg (!legacyRenderer) "The option `legacyRenderer` has been removed. Legacy renderer is no longer supported."; + assert assertMsg (!withHyprtester) "The option `withHyprtester` has been removed. Hyprtester is always built now."; customStdenv.mkDerivation (finalAttrs: { pname = "hyprland${optionalString debug "-debug"}"; inherit version; @@ -85,6 +87,7 @@ in ../assets/install ../hyprctl ../hyprland.pc.in + ../hyprtester ../LICENSE ../protocols ../src @@ -94,7 +97,6 @@ in (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) ])); }; @@ -106,11 +108,13 @@ in sed -i "s#@PREFIX@/##g" hyprland.pc.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 @@ -146,6 +150,7 @@ in libuuid libxkbcommon libgbm + muparser pango pciutils re2 @@ -187,7 +192,7 @@ in "NO_UWSM" = true; "NO_HYPRPM" = true; "TRACY_ENABLE" = false; - "BUILD_HYPRTESTER" = withHyprtester; + "BUILD_HYPRTESTER" = true; }; preConfigure = '' @@ -206,7 +211,7 @@ in pkgconf ]} ''} - '' + optionalString withHyprtester '' + install hyprtester/pointer-warp -t $out/bin install hyprtester/pointer-scroll -t $out/bin ''; diff --git a/nix/overlays.nix b/nix/overlays.nix index 7f6bf2ae3..c7ef95b86 100644 --- a/nix/overlays.nix +++ b/nix/overlays.nix @@ -43,7 +43,12 @@ in { }; hyprland-unwrapped = final.hyprland.override {wrapRuntimeDeps = false;}; - hyprland-with-hyprtester = final.hyprland.override {withHyprtester = true;}; + hyprland-with-hyprtester = + builtins.trace '' + hyprland-with-hyprtester was removed. Please use the hyprland package. + Hyprtester is always built now. + '' + final.hyprland; # deprecated packages hyprland-legacy-renderer = diff --git a/nix/tests/default.nix b/nix/tests/default.nix index d7c000617..ef92a4635 100644 --- a/nix/tests/default.nix +++ b/nix/tests/default.nix @@ -1,6 +1,6 @@ inputs: pkgs: let flake = inputs.self.packages.${pkgs.stdenv.hostPlatform.system}; - hyprland = flake.hyprland-with-hyprtester; + hyprland = flake.hyprland; in { tests = pkgs.testers.runNixOSTest { name = "hyprland-tests"; diff --git a/protocols/meson.build b/protocols/meson.build deleted file mode 100644 index 33663fa38..000000000 --- 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/src/Compositor.cpp b/src/Compositor.cpp index 2dc798e47..0f24a8bfc 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -65,6 +65,7 @@ #include "debug/HyprNotificationOverlay.hpp" #include "debug/HyprDebugOverlay.hpp" #include "helpers/MonitorFrameScheduler.hpp" +#include "i18n/Engine.hpp" #include #include @@ -900,8 +901,8 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper 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 +939,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()) @@ -999,7 +1000,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,7 +1016,7 @@ 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}; if (box.containsPoint(pos)) @@ -1151,7 +1152,7 @@ void CCompositor::focusWindow(PHLWINDOW pWindow, SP pSurface m_lastWindow.reset(); if (PLASTWINDOW && PLASTWINDOW->m_isMapped) { - updateWindowAnimatedDecorationValues(PLASTWINDOW); + PLASTWINDOW->updateDecorationValues(); g_pXWaylandManager->activateWindow(PLASTWINDOW, false); } @@ -1171,7 +1172,7 @@ void CCompositor::focusWindow(PHLWINDOW pWindow, SP pSurface return; } - if (pWindow->m_windowData.noFocus.valueOrDefault()) { + if (pWindow->m_ruleApplicator->noFocus().valueOrDefault()) { Debug::log(LOG, "Ignoring focus to nofocus window!"); return; } @@ -1208,9 +1209,9 @@ void CCompositor::focusWindow(PHLWINDOW pWindow, SP pSurface // 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(); + PLASTWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FOCUS); - updateWindowAnimatedDecorationValues(PLASTWINDOW); + PLASTWINDOW->updateDecorationValues(); if (!pWindow->m_isX11 || !pWindow->isX11OverrideRedirect()) g_pXWaylandManager->activateWindow(PLASTWINDOW, false); @@ -1224,10 +1225,10 @@ void CCompositor::focusWindow(PHLWINDOW pWindow, SP pSurface g_pXWaylandManager->activateWindow(pWindow, true); // sets the m_pLastWindow - pWindow->updateDynamicRules(); + pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FOCUS); pWindow->onFocusAnimUpdate(); - updateWindowAnimatedDecorationValues(pWindow); + pWindow->updateDecorationValues(); if (pWindow->m_isUrgent) pWindow->m_isUrgent = false; @@ -1333,7 +1334,7 @@ SP CCompositor::vectorToLayerSurface(const Vector2D& pos, st 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))) + (aboveLockscreen && ls->m_ruleApplicator->aboveLock().valueOrDefault() != 2)) continue; auto [surf, local] = ls->m_layerSurface->m_surface->at(pos - ls->m_geometry.pos(), true); @@ -1714,7 +1715,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 @@ -1905,103 +1906,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]; })) @@ -2340,14 +2248,14 @@ void CCompositor::changeWindowFullscreenModeClient(const PHLWINDOW PWINDOW, cons } void CCompositor::setWindowFullscreenInternal(const PHLWINDOW PWINDOW, const eFullscreenMode MODE) { - if (PWINDOW->m_windowData.syncFullscreen.valueOrDefault()) + if (PWINDOW->m_ruleApplicator->syncFullscreen().valueOrDefault()) setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = MODE, .client = MODE}); else setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = MODE, .client = PWINDOW->m_fullscreenState.client}); } void CCompositor::setWindowFullscreenClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE) { - if (PWINDOW->m_windowData.syncFullscreen.valueOrDefault()) + if (PWINDOW->m_ruleApplicator->syncFullscreen().valueOrDefault()) setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = MODE, .client = MODE}); else setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = PWINDOW->m_fullscreenState.internal, .client = MODE}); @@ -2388,15 +2296,16 @@ 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); + PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT | + Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE); + PWINDOW->updateDecorationValues(); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID()); return; } @@ -2410,8 +2319,10 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, SFullscreenS g_pEventManager->postEvent(SHyprIPCEvent{.event = "fullscreen", .data = std::to_string(sc(EFFECTIVE_MODE) != FSMODE_NONE)}); EMIT_HOOK_EVENT("fullscreen", PWINDOW); - PWINDOW->updateDynamicRules(); - updateWindowAnimatedDecorationValues(PWINDOW); + PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT | + Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE); + + PWINDOW->updateDecorationValues(); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID()); // make all windows on the same workspace under the fullscreen window @@ -2551,7 +2462,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; @@ -2765,22 +2676,19 @@ void CCompositor::performUserChecks() { 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); + 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); } } @@ -2845,7 +2753,7 @@ void CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWor } pWindow->updateToplevel(); - pWindow->updateDynamicRules(); + pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE); pWindow->uncacheWindowDecos(); pWindow->updateGroupOutputs(); @@ -2876,7 +2784,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; @@ -2905,10 +2813,8 @@ 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); + g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, {{"name", m->m_name}}), CHyprColor{}, 15000, + ICON_WARNING); break; } @@ -3029,6 +2935,8 @@ void CCompositor::arrangeMonitors() { #ifndef NO_XWAYLAND CBox box = g_pCompositor->calculateX11WorkArea(); + if (!g_pXWayland || !g_pXWayland->m_wm) + return; g_pXWayland->m_wm->updateWorkArea(box.x, box.y, box.w, box.h); #endif } diff --git a/src/Compositor.hpp b/src/Compositor.hpp index bf6401e51..30b0f1bd0 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -129,7 +129,6 @@ class CCompositor { PHLMONITOR getMonitorInDirection(const char&); PHLMONITOR getMonitorInDirection(PHLMONITOR, const char&); void updateAllWindowsAnimatedDecorationValues(); - void updateWindowAnimatedDecorationValues(PHLWINDOW); MONITORID getNextAvailableMonitorID(std::string const& name); void moveWorkspaceToMonitor(PHLWORKSPACE, PHLMONITOR, bool noWarpCursor = false); void swapActiveWorkspaces(PHLMONITOR, PHLMONITOR); diff --git a/src/config/ConfigDescriptions.hpp b/src/config/ConfigDescriptions.hpp index 926d49ac7..f819e2930 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)", @@ -1115,6 +1109,12 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_BOOL, .data = SConfigOptionDescription::SRangeData{0, -20, 20}, }, + SConfigOptionDescription{ + .value = "group:groupbar:blur", + .description = "enable background blur for groupbars", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, /* * misc: diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index a3c60d2bb..775d3beb0 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -8,12 +8,15 @@ #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/rule/Engine.hpp" +#include "../desktop/rule/windowRule/WindowRule.hpp" +#include "../desktop/rule/layerRule/LayerRule.hpp" +#include "../debug/HyprCtl.hpp" #include "defaultConfig.hpp" #include "../render/Renderer.hpp" @@ -299,54 +302,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 +386,30 @@ 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 handleLayerrule(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = g_pConfigManager->handleLayerrule(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + return result; +} + void CConfigManager::registerConfigVar(const char* name, const Hyprlang::INT& val) { m_configValueNumber++; m_config->addConfigValue(name, val); @@ -463,7 +442,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"}); @@ -560,6 +538,7 @@ 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:blur", Hyprlang::INT{0}); registerConfigVar("debug:log_damage", Hyprlang::INT{0}); registerConfigVar("debug:overlay", Hyprlang::INT{0}); @@ -857,6 +836,16 @@ CConfigManager::CConfigManager() { m_config->addSpecialConfigValue("monitorv2", "max_luminance", Hyprlang::INT{-1}); m_config->addSpecialConfigValue("monitorv2", "max_avg_luminance", Hyprlang::INT{-1}); + // windowrule v3 + m_config->addSpecialCategory("windowrule", {.key = "name"}); + m_config->addSpecialConfigValue("windowrule", "enable", Hyprlang::INT{1}); + + // layerrule v2 + m_config->addSpecialCategory("layerrule", {.key = "name"}); + m_config->addSpecialConfigValue("layerrule", "enable", Hyprlang::INT{1}); + + reloadRuleConfigs(); + // keywords m_config->registerHandler(&::handleExec, "exec", {false}); m_config->registerHandler(&::handleRawExec, "execr", {false}); @@ -867,14 +856,12 @@ 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}); @@ -904,6 +891,26 @@ CConfigManager::CConfigManager() { g_pEventLoopManager->doLater([ERR] { g_pHyprError->queueCreate(ERR.value(), CHyprColor{1.0, 0.1, 0.1, 1.0}); }); } +void CConfigManager::reloadRuleConfigs() { + // FIXME: this should also remove old values if they are removed + + for (const auto& r : Desktop::Rule::allMatchPropStrings()) { + m_config->addSpecialConfigValue("windowrule", ("match:" + r).c_str(), Hyprlang::STRING{""}); + } + + for (const auto& r : Desktop::Rule::windowEffects()->allEffectStrings()) { + m_config->addSpecialConfigValue("windowrule", r.c_str(), Hyprlang::STRING{""}); + } + + for (const auto& r : Desktop::Rule::allMatchPropStrings()) { + m_config->addSpecialConfigValue("layerrule", ("match:" + r).c_str(), Hyprlang::STRING{""}); + } + + for (const auto& r : Desktop::Rule::layerEffects()->allEffectStrings()) { + m_config->addSpecialConfigValue("layerrule", r.c_str(), Hyprlang::STRING{""}); + } +} + std::optional CConfigManager::generateConfig(std::string configPath) { std::string parentPath = std::filesystem::path(configPath).parent_path(); @@ -983,13 +990,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); 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,20 +1087,18 @@ 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(); @@ -1080,6 +1108,8 @@ std::optional CConfigManager::resetHLConfig() { const auto RET = verifyConfigExists(); + reloadRuleConfigs(); + return RET; } @@ -1178,6 +1208,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(); @@ -1503,229 +1604,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; @@ -1831,16 +1709,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) @@ -1894,7 +1762,7 @@ void CConfigManager::ensureVRR(PHLMONITOR pMonitor) { 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) { @@ -1963,10 +1831,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; @@ -2675,239 +2539,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(','); @@ -3228,6 +2859,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}, 0, ','); + + 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}, 0, ','); + + 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; } diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp index 7f32be419..599ee8e71 100644 --- a/src/config/ConfigManager.hpp +++ b/src/config/ConfigManager.hpp @@ -13,14 +13,12 @@ #include #include "../helpers/Monitor.hpp" #include "../desktop/Window.hpp" -#include "../desktop/LayerRule.hpp" #include "ConfigDataValues.hpp" #include "../SharedDefs.hpp" #include "../helpers/Color.hpp" #include "../desktop/DesktopTypes.hpp" #include "../helpers/memory/Memory.hpp" -#include "../desktop/WindowRule.hpp" #include "../managers/XWaylandManager.hpp" #include "../managers/KeybindManager.hpp" @@ -68,11 +66,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*/ @@ -214,7 +207,6 @@ class CConfigManager { 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&); void* const* getConfigValuePtr(const std::string&); Hyprlang::CConfigValue* getHyprlangConfigValuePtr(const std::string& name, const std::string& specialCat = ""); @@ -229,8 +221,6 @@ class CConfigManager { 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(); const std::vector& getAllDescriptions(); @@ -260,8 +250,6 @@ class CConfigManager { SP getAnimationPropertyConfig(const std::string&); - void addExecRule(const SExecRequestedRule&); - void handlePluginLoads(); std::string getErrors(); @@ -274,22 +262,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 +300,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 +323,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 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 f3a8a2cad..dfd84b43b 100644 --- a/src/config/ConfigWatcher.cpp +++ b/src/config/ConfigWatcher.cpp @@ -1,5 +1,7 @@ #include "ConfigWatcher.hpp" +#if defined(__linux__) #include +#endif #include #include "../debug/Log.hpp" #include diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index b5de65030..82a697157 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -44,6 +44,7 @@ using namespace Hyprutils::OS; #include "config/ConfigManager.hpp" #include "helpers/MiscFunctions.hpp" #include "../desktop/LayerSurface.hpp" +#include "../desktop/rule/Engine.hpp" #include "../version.h" #include "../Compositor.hpp" @@ -317,7 +318,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(), @@ -1272,8 +1273,12 @@ 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 we are executing a dynamic source we have to reload everything, so every if will have a check for source. if (COMMAND == "monitor" || COMMAND == "source") g_pConfigManager->m_wantsMonitorReload = true; // for monitor keywords @@ -1306,8 +1311,7 @@ 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; @@ -1316,6 +1320,9 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in) } } + if (COMMAND.contains("windowrule ") || COMMAND.contains("windowrule[")) + g_pConfigManager->reloadRules(); + if (COMMAND.contains("workspace")) g_pConfigManager->ensurePersistentWorkspacesPresent(); @@ -1521,11 +1528,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, ' '); @@ -1543,9 +1545,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); @@ -1556,7 +1558,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); @@ -1590,7 +1592,7 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ const auto* const ACTIVECOLOR = !PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.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 @@ -1603,7 +1605,7 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ const auto* const INACTIVECOLOR = !PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.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 @@ -1618,38 +1620,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"; } @@ -2014,7 +2070,6 @@ CHyprCtl::CHyprCtl() { 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}); @@ -2130,8 +2185,7 @@ 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 (auto const& m : g_pCompositor->m_monitors) { diff --git a/src/debug/HyprCtl.hpp b/src/debug/HyprCtl.hpp index 95bb65b8c..d4f7aa149 100644 --- a/src/debug/HyprCtl.hpp +++ b/src/debug/HyprCtl.hpp @@ -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/desktop/LayerRule.cpp b/src/desktop/LayerRule.cpp deleted file mode 100644 index d6bfcadf2..000000000 --- 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 7b6c8a6d9..000000000 --- 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.cpp b/src/desktop/LayerSurface.cpp index 6278078d9..aab0b15ac 100644 --- a/src/desktop/LayerSurface.cpp +++ b/src/desktop/LayerSurface.cpp @@ -37,7 +37,7 @@ PHLLS CLayerSurface::create(SP resource) { 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); 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); @@ -55,7 +55,7 @@ PHLLS CLayerSurface::create(SP resource) { 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()); }); } @@ -137,6 +137,8 @@ void CLayerSurface::onMap() { m_mapped = true; m_interactivity = m_layerSurface->m_current.interactivity; + m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ALL); + m_layerSurface->m_surface->map(); // this layer might be re-mapped. @@ -149,8 +151,6 @@ void CLayerSurface::onMap() { if (!PMONITOR) return; - applyRules(); - PMONITOR->m_scheduledRecalc = true; g_pHyprRenderer->arrangeLayersForMonitor(PMONITOR->m_id); @@ -398,83 +398,6 @@ void CLayerSurface::onCommit() { 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; - } - } -} - bool CLayerSurface::isFadedOut() { if (!m_fadingOut) return false; diff --git a/src/desktop/LayerSurface.hpp b/src/desktop/LayerSurface.hpp index b70739cd3..5676e4d21 100644 --- a/src/desktop/LayerSurface.hpp +++ b/src/desktop/LayerSurface.hpp @@ -3,6 +3,7 @@ #include #include "../defines.hpp" #include "WLSurface.hpp" +#include "rule/layerRule/LayerRuleApplicator.hpp" #include "../helpers/AnimatedVariable.hpp" class CLayerShellResource; @@ -17,7 +18,6 @@ class CLayerSurface { public: ~CLayerSurface(); - void applyRules(); bool isFadedOut(); int popupsCount(); @@ -28,47 +28,35 @@ class CLayerSurface { WP m_layerSurface; // the header providing the enum type cannot be imported here - int m_interactivity = 0; + int m_interactivity = 0; - SP m_surface; + SP m_surface; - bool m_mapped = false; - uint32_t m_layer = 0; + bool m_mapped = false; + uint32_t m_layer = 0; - PHLMONITORREF m_monitor; + PHLMONITORREF m_monitor; - bool m_fadingOut = false; - bool m_readyToDelete = false; - bool m_noProcess = false; - bool m_noAnimations = false; + bool m_fadingOut = false; + bool m_readyToDelete = false; + bool m_noProcess = 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; + UP m_ruleApplicator; - std::optional m_animationStyle; + PHLLSREF m_self; - PHLLSREF m_self; + CBox m_geometry = {0, 0, 0, 0}; + Vector2D m_position; + std::string m_namespace = ""; + UP m_popupHead; - CBox m_geometry = {0, 0, 0, 0}; - Vector2D m_position; - std::string m_namespace = ""; - UP m_popupHead; + pid_t getPID(); - pid_t getPID(); - - void onDestroy(); - void onMap(); - void onUnmap(); - void onCommit(); - MONITORID monitorID(); + void onDestroy(); + void onMap(); + void onUnmap(); + void onCommit(); + MONITORID monitorID(); private: struct { diff --git a/src/desktop/Rule.cpp b/src/desktop/Rule.cpp deleted file mode 100644 index 93f38de03..000000000 --- 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 9d3de70e8..000000000 --- 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/Window.cpp b/src/desktop/Window.cpp index 8671c0036..b27128869 100644 --- a/src/desktop/Window.cpp +++ b/src/desktop/Window.cpp @@ -24,6 +24,7 @@ #include "../protocols/FractionalScale.hpp" #include "../xwayland/XWayland.hpp" #include "../helpers/Color.hpp" +#include "../helpers/math/Expression.hpp" #include "../events/Events.hpp" #include "../managers/XWaylandManager.hpp" #include "../render/Renderer.hpp" @@ -41,8 +42,9 @@ using enum NContentType::eContentType; PHLWINDOW CWindow::create(SP surface) { PHLWINDOW pWindow = SP(new CWindow(surface)); - pWindow->m_self = pWindow; - pWindow->m_isX11 = true; + pWindow->m_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); @@ -67,6 +69,7 @@ PHLWINDOW CWindow::create(SP 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); @@ -138,7 +141,7 @@ SBoxExtents CWindow::getFullWindowExtents() { const int BORDERSIZE = getRealBorderSize(); - if (m_windowData.dimAround.valueOrDefault()) { + 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), @@ -191,7 +194,7 @@ SBoxExtents CWindow::getFullWindowExtents() { } CBox CWindow::getFullWindowBoundingBox() { - if (m_windowData.dimAround.valueOrDefault()) { + 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}; } @@ -251,7 +254,7 @@ SBoxExtents CWindow::getWindowExtentsUnified(uint64_t properties) { } CBox CWindow::getWindowBoxUnified(uint64_t properties) { - if (m_windowData.dimAround.valueOrDefault()) { + 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}; @@ -636,222 +639,6 @@ 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 @@ -924,6 +711,8 @@ void CWindow::createGroup() { g_pEventManager->postEvent(SHyprIPCEvent{.event = "togglegroup", .data = std::format("1,{:x}", rc(this))}); } + + m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); } void CWindow::destroyGroup() { @@ -943,6 +732,7 @@ void CWindow::destroyGroup() { g_pCompositor->updateAllWindowsAnimatedDecorationValues(); g_pEventManager->postEvent(SHyprIPCEvent{.event = "togglegroup", .data = std::format("0,{:x}", rc(this))}); + m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); return; } @@ -969,6 +759,7 @@ void CWindow::destroyGroup() { g_pKeybindManager->m_groupsLocked = true; for (auto const& w : members) { g_pLayoutManager->getCurrentLayout()->onWindowCreated(w); + w->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); w->updateWindowDecos(); } g_pKeybindManager->m_groupsLocked = GROUPSLOCKEDPREV; @@ -982,6 +773,8 @@ void CWindow::destroyGroup() { if (!addresses.empty()) addresses.pop_back(); + + m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); g_pEventManager->postEvent(SHyprIPCEvent{.event = "togglegroup", .data = std::format("0,{}", addresses)}); } @@ -1217,16 +1010,16 @@ 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. */ + float roundingPower = m_ruleApplicator->roundingPower().valueOr(*PROUNDINGPOWER); + float rounding = m_ruleApplicator->rounding().valueOr(*PROUNDING) * (roundingPower / 2.0); /* Make perceived roundness consistent. */ - return m_windowData.noRounding.valueOrDefault() ? 0 : rounding; + return rounding; } float CWindow::roundingPower() { static auto PROUNDINGPOWER = CConfigValue("decoration:rounding_power"); - return m_windowData.roundingPower.valueOr(std::clamp(*PROUNDINGPOWER, 1.F, 10.F)); + return m_ruleApplicator->roundingPower().valueOr(std::clamp(*PROUNDINGPOWER, 1.F, 10.F)); } void CWindow::updateWindowData() { @@ -1236,50 +1029,43 @@ void CWindow::updateWindowData() { } 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); + m_ruleApplicator->borderSize().matchOptional(workspaceRule.borderSize, Desktop::Types::PRIORITY_WORKSPACE_RULE); + m_ruleApplicator->decorate().matchOptional(workspaceRule.decorate, Desktop::Types::PRIORITY_WORKSPACE_RULE); + m_ruleApplicator->borderSize().matchOptional(workspaceRule.noBorder ? std::optional(0) : std::nullopt, Desktop::Types::PRIORITY_WORKSPACE_RULE); + m_ruleApplicator->rounding().matchOptional(workspaceRule.noRounding.value_or(false) ? std::optional(0) : std::nullopt, Desktop::Types::PRIORITY_WORKSPACE_RULE); + m_ruleApplicator->noShadow().matchOptional(workspaceRule.noShadow, Desktop::Types::PRIORITY_WORKSPACE_RULE); } int CWindow::getRealBorderSize() { - if (m_windowData.noBorder.valueOrDefault() || (m_workspace && isEffectiveInternalFSMode(FSMODE_FULLSCREEN)) || !m_windowData.decorate.valueOrDefault()) + if ((m_workspace && isEffectiveInternalFSMode(FSMODE_FULLSCREEN)) || !m_ruleApplicator->decorate().valueOrDefault()) return 0; static auto PBORDERSIZE = CConfigValue("general:border_size"); - return m_windowData.borderSize.valueOr(*PBORDERSIZE); + return m_ruleApplicator->borderSize().valueOr(*PBORDERSIZE); } float CWindow::getScrollMouse() { static auto PINPUTSCROLLFACTOR = CConfigValue("input:scroll_factor"); - return m_windowData.scrollMouse.valueOr(*PINPUTSCROLLFACTOR); + return m_ruleApplicator->scrollMouse().valueOr(*PINPUTSCROLLFACTOR); } float CWindow::getScrollTouchpad() { static auto PTOUCHPADSCROLLFACTOR = CConfigValue("input:touchpad:scroll_factor"); - return m_windowData.scrollTouchpad.valueOr(*PTOUCHPADSCROLLFACTOR); + return m_ruleApplicator->scrollTouchpad().valueOr(*PTOUCHPADSCROLLFACTOR); } bool CWindow::isScrollMouseOverridden() { - return m_windowData.scrollMouse.hasValue(); + return m_ruleApplicator->scrollMouse().hasValue(); } bool CWindow::isScrollTouchpadOverridden() { - return m_windowData.scrollTouchpad.hasValue(); + return m_ruleApplicator->scrollTouchpad().hasValue(); } bool CWindow::canBeTorn() { static auto PTEARING = CConfigValue("general:allow_tearing"); - return m_windowData.tearing.valueOr(m_tearingHint) && *PTEARING; + return m_ruleApplicator->tearing().valueOr(m_tearingHint) && *PTEARING; } void CWindow::setSuspended(bool suspend) { @@ -1454,7 +1240,8 @@ void CWindow::activate(bool force) { 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))) + if (!force && + (!m_ruleApplicator->focusOnActivate().valueOr(*PFOCUSONACTIVATE) || (m_suppressedEvents & SUPPRESS_ACTIVATE_FOCUSONLY) || (m_suppressedEvents & SUPPRESS_ACTIVATE))) return; if (!m_isMapped) { @@ -1539,8 +1326,7 @@ void CWindow::onUpdateMeta() { } if (doUpdate) { - updateDynamicRules(); - g_pCompositor->updateWindowAnimatedDecorationValues(m_self.lock()); + m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_TITLE | Desktop::Rule::RULE_PROP_CLASS); updateToplevel(); } } @@ -1718,18 +1504,6 @@ PHLWINDOW CWindow::getSwallower() { 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; } @@ -1753,7 +1527,7 @@ Vector2D CWindow::requestedMinSize() { 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())) + if (((m_isX11 && !m_xwaylandSurface->m_sizeHints) || (!m_isX11 && (!m_xdgSurface || !m_xdgSurface->m_toplevel)) || m_ruleApplicator->noMaxSize().valueOrDefault())) return Vector2D(NO_MAX_SIZE_LIMIT, NO_MAX_SIZE_LIMIT); Vector2D maxSize = m_isX11 ? Vector2D(m_xwaylandSurface->m_sizeHints->max_width, m_xwaylandSurface->m_sizeHints->max_height) : m_xdgSurface->m_toplevel->layoutMaxSize(); @@ -1951,3 +1725,127 @@ Vector2D CWindow::getReportedSize() { return m_wlSurface->resource()->m_current.ackedSize; return m_reportedSize; } + +void CWindow::updateDecorationValues() { + static auto PACTIVECOL = CConfigValue("general:col.active_border"); + static auto PINACTIVECOL = CConfigValue("general:col.inactive_border"); + static auto PNOGROUPACTIVECOL = CConfigValue("general:col.nogroup_border_active"); + static auto PNOGROUPINACTIVECOL = CConfigValue("general:col.nogroup_border"); + static auto PGROUPACTIVECOL = CConfigValue("group:col.border_active"); + static auto PGROUPINACTIVECOL = CConfigValue("group:col.border_inactive"); + static auto PGROUPACTIVELOCKEDCOL = CConfigValue("group:col.border_locked_active"); + static auto PGROUPINACTIVELOCKEDCOL = CConfigValue("group:col.border_locked_inactive"); + static auto PINACTIVEALPHA = CConfigValue("decoration:inactive_opacity"); + static auto PACTIVEALPHA = CConfigValue("decoration:active_opacity"); + static auto PFULLSCREENALPHA = CConfigValue("decoration:fullscreen_opacity"); + static auto PSHADOWCOL = CConfigValue("decoration:shadow:color"); + static auto PSHADOWCOLINACTIVE = CConfigValue("decoration:shadow:color_inactive"); + static auto PDIMSTRENGTH = CConfigValue("decoration:dim_strength"); + static auto PDIMENABLED = CConfigValue("decoration:dim_inactive"); + static auto PDIMMODAL = CConfigValue("decoration:dim_modal"); + + auto* const ACTIVECOL = sc((PACTIVECOL.ptr())->getData()); + auto* const INACTIVECOL = sc((PINACTIVECOL.ptr())->getData()); + auto* const NOGROUPACTIVECOL = sc((PNOGROUPACTIVECOL.ptr())->getData()); + auto* const NOGROUPINACTIVECOL = sc((PNOGROUPINACTIVECOL.ptr())->getData()); + auto* const GROUPACTIVECOL = sc((PGROUPACTIVECOL.ptr())->getData()); + auto* const GROUPINACTIVECOL = sc((PGROUPINACTIVECOL.ptr())->getData()); + auto* const GROUPACTIVELOCKEDCOL = sc((PGROUPACTIVELOCKEDCOL.ptr())->getData()); + auto* const GROUPINACTIVELOCKEDCOL = sc((PGROUPINACTIVELOCKEDCOL.ptr())->getData()); + + auto setBorderColor = [&](CGradientValueData grad) -> void { + if (grad == m_realBorderColor) + return; + + m_realBorderColorPrevious = m_realBorderColor; + m_realBorderColor = grad; + m_borderFadeAnimationProgress->setValueAndWarp(0.f); + *m_borderFadeAnimationProgress = 1.f; + }; + + const bool IS_SHADOWED_BY_MODAL = m_xdgSurface && m_xdgSurface->m_toplevel && m_xdgSurface->m_toplevel->anyChildModal(); + + // border + const auto RENDERDATA = g_pLayoutManager->getCurrentLayout()->requestRenderHints(m_self.lock()); + if (RENDERDATA.isBorderGradient) + setBorderColor(*RENDERDATA.borderGradient); + else { + const bool GROUPLOCKED = m_groupData.pNextWindow.lock() ? getGroupHead()->m_groupData.locked : false; + if (m_self == g_pCompositor->m_lastWindow) { + const auto* const ACTIVECOLOR = + !m_groupData.pNextWindow.lock() ? (!m_groupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); + setBorderColor(m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR)); + } else { + const auto* const INACTIVECOLOR = + !m_groupData.pNextWindow.lock() ? (!m_groupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) : (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); + setBorderColor(m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR)); + } + } + + // opacity + const auto PWORKSPACE = m_workspace; + if (isEffectiveInternalFSMode(FSMODE_FULLSCREEN)) { + *m_activeInactiveAlpha = m_ruleApplicator->alphaFullscreen().valueOrDefault().applyAlpha(*PFULLSCREENALPHA); + } else { + if (m_self == g_pCompositor->m_lastWindow) + *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 == g_pCompositor->m_lastWindow.lock() || 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 == g_pCompositor->m_lastWindow) + *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 : g_pCompositor->m_lastMonitor; + const auto CURSOR_LOCAL = g_pInputManager->getMouseCoordsInternal() - (PMONITOR ? PMONITOR->m_position : Vector2D{}); + + Math::CExpression expr; + expr.addVariable("window_w", m_realSize->goal().x); + expr.addVariable("window_h", m_realSize->goal().y); + expr.addVariable("window_x", m_realPosition->goal().x - (PMONITOR ? PMONITOR->m_position.x : 0)); + expr.addVariable("window_y", m_realPosition->goal().y - (PMONITOR ? PMONITOR->m_position.y : 0)); + + expr.addVariable("monitor_w", PMONITOR ? PMONITOR->m_size.x : 1920); + expr.addVariable("monitor_h", PMONITOR ? PMONITOR->m_size.y : 1080); + + expr.addVariable("cursor_x", CURSOR_LOCAL.x); + expr.addVariable("cursor_y", CURSOR_LOCAL.y); + + return expr.compute(s); +} + +std::optional CWindow::calculateExpression(const std::string& s) { + auto spacePos = s.find(' '); + if (spacePos == std::string::npos) + return std::nullopt; + + const auto LHS = calculateSingleExpr(s.substr(0, spacePos)); + const auto RHS = calculateSingleExpr(s.substr(spacePos + 1)); + + if (!LHS || !RHS) + return std::nullopt; + + return Vector2D{*LHS, *RHS}; +} diff --git a/src/desktop/Window.hpp b/src/desktop/Window.hpp index 0a7e207c3..63492682a 100644 --- a/src/desktop/Window.hpp +++ b/src/desktop/Window.hpp @@ -16,20 +16,12 @@ #include "Subsurface.hpp" #include "WLSurface.hpp" #include "Workspace.hpp" -#include "WindowRule.hpp" -#include "WindowOverridableVar.hpp" +#include "rule/windowRule/WindowRuleApplicator.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, @@ -65,65 +57,6 @@ enum eSuppressEvents : uint8_t { 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; @@ -256,7 +189,7 @@ class CWindow { std::vector m_decosToRemove; // Special render data, rules, etc - SWindowData m_windowData; + UP m_ruleApplicator; // Transformers std::vector> m_transformers; @@ -280,15 +213,9 @@ class CWindow { 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 = ""; @@ -303,12 +230,6 @@ class CWindow { bool m_tearingHint = false; - // stores the currently matched window rules - std::vector> m_matchedRules; - - // window tags - CTagKeeper m_tags; - // ANR PHLANIMVAR m_notRespondingTint; @@ -342,8 +263,7 @@ class CWindow { void onMap(); void setHidden(bool hidden); bool isHidden(); - void applyDynamicRule(const SP& r); - void updateDynamicRules(); + void updateDecorationValues(); SBoxExtents getFullWindowReservedArea(); Vector2D middle(); bool opaque(); @@ -397,7 +317,6 @@ class CWindow { std::string fetchClass(); void warpCursor(bool force = false); PHLWINDOW getSwallower(); - void unsetWindowData(eOverridePriority priority); bool isX11OverrideRedirect(); bool isModal(); Vector2D requestedMinSize(); @@ -418,6 +337,7 @@ class CWindow { bool priorityFocus(); SP getSolitaryResource(); Vector2D getReportedSize(); + std::optional calculateExpression(const std::string& s); CBox getWindowMainSurfaceBox() const { return {m_realPosition->value().x, m_realPosition->value().y, m_realSize->value().x, m_realSize->value().y}; @@ -448,6 +368,8 @@ class CWindow { } m_listeners; private: + std::optional calculateSingleExpr(const std::string& s); + // For hidden windows and stuff bool m_hidden = false; bool m_suspended = false; @@ -474,45 +396,6 @@ inline bool validMapped(PHLWINDOWREF w) { 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* diff --git a/src/desktop/WindowOverridableVar.hpp b/src/desktop/WindowOverridableVar.hpp deleted file mode 100644 index ea113d3e6..000000000 --- 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 dc6564ca6..000000000 --- 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 5bf462e95..000000000 --- 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 ee35313a0..7e1dcd5b6 100644 --- a/src/desktop/Workspace.cpp +++ b/src/desktop/Workspace.cpp @@ -542,7 +542,7 @@ void CWorkspace::updateWindows() { if (!w->m_isMapped || w->m_workspace != m_self) continue; - w->updateDynamicRules(); + w->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE); } } diff --git a/src/desktop/rule/Engine.cpp b/src/desktop/rule/Engine.cpp new file mode 100644 index 000000000..3232035d6 --- /dev/null +++ b/src/desktop/rule/Engine.cpp @@ -0,0 +1,56 @@ +#include "Engine.hpp" +#include "Rule.hpp" +#include "../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 000000000..b0ea118e7 --- /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 000000000..fe7271a67 --- /dev/null +++ b/src/desktop/rule/Rule.cpp @@ -0,0 +1,149 @@ +#include "Rule.hpp" +#include "../../debug/Log.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}, // +}; + +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)) { + Debug::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, bool persistent) { + m_execData.isExecRule = true; + m_execData.isExecPersistent = persistent; + m_execData.token = token; + 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 000000000..2b852b3a5 --- /dev/null +++ b/src/desktop/rule/Rule.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include "matchEngine/MatchEngine.hpp" + +#include "../../helpers/memory/Memory.hpp" +#include "../../helpers/time/Time.hpp" +#include +#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_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, 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; + Time::steady_tp expiresAt; + } m_execData; + }; +} \ No newline at end of file diff --git a/src/desktop/rule/effect/EffectContainer.hpp b/src/desktop/rule/effect/EffectContainer.hpp new file mode 100644 index 000000000..cb2157a6b --- /dev/null +++ b/src/desktop/rule/effect/EffectContainer.hpp @@ -0,0 +1,81 @@ +#pragma once + +#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; + }; +}; \ No newline at end of file diff --git a/src/desktop/rule/layerRule/LayerRule.cpp b/src/desktop/rule/layerRule/LayerRule.cpp new file mode 100644 index 000000000..be7576729 --- /dev/null +++ b/src/desktop/rule/layerRule/LayerRule.cpp @@ -0,0 +1,43 @@ +#include "LayerRule.hpp" +#include "../../../debug/Log.hpp" +#include "../../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: { + Debug::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 000000000..990796c12 --- /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 000000000..bb7da97fb --- /dev/null +++ b/src/desktop/rule/layerRule/LayerRuleApplicator.cpp @@ -0,0 +1,128 @@ +#include "LayerRuleApplicator.hpp" +#include "LayerRule.hpp" +#include "../Engine.hpp" +#include "../../LayerSurface.hpp" +#include "../../types/OverridableVar.hpp" +#include "../../../helpers/MiscFunctions.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) +} + +void CLayerRuleApplicator::applyDynamicRule(const SP& rule) { + for (const auto& [key, effect] : rule->effects()) { + switch (key) { + case LAYER_RULE_EFFECT_NONE: { + Debug::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_noScreenShare.first.set(std::stoi(effect), Types::PRIORITY_WINDOW_RULE); + m_noScreenShare.second |= rule->getPropertiesMask(); + } catch (...) { Debug::log(ERR, "CLayerRuleApplicator::applyDynamicRule: invalid order {}", effect); } + break; + } + case LAYER_RULE_EFFECT_ABOVE_LOCK: { + try { + m_aboveLock.first.set(std::clamp(std::stoull(effect), 0ULL, 2ULL), Types::PRIORITY_WINDOW_RULE); + m_aboveLock.second |= rule->getPropertiesMask(); + } catch (...) { Debug::log(ERR, "CLayerRuleApplicator::applyDynamicRule: invalid order {}", effect); } + break; + } + case LAYER_RULE_EFFECT_IGNORE_ALPHA: { + try { + m_ignoreAlpha.first.set(std::clamp(std::stof(effect), 0.F, 1.F), Types::PRIORITY_WINDOW_RULE); + m_ignoreAlpha.second |= rule->getPropertiesMask(); + } catch (...) { Debug::log(ERR, "CLayerRuleApplicator::applyDynamicRule: invalid order {}", effect); } + 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); + } +} diff --git a/src/desktop/rule/layerRule/LayerRuleApplicator.hpp b/src/desktop/rule/layerRule/LayerRuleApplicator.hpp new file mode 100644 index 000000000..97f15b043 --- /dev/null +++ b/src/desktop/rule/layerRule/LayerRuleApplicator.hpp @@ -0,0 +1,60 @@ +#pragma once + +#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); + +#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 000000000..17394239d --- /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 000000000..e3b3d26c4 --- /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 000000000..f5c472278 --- /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 000000000..bd162cdad --- /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 000000000..c5bc87f68 --- /dev/null +++ b/src/desktop/rule/matchEngine/IntMatchEngine.cpp @@ -0,0 +1,14 @@ +#include "IntMatchEngine.hpp" +#include "../../../debug/Log.hpp" + +using namespace Desktop::Rule; + +CIntMatchEngine::CIntMatchEngine(const std::string& s) { + try { + m_value = std::stoi(s); + } catch (...) { Debug::log(ERR, "CIntMatchEngine: invalid input {}", s); } +} + +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 000000000..2eda492c5 --- /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 000000000..0bc89d7fc --- /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 000000000..9588ac059 --- /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 000000000..14e30af19 --- /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 000000000..e980ce709 --- /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 000000000..d669822a8 --- /dev/null +++ b/src/desktop/rule/matchEngine/TagMatchEngine.cpp @@ -0,0 +1,12 @@ +#include "TagMatchEngine.hpp" +#include "../../../helpers/TagKeeper.hpp" + +using namespace Desktop::Rule; + +CTagMatchEngine::CTagMatchEngine(const std::string& tag) : m_tag(tag) { + ; +} + +bool CTagMatchEngine::match(const CTagKeeper& keeper) { + return keeper.isTagged(m_tag); +} \ No newline at end of file diff --git a/src/desktop/rule/matchEngine/TagMatchEngine.hpp b/src/desktop/rule/matchEngine/TagMatchEngine.hpp new file mode 100644 index 000000000..f8ef3e22a --- /dev/null +++ b/src/desktop/rule/matchEngine/TagMatchEngine.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "MatchEngine.hpp" + +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; + }; +} \ No newline at end of file diff --git a/src/desktop/rule/matchEngine/WorkspaceMatchEngine.cpp b/src/desktop/rule/matchEngine/WorkspaceMatchEngine.cpp new file mode 100644 index 000000000..abaa16577 --- /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->matchesStaticSelector(m_value); +} diff --git a/src/desktop/rule/matchEngine/WorkspaceMatchEngine.hpp b/src/desktop/rule/matchEngine/WorkspaceMatchEngine.hpp new file mode 100644 index 000000000..c70bc8b4d --- /dev/null +++ b/src/desktop/rule/matchEngine/WorkspaceMatchEngine.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "MatchEngine.hpp" + +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 = ""; + }; +} \ No newline at end of file diff --git a/src/desktop/rule/utils/SetUtils.hpp b/src/desktop/rule/utils/SetUtils.hpp new file mode 100644 index 000000000..75fd47394 --- /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 000000000..1bcbbc962 --- /dev/null +++ b/src/desktop/rule/windowRule/WindowRule.cpp @@ -0,0 +1,186 @@ +#include "WindowRule.hpp" +#include "../../Window.hpp" +#include "../../../helpers/Monitor.hpp" +#include "../../../Compositor.hpp" +#include "../../../managers/TokenManager.hpp" + +using namespace Desktop; +using namespace Desktop::Rule; + +std::optional Rule::parseRelativeVector(PHLWINDOW w, const std::string& s) { + try { + const auto VALUE = s.substr(s.find(' ') + 1); + const auto SIZEXSTR = VALUE.substr(0, VALUE.find(' ')); + const auto SIZEYSTR = VALUE.substr(VALUE.find(' ') + 1); + + const auto MAXSIZE = w->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); + + return Vector2D{SIZEX, SIZEY}; + + } catch (...) { Debug::log(LOG, "Rule size failed, rule: {}", s); } + + return std::nullopt; +} + +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: { + Debug::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(g_pCompositor->m_lastWindow == w)) + return false; + break; + case RULE_PROP_GROUP: + if (!engine->match(w->m_groupData.pNextWindow)) + 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(NContentType::toString(w->getContentType()))) + return false; + break; + case RULE_PROP_XDG_TAG: + if (!w->xdgTag().has_value() || !engine->match(*w->xdgTag())) + return false; + break; + case RULE_PROP_EXEC_TOKEN: + // this is only allowed on static rules, we don't need it on dynamic plus it's expensive + if (!allowEnvLookup) + break; + + const auto ENV = w->getEnv(); + if (ENV.contains(EXEC_RULE_ENV_NAME)) { + const auto TKN = ENV.at(EXEC_RULE_ENV_NAME); + if (!engine->match(TKN)) + return false; + break; + } + + return false; + } + } + + 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"}); + } + + const auto TOKEN = g_pTokenManager->registerNewToken(nullptr, std::chrono::seconds(1)); + + wr->markAsExecRule(TOKEN, false /* TODO: could be nice. */); + wr->registerMatch(RULE_PROP_EXEC_TOKEN, TOKEN); + + return wr; +} + +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 000000000..944614ce6 --- /dev/null +++ b/src/desktop/rule/windowRule/WindowRule.hpp @@ -0,0 +1,37 @@ +#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"; + + std::optional parseRelativeVector(PHLWINDOW w, const std::string& s); + + 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 000000000..14d7fbf15 --- /dev/null +++ b/src/desktop/rule/windowRule/WindowRuleApplicator.cpp @@ -0,0 +1,646 @@ +#include "WindowRuleApplicator.hpp" +#include "WindowRule.hpp" +#include "../Engine.hpp" +#include "../utils/SetUtils.hpp" +#include "../../Window.hpp" +#include "../../types/OverridableVar.hpp" +#include "../../../managers/LayoutManager.hpp" +#include "../../../managers/HookSystemManager.hpp" + +#include + +using namespace Hyprutils::String; + +using namespace Desktop; +using namespace Desktop::Rule; + +CWindowRuleApplicator::CWindowRuleApplicator(PHLWINDOW w) : m_window(w) { + ; +} + +void CWindowRuleApplicator::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(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; }); + } +} + +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) { + Debug::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: { + Debug::log(ERR, "CWindowRuleApplicator::applyDynamicRule: BUG THIS: WINDOW_RULE_EFFECT_NONE??"); + break; + } + case WINDOW_RULE_EFFECT_ROUNDING: { + try { + m_rounding.first.set(std::stoull(effect), Types::PRIORITY_WINDOW_RULE); + m_rounding.second |= rule->getPropertiesMask(); + } catch (...) { Debug::log(ERR, "CWindowRuleApplicator::applyDynamicRule: invalid rounding {}", effect); } + break; + } + case WINDOW_RULE_EFFECT_ROUNDING_POWER: { + try { + m_roundingPower.first.set(std::clamp(std::stof(effect), 1.F, 10.F), Types::PRIORITY_WINDOW_RULE); + m_roundingPower.second |= rule->getPropertiesMask(); + } catch (...) { Debug::log(ERR, "CWindowRuleApplicator::applyDynamicRule: invalid rounding_power {}", effect); } + break; + } + case WINDOW_RULE_EFFECT_PERSISTENT_SIZE: { + try { + m_persistentSize.first.set(std::stoi(effect), Types::PRIORITY_WINDOW_RULE); + m_persistentSize.second |= rule->getPropertiesMask(); + } catch (...) { Debug::log(ERR, "CWindowRuleApplicator::applyDynamicRule: invalid rounding_power {}", effect); } + 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.substr(effect.find_first_of(' ') + 1)), 0, 's', true); + + // Basic form has only two colors, everything else can be parsed as a gradient + if (colorsAndAngles.size() == 2 && !colorsAndAngles[1].contains("deg")) { + 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); + 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) + Debug::log(WARN, "Bordercolor rule \"{}\" has more than 10 colors in one gradient, ignoring", effect); + else if (activeBorderGradient.m_colors.empty()) + Debug::log(WARN, "Bordercolor rule \"{}\" has no colors, ignoring", effect); + else if (inactiveBorderGradient.m_colors.empty()) + m_activeBorderColor.first = Types::COverridableVar(activeBorderGradient, Types::PRIORITY_WINDOW_RULE); + else { + m_activeBorderColor.first = Types::COverridableVar(activeBorderGradient, Types::PRIORITY_WINDOW_RULE); + m_inactiveBorderColor.first = Types::COverridableVar(inactiveBorderGradient, Types::PRIORITY_WINDOW_RULE); + } + } catch (std::exception& e) { Debug::log(ERR, "BorderColor rule \"{}\" failed with: {}", effect, e.what()); } + 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 + Debug::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) { Debug::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; + + if (!m_window->m_isFloating && !sc(*PCLAMP_TILED)) + break; + + const auto VEC = configStringToVector2D(effect); + if (VEC.x < 1 || VEC.y < 1) { + Debug::log(ERR, "Invalid size for maxsize"); + break; + } + + m_maxSize.first = Types::COverridableVar(VEC, Types::PRIORITY_WINDOW_RULE); + m_window->clampWindowSize(std::nullopt, m_maxSize.first.value()); + + } catch (std::exception& e) { Debug::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; + + if (!m_window->m_isFloating && !sc(*PCLAMP_TILED)) + break; + + const auto VEC = configStringToVector2D(effect); + if (VEC.x < 1 || VEC.y < 1) { + Debug::log(ERR, "Invalid size for maxsize"); + break; + } + + m_minSize.first = Types::COverridableVar(VEC, Types::PRIORITY_WINDOW_RULE); + m_window->clampWindowSize(std::nullopt, m_minSize.first.value()); + } catch (std::exception& e) { Debug::log(ERR, "minsize rule \"{}\" failed with: {}", effect, e.what()); } + 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 (...) { Debug::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 (...) { Debug::log(ERR, "CWindowRuleApplicator::applyDynamicRule: invalid scroll_mouse {}", effect); } + break; + } + case WINDOW_RULE_EFFECT_SCROLL_TOUCHPAD: { + try { + m_scrollTouchpad.first.set(std::clamp(std::stof(effect), 0.01F, 10.F), Types::PRIORITY_WINDOW_RULE); + m_scrollTouchpad.second |= rule->getPropertiesMask(); + } catch (...) { Debug::log(ERR, "CWindowRuleApplicator::applyDynamicRule: invalid scroll_touchpad {}", effect); } + break; + } + } + } + return result; +} + +CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyStaticRule(const SP& rule) { + for (const auto& [key, effect] : rule->effects()) { + switch (key) { + default: { + Debug::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 (...) { Debug::log(ERR, "CWindowRuleApplicator::applyStaticRule: invalid fullscreen state {}", effect); } + break; + } + case WINDOW_RULE_EFFECT_MOVE: { + static_.position = effect; + break; + } + case WINDOW_RULE_EFFECT_SIZE: { + static_.size = effect; + break; + } + case WINDOW_RULE_EFFECT_CENTER: { + 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 (...) { Debug::log(ERR, "CWindowRuleApplicator::applyStaticRule: invalid no close for {}", effect); } + break; + } + } + } + + return SRuleResult{}; +} + +void CWindowRuleApplicator::readStaticRules() { + if (!m_window) + return; + + static_ = {}; + + std::vector> toRemove; + 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; + + applyStaticRule(wr); + const auto RES = applyDynamicRule(wr); // also apply dynamic, because we won't recheck it before layout gets data + tagsWereChanged = tagsWereChanged || RES.tagsChanged; + + if (wr->isExecRule()) + toRemove.emplace_back(wr); + } + + for (const auto& wr : toRemove) { + ruleEngine()->unregisterRule(wr); + } + + // 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); + } + } +} + +void CWindowRuleApplicator::propertiesChanged(std::underlying_type_t props) { + if (!m_window || !m_window->m_isMapped || m_window->isHidden()) + return; + + resetProps(props); + + bool needsRelayout = false; + + std::unordered_set effectsNeedingRecheck; + std::unordered_set> passedWrs; + + for (const auto& r : ruleEngine()->rules()) { + if (r->type() != RULE_TYPE_WINDOW) + continue; + + if (!(r->getPropertiesMask() & props)) + continue; + + auto wr = reinterpretPointerCast(r); + + if (!wr->matches(m_window.lock())) + continue; + + for (const auto& [type, eff] : wr->effects()) { + effectsNeedingRecheck.emplace(type); + } + + passedWrs.emplace(std::move(wr)); + } + + 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 (!std::ranges::contains(passedWrs, WR) && !WR->matches(m_window.lock())) + continue; + + const auto RES = applyDynamicRule(WR); + needsRelayout = needsRelayout || RES.needsRelayout; + } + + m_window->updateDecorationValues(); + + if (needsRelayout) + g_pDecorationPositioner->forceRecalcFor(m_window.lock()); + + // for plugins + EMIT_HOOK_EVENT("windowUpdateRules", m_window.lock()); +} diff --git a/src/desktop/rule/windowRule/WindowRuleApplicator.hpp b/src/desktop/rule/windowRule/WindowRuleApplicator.hpp new file mode 100644 index 000000000..ad80a0818 --- /dev/null +++ b/src/desktop/rule/windowRule/WindowRuleApplicator.hpp @@ -0,0 +1,148 @@ +#pragma once + +#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); + void resetProps(std::underlying_type_t props, Types::eOverridePriority prio = Types::PRIORITY_WINDOW_RULE); + void readStaticRules(); + void applyStaticRules(); + + // static props + struct { + std::string monitor, workspace, group; + + std::optional floating; + + bool fullscreen = false; + bool maximize = false; + bool pseudo = false; + bool pin = false; + bool noInitialFocus = false; + + std::optional fullscreenStateClient; + std::optional fullscreenStateInternal; + std::optional center; + 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) \ + 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(Types::SAlphaValue, alpha, Types::SAlphaValue{}) + DEFINE_PROP(Types::SAlphaValue, alphaInactive, Types::SAlphaValue{}) + DEFINE_PROP(Types::SAlphaValue, alphaFullscreen, Types::SAlphaValue{}) + + DEFINE_PROP(bool, allowsInput, false) + DEFINE_PROP(bool, decorate, true) + DEFINE_PROP(bool, focusOnActivate, false) + DEFINE_PROP(bool, keepAspectRatio, false) + DEFINE_PROP(bool, nearestNeighbor, false) + DEFINE_PROP(bool, noAnim, false) + DEFINE_PROP(bool, noBlur, false) + DEFINE_PROP(bool, noDim, false) + DEFINE_PROP(bool, noFocus, false) + DEFINE_PROP(bool, noMaxSize, false) + DEFINE_PROP(bool, noShadow, false) + DEFINE_PROP(bool, noShortcutsInhibit, false) + DEFINE_PROP(bool, opaque, false) + DEFINE_PROP(bool, dimAround, false) + DEFINE_PROP(bool, RGBX, false) + DEFINE_PROP(bool, syncFullscreen, true) + DEFINE_PROP(bool, tearing, false) + DEFINE_PROP(bool, xray, false) + DEFINE_PROP(bool, renderUnfocused, false) + DEFINE_PROP(bool, noFollowMouse, false) + DEFINE_PROP(bool, noScreenShare, false) + DEFINE_PROP(bool, noVRR, false) + DEFINE_PROP(bool, persistentSize, false) + DEFINE_PROP(bool, stayFocused, false) + + DEFINE_PROP(int, idleInhibitMode, false) + + DEFINE_PROP(Hyprlang::INT, borderSize, {std::string("general:border_size") COMMA sc(0) COMMA std::nullopt}) + DEFINE_PROP(Hyprlang::INT, rounding, {std::string("decoration:rounding") COMMA sc(0) COMMA std::nullopt}) + + DEFINE_PROP(Hyprlang::FLOAT, roundingPower, {std::string("decoration:rounding_power")}) + DEFINE_PROP(Hyprlang::FLOAT, scrollMouse, {std::string("input:scroll_factor")}) + DEFINE_PROP(Hyprlang::FLOAT, scrollTouchpad, {std::string("input:touchpad:scroll_factor")}) + + DEFINE_PROP(std::string, animationStyle, std::string("")) + + DEFINE_PROP(Vector2D, maxSize, Vector2D{}) + DEFINE_PROP(Vector2D, minSize, Vector2D{}) + + DEFINE_PROP(CGradientValueData, activeBorderColor, {}) + DEFINE_PROP(CGradientValueData, inactiveBorderColor, {}) + + 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 000000000..660bf8716 --- /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 000000000..0827d462d --- /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/types/OverridableVar.hpp b/src/desktop/types/OverridableVar.hpp new file mode 100644 index 000000000..9ecfc890c --- /dev/null +++ b/src/desktop/types/OverridableVar.hpp @@ -0,0 +1,153 @@ +#pragma once + +#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, + }; + + 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 (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 set(T value, eOverridePriority priority) { + m_values[priority] = value; + } + + void unset(eOverridePriority priority) { + m_values.erase(priority); + } + + bool hasValue() const { + return !m_values.empty(); + } + + T value() const { + if (!m_values.empty()) + return std::prev(m_values.end())->second; + else + 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 { + 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 + }; + +} \ No newline at end of file diff --git a/src/events/Windows.cpp b/src/events/Windows.cpp index 9e3b49d4d..dfbb9f2e8 100644 --- a/src/events/Windows.cpp +++ b/src/events/Windows.cpp @@ -135,31 +135,23 @@ void Events::listener_mapWindow(void* owner, void* data) { 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(' '))); + PWINDOW->m_ruleApplicator->readStaticRules(); + { + if (!PWINDOW->m_ruleApplicator->static_.monitor.empty()) { + const auto& MONITORSTR = PWINDOW->m_ruleApplicator->static_.monitor; + if (MONITORSTR == "unset") + PWINDOW->m_monitor = PMONITOR; + else { + const auto MONITOR = g_pCompositor->getMonitorFromString(MONITORSTR); - if (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; - } - } + if (MONITOR) { + PWINDOW->m_monitor = MONITOR; const auto PMONITORFROMID = PWINDOW->m_monitor.lock(); @@ -172,100 +164,73 @@ void Events::listener_mapWindow(void* owner, void* data) { 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; + } else + Debug::log(ERR, "No monitor in monitor {} rule", MONITORSTR); } - 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 = ""; + if (!PWINDOW->m_ruleApplicator->static_.workspace.empty()) { + const auto WORKSPACERQ = PWINDOW->m_ruleApplicator->static_.workspace; + + if (WORKSPACERQ == "unset") + requestedWorkspace = ""; + else + requestedWorkspace = WORKSPACERQ; + + const auto JUSTWORKSPACE = WORKSPACERQ.contains(' ') ? WORKSPACERQ.substr(0, WORKSPACERQ.find_first_of(' ')) : WORKSPACERQ; + + if (JUSTWORKSPACE == PWORKSPACE->m_name || JUSTWORKSPACE == "name:" + PWORKSPACE->m_name) + requestedWorkspace = ""; + + Debug::log(LOG, "Rule workspace matched by {}, {} applied.", PWINDOW, PWINDOW->m_ruleApplicator->static_.workspace); + requestedFSMonitor = MONITOR_INVALID; + } + + if (PWINDOW->m_ruleApplicator->static_.floating.has_value()) + PWINDOW->m_isFloating = PWINDOW->m_ruleApplicator->static_.floating.value(); + + if (PWINDOW->m_ruleApplicator->static_.pseudo) + PWINDOW->m_isPseudotiled = true; + + if (PWINDOW->m_ruleApplicator->static_.noInitialFocus) + PWINDOW->m_noInitialFocus = true; + + if (PWINDOW->m_ruleApplicator->static_.fullscreenStateClient || PWINDOW->m_ruleApplicator->static_.fullscreenStateInternal) { + requestedFSState = SFullscreenState{ + .internal = sc(PWINDOW->m_ruleApplicator->static_.fullscreenStateInternal.value_or(0)), + .client = sc(PWINDOW->m_ruleApplicator->static_.fullscreenStateClient.value_or(0)), + }; + } + + if (!PWINDOW->m_ruleApplicator->static_.suppressEvent.empty()) { + for (const auto& var : PWINDOW->m_ruleApplicator->static_.suppressEvent) { + if (var == "fullscreen") + PWINDOW->m_suppressedEvents |= SUPPRESS_FULLSCREEN; + else if (var == "maximize") + PWINDOW->m_suppressedEvents |= SUPPRESS_MAXIMIZE; + else if (var == "activate") + PWINDOW->m_suppressedEvents |= SUPPRESS_ACTIVATE; + else if (var == "activatefocus") + PWINDOW->m_suppressedEvents |= SUPPRESS_ACTIVATE_FOCUSONLY; + else if (var == "fullscreenoutput") + PWINDOW->m_suppressedEvents |= SUPPRESS_FULLSCREEN_OUTPUT; else - requestedWorkspace = WORKSPACERQ; + Debug::log(ERR, "Error while parsing suppressevent windowrule: unknown event type {}", var); + } + } - const auto JUSTWORKSPACE = WORKSPACERQ.contains(' ') ? WORKSPACERQ.substr(0, WORKSPACERQ.find_first_of(' ')) : WORKSPACERQ; + if (PWINDOW->m_ruleApplicator->static_.pin) + PWINDOW->m_pinned = true; - if (JUSTWORKSPACE == PWORKSPACE->m_name || JUSTWORKSPACE == "name:" + PWORKSPACE->m_name) - requestedWorkspace = ""; + if (PWINDOW->m_ruleApplicator->static_.fullscreen) + requestedInternalFSMode = FSMODE_FULLSCREEN; - 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; + if (PWINDOW->m_ruleApplicator->static_.maximize) + requestedInternalFSMode = FSMODE_MAXIMIZED; - // `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'); + if (!PWINDOW->m_ruleApplicator->static_.group.empty()) { + if (!(PWINDOW->m_groupRules & GROUP_OVERRIDE) && trim(PWINDOW->m_ruleApplicator->static_.group) != "group") { + CVarList2 vars(std::string{PWINDOW->m_ruleApplicator->static_.group}, 0, 's'); std::string vPrev = ""; for (auto const& v : vars) { @@ -302,26 +267,14 @@ void Events::listener_mapWindow(void* owner, void* data) { } 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); + if (PWINDOW->m_ruleApplicator->static_.content) + PWINDOW->setContentType(sc(PWINDOW->m_ruleApplicator->static_.content.value())); + + if (PWINDOW->m_ruleApplicator->static_.noCloseFor) + PWINDOW->m_closeableSince = Time::steadyNow() + std::chrono::milliseconds(PWINDOW->m_ruleApplicator->static_.noCloseFor.value()); } // make it uncloseable if it's a Hyprland dialog @@ -333,7 +286,7 @@ void Events::listener_mapWindow(void* owner, void* data) { if (PWINDOW->m_pinned && !PWINDOW->m_isFloating) PWINDOW->m_pinned = false; - CVarList WORKSPACEARGS = CVarList(requestedWorkspace, 0, ' '); + CVarList2 WORKSPACEARGS = CVarList2(std::move(requestedWorkspace), 0, ' ', false, false); if (!WORKSPACEARGS[0].empty()) { WORKSPACEID requestedWorkspaceID; @@ -420,140 +373,31 @@ void Events::listener_mapWindow(void* owner, void* data) { 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%-h-"); - 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; + if (!PWINDOW->m_ruleApplicator->static_.size.empty()) { + const auto COMPUTED = PWINDOW->calculateExpression(PWINDOW->m_ruleApplicator->static_.size); + if (!COMPUTED) + Debug::log(ERR, "failed to parse {} as an expression", PWINDOW->m_ruleApplicator->static_.size); + else { + *PWINDOW->m_realSize = *COMPUTED; + PWINDOW->setHidden(false); } } + if (!PWINDOW->m_ruleApplicator->static_.position.empty()) { + const auto COMPUTED = PWINDOW->calculateExpression(PWINDOW->m_ruleApplicator->static_.position); + if (!COMPUTED) + Debug::log(ERR, "failed to parse {} as an expression", PWINDOW->m_ruleApplicator->static_.position); + else { + *PWINDOW->m_realPosition = *COMPUTED + PMONITOR->m_position; + PWINDOW->setHidden(false); + } + } + + if (PWINDOW->m_ruleApplicator->static_.center) { + auto RESERVEDOFFSET = (PMONITOR->m_reservedTopLeft - PMONITOR->m_reservedBottomRight) / 2.f; + *PWINDOW->m_realPosition = PMONITOR->middle() - PWINDOW->m_realSize->goal() / 2.f + RESERVEDOFFSET; + } + // set the pseudo size to the GOAL of our current size // because the windows are animated on RealSize PWINDOW->m_pseudoSize = PWINDOW->m_realSize->goal(); @@ -564,28 +408,15 @@ void Events::listener_mapWindow(void* owner, void* data) { 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); - + if (!PWINDOW->m_ruleApplicator->static_.size.empty()) { + const auto COMPUTED = PWINDOW->calculateExpression(PWINDOW->m_ruleApplicator->static_.size); + if (!COMPUTED) + Debug::log(ERR, "failed to parse {} as an expression", PWINDOW->m_ruleApplicator->static_.size); + else { setPseudo = true; - PWINDOW->m_pseudoSize = Vector2D(SIZEX, SIZEY); - + PWINDOW->m_pseudoSize = *COMPUTED; PWINDOW->setHidden(false); - } catch (...) { Debug::log(LOG, "Rule size failed, rule: {} -> {}", r->m_rule, r->m_value); } + } } if (!setPseudo) @@ -594,10 +425,10 @@ void Events::listener_mapWindow(void* owner, void* data) { 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; + if (PWINDOW->m_ruleApplicator->allowsInput().valueOrDefault()) { // if default value wasn't set to false getPriority() would throw an exception + PWINDOW->m_ruleApplicator->noFocusOverride(Desktop::Types::COverridableVar(false, PWINDOW->m_ruleApplicator->allowsInput().getPriority())); + PWINDOW->m_noInitialFocus = false; + PWINDOW->m_X11ShouldntFocus = false; } // check LS focus grab @@ -615,12 +446,12 @@ void Events::listener_mapWindow(void* owner, void* data) { g_pCompositor->setWindowFullscreenInternal(PWINDOW->m_workspace->getFullscreenWindow(), FSMODE_NONE); } - if (!PWINDOW->m_windowData.noFocus.valueOrDefault() && !PWINDOW->m_noInitialFocus && + if (!PWINDOW->m_ruleApplicator->noFocus().valueOrDefault() && !PWINDOW->m_noInitialFocus && (!PWINDOW->isX11OverrideRedirect() || (PWINDOW->m_isX11 && PWINDOW->m_xwaylandSurface->wantsFocus())) && !workspaceSilent && (!PFORCEFOCUS || PFORCEFOCUS == PWINDOW) && !g_pInputManager->isConstrained()) { g_pCompositor->focusWindow(PWINDOW); PWINDOW->m_activeInactiveAlpha->setValueAndWarp(*PACTIVEALPHA); - PWINDOW->m_dimPercent->setValueAndWarp(PWINDOW->m_windowData.noDim.valueOrDefault() ? 0.f : *PDIMSTRENGTH); + PWINDOW->m_dimPercent->setValueAndWarp(PWINDOW->m_ruleApplicator->noDim().valueOrDefault() ? 0.f : *PDIMSTRENGTH); } else { PWINDOW->m_activeInactiveAlpha->setValueAndWarp(*PINACTIVEALPHA); PWINDOW->m_dimPercent->setValueAndWarp(0); @@ -639,9 +470,9 @@ void Events::listener_mapWindow(void* owner, void* data) { PWINDOW->m_realPosition->warp(); PWINDOW->m_realSize->warp(); if (requestedFSState.has_value()) { - PWINDOW->m_windowData.syncFullscreen = CWindowOverridableVar(false, PRIORITY_WINDOW_RULE); + PWINDOW->m_ruleApplicator->syncFullscreenOverride(Desktop::Types::COverridableVar(false, Desktop::Types::PRIORITY_WINDOW_RULE)); g_pCompositor->setWindowFullscreenState(PWINDOW, requestedFSState.value()); - } else if (requestedInternalFSMode.has_value() && requestedClientFSMode.has_value() && !PWINDOW->m_windowData.syncFullscreen.valueOrDefault()) + } else if (requestedInternalFSMode.has_value() && requestedClientFSMode.has_value() && !PWINDOW->m_ruleApplicator->syncFullscreen().valueOrDefault()) g_pCompositor->setWindowFullscreenState(PWINDOW, SFullscreenState{.internal = requestedInternalFSMode.value(), .client = requestedClientFSMode.value()}); else if (requestedInternalFSMode.has_value()) g_pCompositor->setWindowFullscreenInternal(PWINDOW, requestedInternalFSMode.value()); @@ -653,6 +484,7 @@ void Events::listener_mapWindow(void* owner, void* data) { g_pInputManager->recheckIdleInhibitorStatus(); PWINDOW->updateToplevel(); + PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ALL); if (workspaceSilent) { if (validMapped(PFOCUSEDWINDOWPREV)) { @@ -689,7 +521,7 @@ void Events::listener_mapWindow(void* owner, void* data) { PWINDOW->m_realSize->setCallbackOnEnd(setVector2DAnimToMove); // recalc the values for this window - g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW); + PWINDOW->updateDecorationValues(); // avoid this window being visible if (PWORKSPACE->m_hasFullscreenWindow && !PWINDOW->isFullscreen() && !PWINDOW->m_isFloating) PWINDOW->m_alpha->setValueAndWarp(0.f); @@ -736,8 +568,7 @@ void Events::listener_unmapWindow(void* owner, void* data) { 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; })) { + if (PWINDOW->m_isFloating && !PWINDOW->m_isX11 && PWINDOW->m_ruleApplicator->persistentSize().valueOrDefault()) { Debug::log(LOG, "storing floating size {}x{} for window {}::{} on close", PWINDOW->m_realSize->value().x, PWINDOW->m_realSize->value().y, PWINDOW->m_class, PWINDOW->m_title); g_pConfigManager->storeFloatingSize(PWINDOW, PWINDOW->m_realSize->value()); diff --git a/src/helpers/MiscFunctions.cpp b/src/helpers/MiscFunctions.cpp index 4cf3c6716..d11e1be68 100644 --- a/src/helpers/MiscFunctions.cpp +++ b/src/helpers/MiscFunctions.cpp @@ -975,3 +975,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 5feb2de97..183b6face 100644 --- a/src/helpers/MiscFunctions.hpp +++ b/src/helpers/MiscFunctions.hpp @@ -46,6 +46,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 74dd995b0..ce9c69900 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -27,6 +27,7 @@ #include "../managers/animation/DesktopAnimationManager.hpp" #include "../managers/input/InputManager.hpp" #include "../hyprerror/HyprError.hpp" +#include "../i18n/Engine.hpp" #include "sync/SyncTimeline.hpp" #include "time/Time.hpp" #include "../desktop/LayerSurface.hpp" @@ -176,7 +177,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); }); @@ -811,8 +816,8 @@ 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); + auto errorMessage = I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_MONITOR_MODE_FAIL, + {{"name", m_name}, {"mode", std::format("{:X0}@{:.2f}Hz", mode->pixelSize, mode->refreshRate / 1000.f)}}); Debug::log(WARN, errorMessage); g_pHyprNotificationOverlay->addNotification(errorMessage, CHyprColor(0xff0000ff), 5000, ICON_WARNING); @@ -939,8 +944,10 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { Debug::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; } @@ -1509,6 +1516,10 @@ CBox CMonitor::logicalBox() { return {m_position, m_size}; } +CBox CMonitor::logicalBoxMinusExtents() { + return {m_position + m_reservedTopLeft, m_size - m_reservedTopLeft - m_reservedBottomRight}; +} + void CMonitor::scheduleDone() { if (m_doneScheduled) return; diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 5be6a7ebc..f1f466698 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -298,6 +298,7 @@ class CMonitor { WORKSPACEID activeWorkspaceID(); WORKSPACEID activeSpecialWorkspaceID(); CBox logicalBox(); + CBox logicalBoxMinusExtents(); void scheduleDone(); uint32_t isSolitaryBlocked(bool full = false); void recheckSolitary(); diff --git a/src/helpers/TagKeeper.cpp b/src/helpers/TagKeeper.cpp index 3c7071d54..7f377657e 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 f47320050..d18a0d29a 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/math/Expression.cpp b/src/helpers/math/Expression.cpp new file mode 100644 index 000000000..fb28628dc --- /dev/null +++ b/src/helpers/math/Expression.cpp @@ -0,0 +1,22 @@ +#include "Expression.hpp" +#include "muParser.h" +#include "../../debug/Log.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) { Debug::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 000000000..1780e3eeb --- /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/varlist/VarList.hpp b/src/helpers/varlist/VarList.hpp index 4cdc17288..ca68751e6 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/i18n/Engine.cpp b/src/i18n/Engine.cpp new file mode 100644 index 000000000..46d4c1aff --- /dev/null +++ b/src/i18n/Engine.cpp @@ -0,0 +1,988 @@ +#include "Engine.hpp" + +#include + +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-біт."); + + // 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_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."); + + // 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, "Una aplicación {title} - {class} no responde.\n¿Qué quieres hacer?"); + huEngine->registerEntry("es", TXT_KEY_ANR_OPTION_TERMINATE, "Terminar"); + 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¿Quieres permitirlo?"); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Una aplicación {app} está intentando cargar un plugin: {plugin}.\n\n¿Quieres permitirlo?"); + huEngine->registerEntry("es", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Se ha detectado un nuevo teclado: {keyboard}.\n\n¿Quieres permitir su funcionamiento?"); + 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 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 (wayland client ID {wayland_id})"); + + huEngine->registerEntry("es", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "La variable de entorno XDG_CURRENT_DESKTOP parece estar gestionada externamente, y el valor actual es {value}.\nEsto podría causar problemas, a menos " + "que sea intencionado."); + huEngine->registerEntry( + "es", TXT_KEY_NOTIF_NO_GUIUTILS, + "Tu sistema no tiene instalado hyprland-guiutils. Se trata de una dependencia de tiempo de ejecución para algunos diálogos. Considera la posibilidad de 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 ha podido cargar {count} recurso clave, ¡culpa a tu empaquetador por hacer un mal trabajo!"; + return "No se ha podido cargar {count} recursos clave, ¡culpa a tu empaquetador por hacer un mal trabajo!"; + }); + huEngine->registerEntry( + "es", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "La configuración de su monitor no es correcta. El monitor {name} se superpone con otros monitores en la configuración. Consulte la wiki (página Monitors, en inglés) " + "para obtener más información. Esto provocará problemas."); + huEngine->registerEntry("es", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, + "El monitor {name} no ha podido configurar ninguno de los modos solicitados, por lo que ha recurrido al modo {mode}."); + huEngine->registerEntry("es", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Escala no válida pasada al monitor {name}: {scale}, utilizando 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 sombreador CM, recurriendo a rgba/rgbx."); + huEngine->registerEntry("es", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: la gama de colores amplia está habilitada, pero la pantalla no está en modo de 10-bit."); + + // 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}: گسترهٔ رنگ وسیع فعال است اما نمایشگر در حالت ۱۰ بیتی نیست."); + + // 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'y autoriser?"); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Une application {app} tente de charger un module : {plugin}.\n\nVoulez-vous l'y autoriser?"); + huEngine->registerEntry("fr_FR", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Un nouveau clavier a été détecté : {keyboard}.\n\nVouslez-vous l'autoriser à fonctioner?"); + 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, + "Vous 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 aucun des 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-बिट मोड में नहीं है।"); + + // 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\nGlie lo vuoi permettere?"); + huEngine->registerEntry("it_IT", TXT_KEY_PERMISSION_REQUEST_PLUGIN, + "Un'applicazione {app} sta provando a caricare un plugin: {plugin}.\n\nGlie lo vuoi permettere?"); + 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ウィキのモニターページで詳細を確認してください。これは絶対に問題になります。"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "画面{name}は設定したモードを正常に受け入れませんでした。{mode}を使いました。"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "画面{name}のスケールは無効:{scale}、代わりにおすすめのスケール{fixed_scale}を使いました。"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "プラグイン{name}のロード失敗: {error}"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CMシェーダーのリロード失敗、rgba/rgbxを使いました。"); + huEngine->registerEntry("ja_JP", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "画面{name}:広い色域は設定していますけど、画面は10ビットモードに設定されていません。"); + + // 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."); + + // 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."); + + // 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模式。"); + + // 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 بت."); + + // 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 режиме."); + + // 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."); +} + +std::string I18n::CI18nEngine::localize(eI18nKeys key, const Hyprutils::I18n::translationVarMap& vars) { + return huEngine->localizeEntry(localeStr, key, vars); +} diff --git a/src/i18n/Engine.hpp b/src/i18n/Engine.hpp new file mode 100644 index 000000000..d1182632a --- /dev/null +++ b/src/i18n/Engine.hpp @@ -0,0 +1,50 @@ +#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_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, + }; + + class CI18nEngine { + public: + CI18nEngine(); + ~CI18nEngine() = default; + + std::string localize(eI18nKeys key, const std::unordered_map& vars = {}); + }; + + SP i18nEngine(); +}; \ No newline at end of file diff --git a/src/layout/DwindleLayout.cpp b/src/layout/DwindleLayout.cpp index e46a09633..4925a50e6 100644 --- a/src/layout/DwindleLayout.cpp +++ b/src/layout/DwindleLayout.cpp @@ -9,51 +9,14 @@ #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; + splitTop = box.h * *PFLMULT > box.w; if (verticalOverride) splitTop = true; @@ -64,28 +27,14 @@ void SDwindleNodeData::recalcSizePosRecursive(bool force, bool horizontalOverrid 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(); + 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 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(); + 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); @@ -167,7 +116,10 @@ void CHyprDwindleLayout::applyNodeDataToWindow(SDwindleNodeData* pNode, bool for } // for gaps outer - const auto edges = getNodeDisplayEdgeFlags(pNode->box, PMONITOR); + const bool DISPLAYLEFT = STICKS(pNode->box.x, PMONITOR->m_position.x + PMONITOR->m_reservedTopLeft.x); + const bool DISPLAYRIGHT = STICKS(pNode->box.x + pNode->box.w, PMONITOR->m_position.x + PMONITOR->m_size.x - PMONITOR->m_reservedBottomRight.x); + const bool DISPLAYTOP = STICKS(pNode->box.y, PMONITOR->m_position.y + PMONITOR->m_reservedTopLeft.y); + const bool DISPLAYBOTTOM = STICKS(pNode->box.y + pNode->box.h, PMONITOR->m_position.y + PMONITOR->m_size.y - PMONITOR->m_reservedBottomRight.y); const auto PWINDOW = pNode->pWindow.lock(); // get specific gaps and rules for this workspace, @@ -183,7 +135,7 @@ void CHyprDwindleLayout::applyNodeDataToWindow(SDwindleNodeData* pNode, bool for if (PWINDOW->isFullscreen() && !pNode->ignoreFullscreenChecks) return; - PWINDOW->unsetWindowData(PRIORITY_LAYOUT); + PWINDOW->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); PWINDOW->updateWindowData(); static auto PGAPSINDATA = CConfigValue("general:gaps_in"); @@ -228,9 +180,9 @@ void CHyprDwindleLayout::applyNodeDataToWindow(SDwindleNodeData* pNode, bool for } } - const auto GAPOFFSETTOPLEFT = Vector2D(sc(edges.left ? gapsOut.m_left : gapsIn.m_left), sc(edges.top ? gapsOut.m_top : gapsIn.m_top)); + const auto GAPOFFSETTOPLEFT = Vector2D(sc(DISPLAYLEFT ? gapsOut.m_left : gapsIn.m_left), sc(DISPLAYTOP ? gapsOut.m_top : gapsIn.m_top)); - const auto GAPOFFSETBOTTOMRIGHT = Vector2D(sc(edges.right ? gapsOut.m_right : gapsIn.m_right), sc(edges.bottom ? gapsOut.m_bottom : gapsIn.m_bottom)); + const auto GAPOFFSETBOTTOMRIGHT = Vector2D(sc(DISPLAYRIGHT ? gapsOut.m_right : gapsIn.m_right), sc(DISPLAYBOTTOM ? gapsOut.m_bottom : gapsIn.m_bottom)); calcPos = calcPos + GAPOFFSETTOPLEFT + ratioPadding / 2; calcSize = calcSize - GAPOFFSETTOPLEFT - GAPOFFSETBOTTOMRIGHT - ratioPadding; @@ -272,10 +224,10 @@ void CHyprDwindleLayout::applyNodeDataToWindow(SDwindleNodeData* pNode, bool for 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); + Vector2D minSize = PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}).clamp(Vector2D{0, 0}, monitorAvailable); + Vector2D maxSize = PWINDOW->isFullscreen() ? Vector2D{INFINITY, INFINITY} : + PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}).clamp(Vector2D{0, 0}, monitorAvailable); + calcSize = calcSize.clamp(minSize, maxSize); calcPos += (availableSpace - calcSize) / 2.0; @@ -398,6 +350,7 @@ void CHyprDwindleLayout::onWindowCreatedTiling(PHLWINDOW pWindow, eDirection dir } // get the node under our cursor + m_dwindleNodesData.emplace_back(); const auto NEWPARENT = &m_dwindleNodesData.back(); @@ -410,17 +363,8 @@ void CHyprDwindleLayout::onWindowCreatedTiling(PHLWINDOW pWindow, eDirection dir 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; + const auto SIDEBYSIDE = NEWPARENT->box.w > NEWPARENT->box.h * *PWIDTHMULTIPLIER; NEWPARENT->splitTop = !SIDEBYSIDE; static auto PFORCESPLIT = CConfigValue("dwindle:force_split"); @@ -547,7 +491,7 @@ void CHyprDwindleLayout::onWindowRemovedTiling(PHLWINDOW pWindow) { return; } - pWindow->unsetWindowData(PRIORITY_LAYOUT); + pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); pWindow->updateWindowData(); if (pWindow->isFullscreen()) @@ -601,6 +545,8 @@ void CHyprDwindleLayout::recalculateMonitor(const MONITORID& monid) { #ifndef NO_XWAYLAND CBox box = g_pCompositor->calculateX11WorkArea(); + if (!g_pXWayland || !g_pXWayland->m_wm) + return; g_pXWayland->m_wm->updateWorkArea(box.x, box.y, box.w, box.h); #endif } @@ -662,9 +608,9 @@ void CHyprDwindleLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorn 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->m_realSize = (PWINDOW->m_realSize->goal() + pixResize) + .clamp(PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}), + PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY})); PWINDOW->updateWindowDecos(); return; } @@ -673,8 +619,11 @@ void CHyprDwindleLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorn 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); + const auto PMONITOR = PWINDOW->m_monitor.lock(); + const bool DISPLAYLEFT = STICKS(PWINDOW->m_position.x, PMONITOR->m_position.x + PMONITOR->m_reservedTopLeft.x); + const bool DISPLAYRIGHT = STICKS(PWINDOW->m_position.x + PWINDOW->m_size.x, PMONITOR->m_position.x + PMONITOR->m_size.x - PMONITOR->m_reservedBottomRight.x); + const bool DISPLAYTOP = STICKS(PWINDOW->m_position.y, PMONITOR->m_position.y + PMONITOR->m_reservedTopLeft.y); + const bool DISPLAYBOTTOM = STICKS(PWINDOW->m_position.y + PWINDOW->m_size.y, PMONITOR->m_position.y + PMONITOR->m_size.y - PMONITOR->m_reservedBottomRight.y); if (PWINDOW->m_isPseudotiled) { if (!m_pseudoDragFlags.started) { @@ -707,8 +656,8 @@ void CHyprDwindleLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorn 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 minSize = PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{30.0, 30.0}); + Vector2D maxSize = PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}); Vector2D upperBound = Vector2D{std::min(maxSize.x, wbox.w), std::min(maxSize.y, wbox.h)}; PWINDOW->m_pseudoSize = PWINDOW->m_pseudoSize.clamp(minSize, upperBound); @@ -722,10 +671,10 @@ void CHyprDwindleLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorn // construct allowed movement Vector2D allowedMovement = pixResize; - if (edges.left && edges.right) + if (DISPLAYLEFT && DISPLAYRIGHT) allowedMovement.x = 0; - if (edges.bottom && edges.top) + if (DISPLAYBOTTOM && DISPLAYTOP) allowedMovement.y = 0; if (*PSMARTRESIZING == 1) { @@ -735,10 +684,10 @@ void CHyprDwindleLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorn 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 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) { @@ -868,7 +817,7 @@ void CHyprDwindleLayout::fullscreenRequestForWindow(PHLWINDOW pWindow, const eFu *pWindow->m_realPosition = pWindow->m_lastFloatingPosition; *pWindow->m_realSize = pWindow->m_lastFloatingSize; - pWindow->unsetWindowData(PRIORITY_LAYOUT); + pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); pWindow->updateWindowData(); } } else { diff --git a/src/layout/DwindleLayout.hpp b/src/layout/DwindleLayout.hpp index de80beedd..23f19956a 100644 --- a/src/layout/DwindleLayout.hpp +++ b/src/layout/DwindleLayout.hpp @@ -1,7 +1,6 @@ #pragma once #include "IHyprLayout.hpp" -#include "../config/ConfigDataValues.hpp" #include "../desktop/DesktopTypes.hpp" #include @@ -13,15 +12,6 @@ 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; @@ -75,9 +65,6 @@ class CHyprDwindleLayout : public IHyprLayout { virtual void onDisable(); private: - SWorkspaceGaps getWorkspaceGaps(const PHLWORKSPACE& pWorkspace); - SNodeDisplayEdgeFlags getNodeDisplayEdgeFlags(const CBox& box, const PHLMONITOR& monitor); - std::list m_dwindleNodesData; struct { diff --git a/src/layout/IHyprLayout.cpp b/src/layout/IHyprLayout.cpp index 388684244..85b401bd0 100644 --- a/src/layout/IHyprLayout.cpp +++ b/src/layout/IHyprLayout.cpp @@ -13,11 +13,12 @@ #include "../managers/EventManager.hpp" #include "../managers/HookSystemManager.hpp" #include "../managers/cursor/CursorShapeOverrideController.hpp" +#include "../desktop/rule/windowRule/WindowRule.hpp" void IHyprLayout::onWindowCreated(PHLWINDOW pWindow, eDirection direction) { CBox desiredGeometry = g_pXWaylandManager->getGeometryForWindow(pWindow); - const bool HASPERSISTENTSIZE = std::ranges::any_of(pWindow->m_matchedRules, [](const auto& rule) { return rule->m_ruleType == CWindowRule::RULE_PERSISTENTSIZE; }); + const bool HASPERSISTENTSIZE = pWindow->m_ruleApplicator->persistentSize().valueOrDefault(); const auto STOREDSIZE = HASPERSISTENTSIZE ? g_pConfigManager->getStoredFloatingSize(pWindow) : std::nullopt; @@ -77,7 +78,7 @@ void IHyprLayout::onWindowRemoved(PHLWINDOW pWindow) { pWindow->updateWindowDecos(); PWINDOWPREV->getGroupCurrent()->updateWindowDecos(); - g_pCompositor->updateWindowAnimatedDecorationValues(pWindow); + pWindow->updateDecorationValues(); return; } @@ -637,10 +638,10 @@ void IHyprLayout::onMouseMove(const Vector2D& mousePos) { } 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 MINSIZE = DRAGGINGWINDOW->requestedMinSize().clamp(DRAGGINGWINDOW->m_ruleApplicator->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()); + if (DRAGGINGWINDOW->m_ruleApplicator->maxSize().hasValue()) + MAXSIZE = DRAGGINGWINDOW->requestedMaxSize().clamp({}, DRAGGINGWINDOW->m_ruleApplicator->maxSize().value()); else MAXSIZE = DRAGGINGWINDOW->requestedMaxSize().clamp({}, Vector2D(std::numeric_limits::max(), std::numeric_limits::max())); @@ -657,7 +658,7 @@ void IHyprLayout::onMouseMove(const Vector2D& mousePos) { newSize = newSize + Vector2D(-DELTA.x, DELTA.y); eMouseBindMode mode = g_pInputManager->m_dragMode; - if (DRAGGINGWINDOW->m_windowData.keepAspectRatio.valueOrDefault() && mode != MBIND_RESIZE_BLOCK_RATIO) + if (DRAGGINGWINDOW->m_ruleApplicator->keepAspectRatio().valueOrDefault() && mode != MBIND_RESIZE_BLOCK_RATIO) mode = MBIND_RESIZE_FORCE_RATIO; if (m_beginDragSizeXY.x >= 1 && m_beginDragSizeXY.y >= 1 && mode == MBIND_RESIZE_FORCE_RATIO) { @@ -803,14 +804,15 @@ void IHyprLayout::changeWindowFloatingMode(PHLWINDOW pWindow) { g_pHyprRenderer->damageMonitor(pWindow->m_monitor.lock()); - pWindow->unsetWindowData(PRIORITY_LAYOUT); + pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); pWindow->updateWindowData(); if (pWindow == m_lastTiledWindow) m_lastTiledWindow.reset(); } - g_pCompositor->updateWindowAnimatedDecorationValues(pWindow); + pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE | Desktop::Rule::RULE_PROP_FLOATING); + pWindow->updateDecorationValues(); pWindow->updateToplevel(); g_pHyprRenderer->damageWindow(pWindow); } @@ -826,19 +828,20 @@ void IHyprLayout::fitFloatingWindowOnMonitor(PHLWINDOW w, std::optional tb const auto EXTENTS = w->getWindowExtentsUnified(RESERVED_EXTENTS | INPUT_EXTENTS); CBox targetBoxMonLocal = tb.value_or(w->getWindowMainSurfaceBox()).translate(-PMONITOR->m_position).addExtents(EXTENTS); + const auto MONITOR_LOCAL_BOX = PMONITOR->logicalBoxMinusExtents().translate(-PMONITOR->m_position); - if (targetBoxMonLocal.w < 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.w < MONITOR_LOCAL_BOX.w) { + if (targetBoxMonLocal.x < MONITOR_LOCAL_BOX.x) + targetBoxMonLocal.x = MONITOR_LOCAL_BOX.x; + else if (targetBoxMonLocal.x + targetBoxMonLocal.w > MONITOR_LOCAL_BOX.w) + targetBoxMonLocal.x = MONITOR_LOCAL_BOX.w - targetBoxMonLocal.w; } - if (targetBoxMonLocal.h < 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; + if (targetBoxMonLocal.h < MONITOR_LOCAL_BOX.h) { + if (targetBoxMonLocal.y < MONITOR_LOCAL_BOX.y) + targetBoxMonLocal.y = MONITOR_LOCAL_BOX.y; + else if (targetBoxMonLocal.y + targetBoxMonLocal.h > MONITOR_LOCAL_BOX.h) + targetBoxMonLocal.y = MONITOR_LOCAL_BOX.h - targetBoxMonLocal.h; } *w->m_realPosition = (targetBoxMonLocal.pos() + PMONITOR->m_position + EXTENTS.topLeft).round(); @@ -884,7 +887,7 @@ PHLWINDOW IHyprLayout::getNextWindowCandidate(PHLWINDOW pWindow) { // 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) { + !w->m_ruleApplicator->noFocus().valueOrDefault() && w != pWindow) { if (VECINRECT((pWindow->m_size / 2.f + pWindow->m_position), w->m_position.x, w->m_position.y, w->m_position.x + w->m_size.x, w->m_position.y + w->m_size.y)) { return w; } @@ -903,7 +906,7 @@ PHLWINDOW IHyprLayout::getNextWindowCandidate(PHLWINDOW pWindow) { // 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) + !w->m_ruleApplicator->noFocus().valueOrDefault() && w != pWindow) return w; } @@ -952,7 +955,7 @@ Vector2D IHyprLayout::predictSizeForNewWindowFloating(PHLWINDOW pWindow) { // ge 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 bool HASPERSISTENTSIZE = pWindow->m_ruleApplicator->persistentSize().valueOrDefault(); const auto STOREDSIZE = HASPERSISTENTSIZE ? g_pConfigManager->getStoredFloatingSize(pWindow) : std::nullopt; @@ -961,27 +964,10 @@ Vector2D IHyprLayout::predictSizeForNewWindowFloating(PHLWINDOW pWindow) { // ge 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; + if (!pWindow->m_ruleApplicator->static_.size.empty()) { + const auto SIZE = Desktop::Rule::parseRelativeVector(pWindow, pWindow->m_ruleApplicator->static_.size); + if (SIZE) + return SIZE.value(); } } @@ -989,17 +975,7 @@ Vector2D IHyprLayout::predictSizeForNewWindowFloating(PHLWINDOW pWindow) { // ge } 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; - } - } + bool shouldBeFloated = g_pXWaylandManager->shouldBeFloated(pWindow, true) || pWindow->m_ruleApplicator->static_.floating.value_or(false); Vector2D sizePredicted = {}; @@ -1042,7 +1018,7 @@ bool IHyprLayout::updateDragWindow() { 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))); + Vector2D MINSIZE = DRAGGINGWINDOW->requestedMinSize().clamp(DRAGGINGWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D(MIN_WINDOW_SIZE, MIN_WINDOW_SIZE))); DRAGGINGWINDOW->m_lastFloatingSize = (DRAGGINGWINDOW->m_realSize->goal() * 0.8489).clamp(MINSIZE, Vector2D{}).floor(); *DRAGGINGWINDOW->m_realPosition = g_pInputManager->getMouseCoordsInternal() - DRAGGINGWINDOW->m_realSize->goal() / 2.f; if (g_pInputManager->m_dragThresholdReached) { diff --git a/src/layout/MasterLayout.cpp b/src/layout/MasterLayout.cpp index b40c339ac..5b2284c5f 100644 --- a/src/layout/MasterLayout.cpp +++ b/src/layout/MasterLayout.cpp @@ -242,7 +242,7 @@ void CHyprMasterLayout::onWindowRemovedTiling(PHLWINDOW pWindow) { const auto MASTERSLEFT = getMastersOnWorkspace(WORKSPACEID); static auto SMALLSPLIT = CConfigValue("master:allow_small_split"); - pWindow->unsetWindowData(PRIORITY_LAYOUT); + pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); pWindow->updateWindowData(); if (pWindow->isFullscreen()) @@ -297,6 +297,8 @@ void CHyprMasterLayout::recalculateMonitor(const MONITORID& monid) { #ifndef NO_XWAYLAND CBox box = g_pCompositor->calculateX11WorkArea(); + if (!g_pXWayland || !g_pXWayland->m_wm) + return; g_pXWayland->m_wm->updateWorkArea(box.x, box.y, box.w, box.h); #endif } @@ -661,7 +663,7 @@ void CHyprMasterLayout::applyNodeDataToWindow(SMasterNodeData* pNode) { if (PWINDOW->isFullscreen() && !pNode->ignoreFullscreenChecks) return; - PWINDOW->unsetWindowData(PRIORITY_LAYOUT); + PWINDOW->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); PWINDOW->updateWindowData(); static auto PANIMATE = CConfigValue("misc:animate_manual_resizes"); @@ -706,10 +708,10 @@ void CHyprMasterLayout::applyNodeDataToWindow(SMasterNodeData* pNode) { 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); + Vector2D minSize = PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}).clamp(Vector2D{0, 0}, monitorAvailable); + Vector2D maxSize = PWINDOW->isFullscreen() ? Vector2D{INFINITY, INFINITY} : + PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}).clamp(Vector2D{0, 0}, monitorAvailable); + calcSize = calcSize.clamp(minSize, maxSize); calcPos += (availableSpace - calcSize) / 2.0; @@ -760,9 +762,9 @@ void CHyprMasterLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorne 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->m_realSize = (PWINDOW->m_realSize->goal() + pixResize) + .clamp(PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}), + PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY})); PWINDOW->updateWindowDecos(); return; } @@ -917,7 +919,7 @@ void CHyprMasterLayout::fullscreenRequestForWindow(PHLWINDOW pWindow, const eFul *pWindow->m_realPosition = pWindow->m_lastFloatingPosition; *pWindow->m_realSize = pWindow->m_lastFloatingSize; - pWindow->unsetWindowData(PRIORITY_LAYOUT); + pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); pWindow->updateWindowData(); } } else { diff --git a/src/managers/ANRManager.cpp b/src/managers/ANRManager.cpp index 65e0dea13..daab4d0a9 100644 --- a/src/managers/ANRManager.cpp +++ b/src/managers/ANRManager.cpp @@ -8,6 +8,7 @@ #include "./eventLoop/EventLoopManager.hpp" #include "../config/ConfigValue.hpp" #include "../xwayland/XSurface.hpp" +#include "../i18n/Engine.hpp" using namespace Hyprutils::OS; @@ -83,7 +84,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,16 +177,29 @@ 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, {}); - dialogBox->open()->then([dialogWmPID, this](SP> r) { + dialogBox = + CAsyncDialogBox::create(I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_TITLE, {}), + I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_CONTENT, + { + // + {"class", appClass.empty() ? I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_PROP_UNKNOWN, {}) : appClass}, // + {"title", appName.empty() ? I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_PROP_UNKNOWN, {}) : appName} // + }), + std::vector{ + // + OPTION_TERMINATE_STR, // + OPTION_WAIT_STR // + } // + ); + + dialogBox->open()->then([dialogWmPID, this, OPTION_TERMINATE_STR, OPTION_WAIT_STR](SP> r) { if (r->hasError()) { Debug::log(ERR, "CANRManager::SANRData::runDialog: error spawning dialog"); return; @@ -193,9 +207,9 @@ void CANRManager::SANRData::runDialog(const std::string& title, const std::strin 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); diff --git a/src/managers/ANRManager.hpp b/src/managers/ANRManager.hpp index f5c0085ba..286e834f9 100644 --- a/src/managers/ANRManager.hpp +++ b/src/managers/ANRManager.hpp @@ -39,7 +39,7 @@ 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; diff --git a/src/managers/KeybindManager.cpp b/src/managers/KeybindManager.cpp index 8194f7394..feee03693 100644 --- a/src/managers/KeybindManager.cpp +++ b/src/managers/KeybindManager.cpp @@ -21,6 +21,8 @@ #include "../render/Renderer.hpp" #include "../hyprerror/HyprError.hpp" #include "../config/ConfigManager.hpp" +#include "../desktop/rule/windowRule/WindowRule.hpp" +#include "../desktop/rule/Engine.hpp" #include #include @@ -929,18 +931,20 @@ uint64_t CKeybindManager::spawnWithRules(std::string args, PHLWORKSPACE pInitial args = args.substr(args.find_first_of(']') + 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)}); - } + execToken = rule->execToken(); - Debug::log(LOG, "Applied {} rule arguments for exec.", RULESLIST.size()); + Desktop::Rule::ruleEngine()->registerRule(std::move(rule)); + + Debug::log(LOG, "Applied rule arguments for exec."); } + const uint64_t PROC = spawnRawProc(args, pInitialWorkspace, execToken); + return PROC; } @@ -949,7 +953,7 @@ 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) { +uint64_t CKeybindManager::spawnRawProc(std::string args, PHLWORKSPACE pInitialWorkspace, const std::string& execRuleToken) { Debug::log(LOG, "Executing {}", args); const auto HLENV = getHyprlandLaunchEnv(pInitialWorkspace); @@ -971,6 +975,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) { @@ -1344,7 +1350,7 @@ SDispatchResult CKeybindManager::fullscreenStateActive(std::string args) { 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 { @@ -1370,7 +1376,8 @@ SDispatchResult CKeybindManager::fullscreenStateActive(std::string args) { 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 {}; } @@ -2363,9 +2370,9 @@ SDispatchResult CKeybindManager::tagWindow(std::string args) { 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 {}; @@ -2756,8 +2763,7 @@ SDispatchResult CKeybindManager::pinActive(std::string args) { PWINDOW->m_workspace = PMONITOR->m_activeWorkspace; - PWINDOW->updateDynamicRules(); - g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW); + PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_PINNED); const auto PWORKSPACE = PWINDOW->m_workspace; @@ -2887,7 +2893,7 @@ SDispatchResult CKeybindManager::lockActiveGroup(std::string args) { else PHEAD->m_groupData.locked = false; - g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW); + PWINDOW->updateDecorationValues(); return {}; } @@ -3064,7 +3070,7 @@ SDispatchResult CKeybindManager::moveWindowOrGroup(std::string args) { PWINDOW->warpCursor(); } - g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW); + PWINDOW->updateDecorationValues(); return {}; } @@ -3092,7 +3098,7 @@ SDispatchResult CKeybindManager::denyWindowFromGroup(std::string args) { else PWINDOW->m_groupData.deny = args == "on"; - g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW); + PWINDOW->updateDecorationValues(); return {}; } @@ -3142,6 +3148,39 @@ 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 (...) { Debug::log(ERR, "Hyprctl: parsePropTrivial: failed to parse setprop for {}", s); } +} + SDispatchResult CKeybindManager::setProp(std::string args) { CVarList vars(args, 3, ' '); @@ -3157,37 +3196,18 @@ 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") { + PWINDOW->m_ruleApplicator->maxSizeOverride(Desktop::Types::COverridableVar(configStringToVector2D(VAL), Desktop::Types::PRIORITY_SET_PROP)); + PWINDOW->clampWindowSize(std::nullopt, PWINDOW->m_ruleApplicator->maxSize().value()); PWINDOW->setHidden(false); - } else if (PROP == "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") { + PWINDOW->m_ruleApplicator->minSizeOverride(Desktop::Types::COverridableVar(configStringToVector2D(VAL), Desktop::Types::PRIORITY_SET_PROP)); + PWINDOW->clampWindowSize(std::nullopt, PWINDOW->m_ruleApplicator->minSize().value()); PWINDOW->setHidden(false); - } else if (PROP == "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,43 +3228,101 @@ 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)) { + if (!(PWINDOW->m_ruleApplicator->noFocus().valueOrDefault() == noFocus)) { g_pCompositor->focusWindow(nullptr); g_pCompositor->focusWindow(PWINDOW); g_pCompositor->focusWindow(PLASTWINDOW); diff --git a/src/managers/KeybindManager.hpp b/src/managers/KeybindManager.hpp index e3433a100..b4100beb0 100644 --- a/src/managers/KeybindManager.hpp +++ b/src/managers/KeybindManager.hpp @@ -164,7 +164,7 @@ class CKeybindManager { static void moveWindowOutOfGroup(PHLWINDOW pWindow, const std::string& dir = ""); static void moveWindowIntoGroup(PHLWINDOW pWindow, PHLWINDOW pWindowInDirection); static void switchToWindow(PHLWINDOW PWINDOWTOCHANGETO, bool preserveFocusHistory = false); - static uint64_t spawnRawProc(std::string, PHLWORKSPACE pInitialWorkspace); + static uint64_t spawnRawProc(std::string, PHLWORKSPACE pInitialWorkspace, const std::string& execRuleToken = ""); static uint64_t spawnWithRules(std::string, PHLWORKSPACE pInitialWorkspace); // -------------- Dispatchers -------------- // diff --git a/src/managers/animation/AnimationManager.cpp b/src/managers/animation/AnimationManager.cpp index f38f4ccf1..38efb829a 100644 --- a/src/managers/animation/AnimationManager.cpp +++ b/src/managers/animation/AnimationManager.cpp @@ -98,7 +98,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 +142,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(); diff --git a/src/managers/animation/DesktopAnimationManager.cpp b/src/managers/animation/DesktopAnimationManager.cpp index f156dfa97..8a3405544 100644 --- a/src/managers/animation/DesktopAnimationManager.cpp +++ b/src/managers/animation/DesktopAnimationManager.cpp @@ -41,8 +41,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 +106,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(); diff --git a/src/managers/input/IdleInhibitor.cpp b/src/managers/input/IdleInhibitor.cpp index 82f43f47c..851e917a6 100644 --- a/src/managers/input/IdleInhibitor.cpp +++ b/src/managers/input/IdleInhibitor.cpp @@ -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) diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index fe93a83bb..d1d8ec15c 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -50,9 +50,6 @@ 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; @@ -66,6 +63,9 @@ CInputManager::CInputManager() { m_cursorSurfaceInfo.name = event.shapeName; m_cursorSurfaceInfo.hidden = false; + if (!cursorImageUnlocked()) + return; + g_pHyprRenderer->setCursorFromName(m_cursorSurfaceInfo.name); }); @@ -546,6 +546,13 @@ 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) { @@ -578,7 +585,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st // 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_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); @@ -646,9 +653,6 @@ void CInputManager::onMouseButton(IPointer::SButtonEvent e) { } void CInputManager::processMouseRequest(const CSeatManager::SSetCursorEvent& event) { - if (!cursorImageUnlocked()) - return; - Debug::log(LOG, "cursorImage request: surface {:x}", rc(event.surf.get())); if (event.surf != m_cursorSurfaceInfo.wlSurface->resource()) { @@ -668,6 +672,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); } diff --git a/src/managers/input/trackpad/gestures/CloseGesture.cpp b/src/managers/input/trackpad/gestures/CloseGesture.cpp index 59beb7121..ad2d0f451 100644 --- a/src/managers/input/trackpad/gestures/CloseGesture.cpp +++ b/src/managers/input/trackpad/gestures/CloseGesture.cpp @@ -133,7 +133,7 @@ void CCloseTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) return; g_pLayoutManager->getCurrentLayout()->recalculateWindow(window.lock()); - g_pCompositor->updateWindowAnimatedDecorationValues(window.lock()); + window->updateDecorationValues(); window->sendWindowSize(true); *window->m_alpha = 1.F; }, diff --git a/src/managers/permissions/DynamicPermissionManager.cpp b/src/managers/permissions/DynamicPermissionManager.cpp index af1de990b..a54847737 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; @@ -57,17 +58,6 @@ static const char* permissionToString(eDynamicPermissionType type) { 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?"; - } - - return "error"; -} - static const char* specialPidToString(eSpecialPidTypes type) { switch (type) { case SPECIAL_PID_TYPE_CONFIG: return "config"; @@ -244,39 +234,41 @@ 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_PLUGIN: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, {{"app", appName}, {"plugin", binaryPath}}); break; + case PERMISSION_TYPE_KEYBOARD: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_KEYBOARD, {{"keyboard", binaryPath}}); break; + case PERMISSION_TYPE_UNKNOWN: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_UNKNOWN, {{"app", appName}}); break; + } + 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) { @@ -286,7 +278,7 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s } 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; @@ -303,15 +295,15 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s Debug::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/meson.build b/src/meson.build deleted file mode 100644 index d0a1590e8..000000000 --- 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 c5def6a5f..031b1def6 100644 --- a/src/plugins/HookSystem.cpp +++ b/src/plugins/HookSystem.cpp @@ -144,6 +144,12 @@ bool CFunctionHook::hook() { return false; #endif + if (g_pFunctionHookSystem->m_activeHooks.contains(rc(m_source))) { + // TODO: return actual error codes... + Debug::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}; @@ -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); diff --git a/src/plugins/HookSystem.hpp b/src/plugins/HookSystem.hpp index cf098dd1b..3431e8c8f 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/PluginSystem.cpp b/src/plugins/PluginSystem.cpp index 740b2cce8..27e8232ec 100644 --- a/src/plugins/PluginSystem.cpp +++ b/src/plugins/PluginSystem.cpp @@ -9,6 +9,7 @@ #include "../managers/eventLoop/EventLoopManager.hpp" #include "../managers/permissions/DynamicPermissionManager.hpp" #include "../debug/HyprNotificationOverlay.hpp" +#include "../i18n/Engine.hpp" CPluginSystem::CPluginSystem() { g_pFunctionHookSystem = makeUnique(); @@ -224,7 +225,8 @@ void CPluginSystem::updateConfigPlugins(const std::vector& plugins, 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); + 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; } diff --git a/src/protocols/Screencopy.cpp b/src/protocols/Screencopy.cpp index 1b47fb66e..84487a187 100644 --- a/src/protocols/Screencopy.cpp +++ b/src/protocols/Screencopy.cpp @@ -230,7 +230,7 @@ void CScreencopyFrame::renderMon() { }; for (auto const& l : g_pCompositor->m_layers) { - if (!l->m_noScreenShare) + if (!l->m_ruleApplicator->noScreenShare().valueOrDefault()) continue; if UNLIKELY ((!l->m_mapped && !l->m_fadingOut) || l->m_alpha->value() == 0.f) @@ -246,11 +246,12 @@ void CScreencopyFrame::renderMon() { const auto geom = l->m_geometry; const Vector2D popupBaseOffset = REALPOS - Vector2D{geom.pos().x, geom.pos().y}; - l->m_popupHead->breadthfirst(hidePopups(popupBaseOffset), nullptr); + if (l->m_popupHead) + l->m_popupHead->breadthfirst(hidePopups(popupBaseOffset), nullptr); } for (auto const& w : g_pCompositor->m_windows) { - if (!w->m_windowData.noScreenShare.valueOrDefault()) + if (!w->m_ruleApplicator->noScreenShare().valueOrDefault()) continue; if (!g_pHyprRenderer->shouldRenderWindow(w, m_monitor.lock())) @@ -271,7 +272,7 @@ void CScreencopyFrame::renderMon() { .scale(m_monitor->m_scale) .translate(-m_box.pos()); - const auto dontRound = w->isEffectiveInternalFSMode(FSMODE_FULLSCREEN) || w->m_windowData.noRounding.valueOrDefault(); + const auto dontRound = w->isEffectiveInternalFSMode(FSMODE_FULLSCREEN); const auto rounding = dontRound ? 0 : w->rounding() * m_monitor->m_scale; const auto roundingPower = dontRound ? 2.0f : w->roundingPower(); diff --git a/src/protocols/ShortcutsInhibit.cpp b/src/protocols/ShortcutsInhibit.cpp index b33db9989..749390cd0 100644 --- a/src/protocols/ShortcutsInhibit.cpp +++ b/src/protocols/ShortcutsInhibit.cpp @@ -70,7 +70,7 @@ bool CKeyboardShortcutsInhibitProtocol::isInhibited() { if (!g_pCompositor->m_lastFocus) 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(g_pCompositor->m_lastFocus.lock()); PWINDOW && PWINDOW->m_ruleApplicator->noShortcutsInhibit().valueOrDefault()) return false; for (auto const& in : m_inhibitors) { diff --git a/src/protocols/ToplevelExport.cpp b/src/protocols/ToplevelExport.cpp index eb0a39aa6..c66c1f2b7 100644 --- a/src/protocols/ToplevelExport.cpp +++ b/src/protocols/ToplevelExport.cpp @@ -257,7 +257,7 @@ bool CToplevelExportFrame::copyShm(const Time::steady_tp& now) { // render client at 0,0 if (PERM == PERMISSION_RULE_ALLOW_MODE_ALLOW) { - if (!m_window->m_windowData.noScreenShare.valueOrDefault()) { + if (!m_window->m_ruleApplicator->noScreenShare().valueOrDefault()) { g_pHyprRenderer->m_bBlockSurfaceFeedback = g_pHyprRenderer->shouldRenderWindow(m_window); // block the feedback to avoid spamming the surface if it's visible g_pHyprRenderer->renderWindow(m_window, PMONITOR, now, false, RENDER_PASS_ALL, true, true); g_pHyprRenderer->m_bBlockSurfaceFeedback = false; @@ -339,7 +339,7 @@ bool CToplevelExportFrame::copyDmabuf(const Time::steady_tp& now) { g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 1.0)); if (PERM == PERMISSION_RULE_ALLOW_MODE_ALLOW) { - if (!m_window->m_windowData.noScreenShare.valueOrDefault()) { + if (!m_window->m_ruleApplicator->noScreenShare().valueOrDefault()) { g_pHyprRenderer->m_bBlockSurfaceFeedback = g_pHyprRenderer->shouldRenderWindow(m_window); // block the feedback to avoid spamming the surface if it's visible g_pHyprRenderer->renderWindow(m_window, PMONITOR, now, false, RENDER_PASS_ALL, true, true); g_pHyprRenderer->m_bBlockSurfaceFeedback = false; diff --git a/src/protocols/XDGDialog.cpp b/src/protocols/XDGDialog.cpp index c38a10774..c64a8379c 100644 --- a/src/protocols/XDGDialog.cpp +++ b/src/protocols/XDGDialog.cpp @@ -30,7 +30,7 @@ void CXDGDialogV1Resource::updateWindow() { if UNLIKELY (!HLSurface || !HLSurface->getWindow()) return; - g_pCompositor->updateWindowAnimatedDecorationValues(HLSurface->getWindow()); + HLSurface->getWindow()->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_MODAL); } bool CXDGDialogV1Resource::good() { diff --git a/src/protocols/XDGTag.cpp b/src/protocols/XDGTag.cpp index 2966ac900..98c8651f3 100644 --- a/src/protocols/XDGTag.cpp +++ b/src/protocols/XDGTag.cpp @@ -1,5 +1,6 @@ #include "XDGTag.hpp" #include "XDGShell.hpp" +#include "../desktop/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/types/ContentType.cpp b/src/protocols/types/ContentType.cpp index c0a3d30f4..b5b0041c5 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 66fcbca77..68bc7a417 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/render/OpenGL.cpp b/src/render/OpenGL.cpp index baf69b968..b2ec69f3e 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -24,6 +24,7 @@ #include "../managers/CursorManager.hpp" #include "../helpers/fs/FsUtils.hpp" #include "../helpers/MainLoopExecutor.hpp" +#include "../i18n/Engine.hpp" #include "debug/HyprNotificationOverlay.hpp" #include "hyprerror/HyprError.hpp" #include "pass/TexPassElement.hpp" @@ -1006,7 +1007,7 @@ bool CHyprOpenGLImpl::initShaders() { 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); + g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_CM_RELOAD_FAILED), CHyprColor{}, 15000, ICON_WARNING); m_cmSupported = prog > 0; if (m_cmSupported) { @@ -1669,7 +1670,7 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c } } - if (m_renderData.currentWindow && m_renderData.currentWindow->m_windowData.RGBX.valueOrDefault()) { + if (m_renderData.currentWindow && m_renderData.currentWindow->m_ruleApplicator->RGBX().valueOrDefault()) { shader = &m_shaders->m_shRGBX; texType = TEXTURE_RGBX; } @@ -2194,7 +2195,7 @@ 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) @@ -2238,7 +2239,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) @@ -2310,16 +2311,16 @@ bool CHyprOpenGLImpl::shouldUseNewBlurOptimizations(PHLLS pLayer, PHLWINDOW pWin if (!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; @@ -2473,7 +2474,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; @@ -2557,7 +2558,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; diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index c13a54d16..aec2bbc6e 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -26,6 +26,7 @@ #include "../hyprerror/HyprError.hpp" #include "../debug/HyprDebugOverlay.hpp" #include "../debug/HyprNotificationOverlay.hpp" +#include "../i18n/Engine.hpp" #include "helpers/CursorShapes.hpp" #include "helpers/Monitor.hpp" #include "pass/TexPassElement.hpp" @@ -166,7 +167,7 @@ CHyprRenderer::CHyprRenderer() { } 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)); @@ -508,7 +509,7 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T 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.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 +525,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; @@ -536,7 +537,7 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T 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,7 +585,7 @@ 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) { @@ -656,7 +657,7 @@ 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; @@ -713,12 +714,13 @@ void CHyprRenderer::renderLayer(PHLLS pLayer, PHLMONITOR pMonitor, const Time::s 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()); @@ -748,9 +750,9 @@ 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) @@ -768,7 +770,7 @@ 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( @@ -1133,8 +1135,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 { @@ -1576,7 +1582,8 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { Debug::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; } } @@ -1853,7 +1860,8 @@ void CHyprRenderer::arrangeLayersForMonitor(const MONITORID& monitor) { } 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) @@ -2558,7 +2566,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 +2583,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))); } @@ -2627,7 +2635,7 @@ 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))); } @@ -2672,7 +2680,7 @@ bool CHyprRenderer::shouldBlur(PHLLS ls) { 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,7 +2688,7 @@ 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; } diff --git a/src/render/decorations/CHyprBorderDecoration.cpp b/src/render/decorations/CHyprBorderDecoration.cpp index 75298ff75..a082f0738 100644 --- a/src/render/decorations/CHyprBorderDecoration.cpp +++ b/src/render/decorations/CHyprBorderDecoration.cpp @@ -161,6 +161,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 bcc4c84e1..dd82abc53 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"); diff --git a/src/render/decorations/CHyprGroupBarDecoration.cpp b/src/render/decorations/CHyprGroupBarDecoration.cpp index 49fcd3471..3b95d7491 100644 --- a/src/render/decorations/CHyprGroupBarDecoration.cpp +++ b/src/render/decorations/CHyprGroupBarDecoration.cpp @@ -126,6 +126,7 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { static auto PINNERGAP = CConfigValue("group:groupbar:gaps_in"); static auto PKEEPUPPERGAP = CConfigValue("group:groupbar:keep_upper_gap"); static auto PTEXTOFFSET = CConfigValue("group:groupbar:text_offset"); + static auto 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 +145,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; @@ -163,31 +166,24 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& 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)); } @@ -203,32 +199,25 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { (GROUPLOCKED ? m_tGradientLockedInactive : m_tGradientInactive)); if (GRADIENTTEX->m_texID) { CTexPassElement::SRenderData data; - data.tex = GRADIENTTEX; - data.box = rect; + data.tex = GRADIENTTEX; + data.blur = blur; + data.box = rect; 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)); } @@ -608,5 +597,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/subprojects/tracy.wrap b/subprojects/tracy.wrap deleted file mode 100644 index 11b217878..000000000 --- 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 dfb639841..000000000 --- a/subprojects/udis86.wrap +++ /dev/null @@ -1,5 +0,0 @@ -[wrap-file] -method = cmake - -[provide] -udis86 = libudis86_dep diff --git a/systemd/meson.build b/systemd/meson.build deleted file mode 100644 index bc62e95ae..000000000 --- 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